Skip to content

Commit

Permalink
Add MakeTypesInternalAnalyzer (#6820)
Browse files Browse the repository at this point in the history
* Add MakeTypesInternalAnalyzer

* Add FixAll support

* Add header comment.

* Add file header

* Make all OutputKinds configurable.
  • Loading branch information
CollinAlpert authored Sep 25, 2023
1 parent 39ccb5b commit b39866e
Show file tree
Hide file tree
Showing 28 changed files with 1,321 additions and 4 deletions.
4 changes: 3 additions & 1 deletion docs/Analyzer Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ Users can also provide a comma separated list of above option values. For exampl

Option Name: `output_kind`

Configurable Rules: [CA2007](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)
Configurable Rules:
[CA1515](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515),
[CA2007](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)

Option Values: One or more fields of enum [Microsoft.CodeAnalysis.CompilationOptions.OutputKind](https://learn.microsoft.com/dotnet/api/microsoft.codeanalysis.outputkind) as a comma separated list.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeQuality.Analyzers.Maintainability;

namespace Microsoft.CodeQuality.CSharp.Analyzers.Maintainability
{
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class CSharpMakeTypesInternalFixer : MakeTypesInternalFixer
{
protected override SyntaxNode MakeInternal(SyntaxNode node) =>
node switch
{
TypeDeclarationSyntax type => MakeMemberInternal(type),
EnumDeclarationSyntax @enum => MakeMemberInternal(@enum),
DelegateDeclarationSyntax @delegate => MakeMemberInternal(@delegate),
_ => throw new NotSupportedException()
};

private static SyntaxNode MakeMemberInternal(MemberDeclarationSyntax type)
{
var publicKeyword = type.Modifiers.First(m => m.IsKind(SyntaxKind.PublicKeyword));
var modifiers = type.Modifiers.Replace(publicKeyword, SyntaxFactory.Token(SyntaxKind.InternalKeyword));

return type.WithModifiers(modifiers);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeQuality.Analyzers.Maintainability;

namespace Microsoft.CodeQuality.CSharp.Analyzers.Maintainability
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpMakeTypesInternal : MakeTypesInternal<SyntaxKind>
{
protected override ImmutableArray<SyntaxKind> TypeKinds { get; } =
ImmutableArray.Create(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.RecordDeclaration);

protected override SyntaxKind EnumKind { get; } = SyntaxKind.EnumDeclaration;

protected override ImmutableArray<SyntaxKind> DelegateKinds { get; } = ImmutableArray.Create(SyntaxKind.DelegateDeclaration);

protected override void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context)
{
var type = (TypeDeclarationSyntax)context.Node;
ReportIfPublic(context, type.Modifiers, type.Identifier);
}

protected override void AnalyzeEnumDeclaration(SyntaxNodeAnalysisContext context)
{
var @enum = (EnumDeclarationSyntax)context.Node;
ReportIfPublic(context, @enum.Modifiers, @enum.Identifier);
}

protected override void AnalyzeDelegateDeclaration(SyntaxNodeAnalysisContext context)
{
var @delegate = (DelegateDeclarationSyntax)context.Node;
ReportIfPublic(context, @delegate.Modifiers, @delegate.Identifier);
}

private static void ReportIfPublic(SyntaxNodeAnalysisContext context, SyntaxTokenList modifiers, SyntaxToken identifier)
{
if (modifiers.Any(SyntaxKind.PublicKeyword))
{
context.ReportDiagnostic(identifier.CreateDiagnostic(Rule));
}
}
}
}
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514)
CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;

namespace Microsoft.CodeQuality.Analyzers.Maintainability
{
public abstract class MakeTypesInternalFixer : CodeFixProvider
{
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span);

var codeAction = CodeAction.Create(
MicrosoftCodeQualityAnalyzersResources.MakeTypesInternalCodeFixTitle,
_ =>
{
var newNode = MakeInternal(node);
var newRoot = root.ReplaceNode(node, newNode.WithTriviaFrom(node));

return Task.FromResult(context.Document.WithSyntaxRoot(newRoot));
},
MicrosoftCodeQualityAnalyzersResources.MakeTypesInternalCodeFixTitle);
context.RegisterCodeFix(codeAction, context.Diagnostics);
}

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

protected abstract SyntaxNode MakeInternal(SyntaxNode node);

public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(MakeTypesInternal<SymbolKind>.RuleId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeQuality.Analyzers.Maintainability
{
using static MicrosoftCodeQualityAnalyzersResources;

public abstract class MakeTypesInternal<TSyntaxKind> : DiagnosticAnalyzer
where TSyntaxKind : struct, Enum
{
internal const string RuleId = "CA1515";

protected static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
RuleId,
CreateLocalizableResourceString(nameof(MakeTypesInternalTitle)),
CreateLocalizableResourceString(nameof(MakeTypesInternalMessage)),
DiagnosticCategory.Maintainability,
RuleLevel.Disabled,
description: CreateLocalizableResourceString(nameof(MakeTypesInternalDescription)),
isPortedFxCopRule: false,
isDataflowRule: false);

private static readonly ImmutableHashSet<OutputKind> DefaultOutputKinds =
ImmutableHashSet.Create(OutputKind.ConsoleApplication, OutputKind.WindowsApplication, OutputKind.WindowsRuntimeApplication);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(context =>
{
var compilation = context.Compilation;
if (context.Compilation.SyntaxTrees.FirstOrDefault() is not { } firstSyntaxTree
|| !context.Options.GetOutputKindsOption(Rule, firstSyntaxTree, compilation, DefaultOutputKinds).Contains(compilation.Options.OutputKind))
{
return;
}

context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, TypeKinds);
context.RegisterSyntaxNodeAction(AnalyzeEnumDeclaration, EnumKind);
context.RegisterSyntaxNodeAction(AnalyzeDelegateDeclaration, DelegateKinds);
});
}

protected abstract ImmutableArray<TSyntaxKind> TypeKinds { get; }

protected abstract TSyntaxKind EnumKind { get; }

protected abstract ImmutableArray<TSyntaxKind> DelegateKinds { get; }

protected abstract void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context);

protected abstract void AnalyzeEnumDeclaration(SyntaxNodeAnalysisContext context);

protected abstract void AnalyzeDelegateDeclaration(SyntaxNodeAnalysisContext context);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1293,4 +1293,16 @@
<data name="CollectionsShouldImplementGenericInterfaceMultipleMessage" xml:space="preserve">
<value>Type '{0}' directly or indirectly inherits '{1}' without implementing any of '{2}'. Publicly-visible types should implement the generic version to broaden usability.</value>
</data>
<data name="MakeTypesInternalCodeFixTitle" xml:space="preserve">
<value>Make the public type internal</value>
</data>
<data name="MakeTypesInternalDescription" xml:space="preserve">
<value>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</value>
</data>
<data name="MakeTypesInternalMessage" xml:space="preserve">
<value>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</value>
</data>
<data name="MakeTypesInternalTitle" xml:space="preserve">
<value>Consider making public types internal</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@
<target state="translated">Používat PascalCase pro pojmenované zástupné objekty</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalCodeFixTitle">
<source>Make the public type internal</source>
<target state="new">Make the public type internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalDescription">
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalMessage">
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalTitle">
<source>Consider making public types internal</source>
<target state="new">Consider making public types internal</target>
<note />
</trans-unit>
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
<source>Apply 'AttributeUsageAttribute'</source>
<target state="translated">Použít AttributeUsageAttribute</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@
<target state="translated">PascalCase für benannte Platzhalter verwenden</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalCodeFixTitle">
<source>Make the public type internal</source>
<target state="new">Make the public type internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalDescription">
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalMessage">
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalTitle">
<source>Consider making public types internal</source>
<target state="new">Consider making public types internal</target>
<note />
</trans-unit>
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
<source>Apply 'AttributeUsageAttribute'</source>
<target state="translated">"AttributeUsageAttribute" anwenden</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@
<target state="translated">Usar PascalCase para marcadores de posición con nombre</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalCodeFixTitle">
<source>Make the public type internal</source>
<target state="new">Make the public type internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalDescription">
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalMessage">
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalTitle">
<source>Consider making public types internal</source>
<target state="new">Consider making public types internal</target>
<note />
</trans-unit>
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
<source>Apply 'AttributeUsageAttribute'</source>
<target state="translated">Aplicar "AttributeUsageAttribute"</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@
<target state="translated">Utiliser casse Pascal pour les espaces réservés nommés</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalCodeFixTitle">
<source>Make the public type internal</source>
<target state="new">Make the public type internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalDescription">
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalMessage">
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalTitle">
<source>Consider making public types internal</source>
<target state="new">Consider making public types internal</target>
<note />
</trans-unit>
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
<source>Apply 'AttributeUsageAttribute'</source>
<target state="translated">Appliquer 'AttributeUsageAttribute'</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@
<target state="translated">Usare la notazione Pascal per i segnaposto denominati</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalCodeFixTitle">
<source>Make the public type internal</source>
<target state="new">Make the public type internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalDescription">
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalMessage">
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalTitle">
<source>Consider making public types internal</source>
<target state="new">Consider making public types internal</target>
<note />
</trans-unit>
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
<source>Apply 'AttributeUsageAttribute'</source>
<target state="translated">Applica 'AttributeUsageAttribute'</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,26 @@
<target state="translated">名前付きプレースホルダーに PascalCase を使用してください</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalCodeFixTitle">
<source>Make the public type internal</source>
<target state="new">Make the public type internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalDescription">
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalMessage">
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
<note />
</trans-unit>
<trans-unit id="MakeTypesInternalTitle">
<source>Consider making public types internal</source>
<target state="new">Consider making public types internal</target>
<note />
</trans-unit>
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
<source>Apply 'AttributeUsageAttribute'</source>
<target state="translated">'AttributeUsageAttribute' の適用</target>
Expand Down
Loading

0 comments on commit b39866e

Please sign in to comment.