Skip to content

Commit b39866e

Browse files
authored
Add MakeTypesInternalAnalyzer (#6820)
* Add MakeTypesInternalAnalyzer * Add FixAll support * Add header comment. * Add file header * Make all OutputKinds configurable.
1 parent 39ccb5b commit b39866e

28 files changed

+1321
-4
lines changed

docs/Analyzer Configuration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ Users can also provide a comma separated list of above option values. For exampl
133133

134134
Option Name: `output_kind`
135135

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

138140
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.
139141

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Composition;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeFixes;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeQuality.Analyzers.Maintainability;
11+
12+
namespace Microsoft.CodeQuality.CSharp.Analyzers.Maintainability
13+
{
14+
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
15+
public sealed class CSharpMakeTypesInternalFixer : MakeTypesInternalFixer
16+
{
17+
protected override SyntaxNode MakeInternal(SyntaxNode node) =>
18+
node switch
19+
{
20+
TypeDeclarationSyntax type => MakeMemberInternal(type),
21+
EnumDeclarationSyntax @enum => MakeMemberInternal(@enum),
22+
DelegateDeclarationSyntax @delegate => MakeMemberInternal(@delegate),
23+
_ => throw new NotSupportedException()
24+
};
25+
26+
private static SyntaxNode MakeMemberInternal(MemberDeclarationSyntax type)
27+
{
28+
var publicKeyword = type.Modifiers.First(m => m.IsKind(SyntaxKind.PublicKeyword));
29+
var modifiers = type.Modifiers.Replace(publicKeyword, SyntaxFactory.Token(SyntaxKind.InternalKeyword));
30+
31+
return type.WithModifiers(modifiers);
32+
}
33+
}
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.Collections.Immutable;
4+
using Analyzer.Utilities.Extensions;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
using Microsoft.CodeQuality.Analyzers.Maintainability;
10+
11+
namespace Microsoft.CodeQuality.CSharp.Analyzers.Maintainability
12+
{
13+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
14+
public sealed class CSharpMakeTypesInternal : MakeTypesInternal<SyntaxKind>
15+
{
16+
protected override ImmutableArray<SyntaxKind> TypeKinds { get; } =
17+
ImmutableArray.Create(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.RecordDeclaration);
18+
19+
protected override SyntaxKind EnumKind { get; } = SyntaxKind.EnumDeclaration;
20+
21+
protected override ImmutableArray<SyntaxKind> DelegateKinds { get; } = ImmutableArray.Create(SyntaxKind.DelegateDeclaration);
22+
23+
protected override void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context)
24+
{
25+
var type = (TypeDeclarationSyntax)context.Node;
26+
ReportIfPublic(context, type.Modifiers, type.Identifier);
27+
}
28+
29+
protected override void AnalyzeEnumDeclaration(SyntaxNodeAnalysisContext context)
30+
{
31+
var @enum = (EnumDeclarationSyntax)context.Node;
32+
ReportIfPublic(context, @enum.Modifiers, @enum.Identifier);
33+
}
34+
35+
protected override void AnalyzeDelegateDeclaration(SyntaxNodeAnalysisContext context)
36+
{
37+
var @delegate = (DelegateDeclarationSyntax)context.Node;
38+
ReportIfPublic(context, @delegate.Modifiers, @delegate.Identifier);
39+
}
40+
41+
private static void ReportIfPublic(SyntaxNodeAnalysisContext context, SyntaxTokenList modifiers, SyntaxToken identifier)
42+
{
43+
if (modifiers.Any(SyntaxKind.PublicKeyword))
44+
{
45+
context.ReportDiagnostic(identifier.CreateDiagnostic(Rule));
46+
}
47+
}
48+
}
49+
}

src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
Rule ID | Category | Severity | Notes
66
--------|----------|----------|-------
77
CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514)
8+
CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.Collections.Immutable;
4+
using System.Threading.Tasks;
5+
using Analyzer.Utilities;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeActions;
8+
using Microsoft.CodeAnalysis.CodeFixes;
9+
10+
namespace Microsoft.CodeQuality.Analyzers.Maintainability
11+
{
12+
public abstract class MakeTypesInternalFixer : CodeFixProvider
13+
{
14+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
15+
{
16+
var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
17+
var node = root.FindNode(context.Span);
18+
19+
var codeAction = CodeAction.Create(
20+
MicrosoftCodeQualityAnalyzersResources.MakeTypesInternalCodeFixTitle,
21+
_ =>
22+
{
23+
var newNode = MakeInternal(node);
24+
var newRoot = root.ReplaceNode(node, newNode.WithTriviaFrom(node));
25+
26+
return Task.FromResult(context.Document.WithSyntaxRoot(newRoot));
27+
},
28+
MicrosoftCodeQualityAnalyzersResources.MakeTypesInternalCodeFixTitle);
29+
context.RegisterCodeFix(codeAction, context.Diagnostics);
30+
}
31+
32+
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
33+
34+
protected abstract SyntaxNode MakeInternal(SyntaxNode node);
35+
36+
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(MakeTypesInternal<SymbolKind>.RuleId);
37+
}
38+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using Analyzer.Utilities;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
10+
namespace Microsoft.CodeQuality.Analyzers.Maintainability
11+
{
12+
using static MicrosoftCodeQualityAnalyzersResources;
13+
14+
public abstract class MakeTypesInternal<TSyntaxKind> : DiagnosticAnalyzer
15+
where TSyntaxKind : struct, Enum
16+
{
17+
internal const string RuleId = "CA1515";
18+
19+
protected static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
20+
RuleId,
21+
CreateLocalizableResourceString(nameof(MakeTypesInternalTitle)),
22+
CreateLocalizableResourceString(nameof(MakeTypesInternalMessage)),
23+
DiagnosticCategory.Maintainability,
24+
RuleLevel.Disabled,
25+
description: CreateLocalizableResourceString(nameof(MakeTypesInternalDescription)),
26+
isPortedFxCopRule: false,
27+
isDataflowRule: false);
28+
29+
private static readonly ImmutableHashSet<OutputKind> DefaultOutputKinds =
30+
ImmutableHashSet.Create(OutputKind.ConsoleApplication, OutputKind.WindowsApplication, OutputKind.WindowsRuntimeApplication);
31+
32+
public override void Initialize(AnalysisContext context)
33+
{
34+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
35+
context.EnableConcurrentExecution();
36+
context.RegisterCompilationStartAction(context =>
37+
{
38+
var compilation = context.Compilation;
39+
if (context.Compilation.SyntaxTrees.FirstOrDefault() is not { } firstSyntaxTree
40+
|| !context.Options.GetOutputKindsOption(Rule, firstSyntaxTree, compilation, DefaultOutputKinds).Contains(compilation.Options.OutputKind))
41+
{
42+
return;
43+
}
44+
45+
context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, TypeKinds);
46+
context.RegisterSyntaxNodeAction(AnalyzeEnumDeclaration, EnumKind);
47+
context.RegisterSyntaxNodeAction(AnalyzeDelegateDeclaration, DelegateKinds);
48+
});
49+
}
50+
51+
protected abstract ImmutableArray<TSyntaxKind> TypeKinds { get; }
52+
53+
protected abstract TSyntaxKind EnumKind { get; }
54+
55+
protected abstract ImmutableArray<TSyntaxKind> DelegateKinds { get; }
56+
57+
protected abstract void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context);
58+
59+
protected abstract void AnalyzeEnumDeclaration(SyntaxNodeAnalysisContext context);
60+
61+
protected abstract void AnalyzeDelegateDeclaration(SyntaxNodeAnalysisContext context);
62+
63+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
64+
}
65+
}

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,4 +1293,16 @@
12931293
<data name="CollectionsShouldImplementGenericInterfaceMultipleMessage" xml:space="preserve">
12941294
<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>
12951295
</data>
1296+
<data name="MakeTypesInternalCodeFixTitle" xml:space="preserve">
1297+
<value>Make the public type internal</value>
1298+
</data>
1299+
<data name="MakeTypesInternalDescription" xml:space="preserve">
1300+
<value>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</value>
1301+
</data>
1302+
<data name="MakeTypesInternalMessage" xml:space="preserve">
1303+
<value>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</value>
1304+
</data>
1305+
<data name="MakeTypesInternalTitle" xml:space="preserve">
1306+
<value>Consider making public types internal</value>
1307+
</data>
12961308
</root>

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,26 @@
357357
<target state="translated">Používat PascalCase pro pojmenované zástupné objekty</target>
358358
<note />
359359
</trans-unit>
360+
<trans-unit id="MakeTypesInternalCodeFixTitle">
361+
<source>Make the public type internal</source>
362+
<target state="new">Make the public type internal</target>
363+
<note />
364+
</trans-unit>
365+
<trans-unit id="MakeTypesInternalDescription">
366+
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
367+
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
368+
<note />
369+
</trans-unit>
370+
<trans-unit id="MakeTypesInternalMessage">
371+
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
372+
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
373+
<note />
374+
</trans-unit>
375+
<trans-unit id="MakeTypesInternalTitle">
376+
<source>Consider making public types internal</source>
377+
<target state="new">Consider making public types internal</target>
378+
<note />
379+
</trans-unit>
360380
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
361381
<source>Apply 'AttributeUsageAttribute'</source>
362382
<target state="translated">Použít AttributeUsageAttribute</target>

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,26 @@
357357
<target state="translated">PascalCase für benannte Platzhalter verwenden</target>
358358
<note />
359359
</trans-unit>
360+
<trans-unit id="MakeTypesInternalCodeFixTitle">
361+
<source>Make the public type internal</source>
362+
<target state="new">Make the public type internal</target>
363+
<note />
364+
</trans-unit>
365+
<trans-unit id="MakeTypesInternalDescription">
366+
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
367+
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
368+
<note />
369+
</trans-unit>
370+
<trans-unit id="MakeTypesInternalMessage">
371+
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
372+
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
373+
<note />
374+
</trans-unit>
375+
<trans-unit id="MakeTypesInternalTitle">
376+
<source>Consider making public types internal</source>
377+
<target state="new">Consider making public types internal</target>
378+
<note />
379+
</trans-unit>
360380
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
361381
<source>Apply 'AttributeUsageAttribute'</source>
362382
<target state="translated">"AttributeUsageAttribute" anwenden</target>

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,26 @@
357357
<target state="translated">Usar PascalCase para marcadores de posición con nombre</target>
358358
<note />
359359
</trans-unit>
360+
<trans-unit id="MakeTypesInternalCodeFixTitle">
361+
<source>Make the public type internal</source>
362+
<target state="new">Make the public type internal</target>
363+
<note />
364+
</trans-unit>
365+
<trans-unit id="MakeTypesInternalDescription">
366+
<source>Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</source>
367+
<target state="new">Unlike a class library, an application's API isn't typically referenced publicly, so types can be marked internal.</target>
368+
<note />
369+
</trans-unit>
370+
<trans-unit id="MakeTypesInternalMessage">
371+
<source>Because an application's API isn't typically referenced from outside the assembly, types can be made internal</source>
372+
<target state="new">Because an application's API isn't typically referenced from outside the assembly, types can be made internal</target>
373+
<note />
374+
</trans-unit>
375+
<trans-unit id="MakeTypesInternalTitle">
376+
<source>Consider making public types internal</source>
377+
<target state="new">Consider making public types internal</target>
378+
<note />
379+
</trans-unit>
360380
<trans-unit id="MarkAttributesWithAttributeUsageCodeFix">
361381
<source>Apply 'AttributeUsageAttribute'</source>
362382
<target state="translated">Aplicar "AttributeUsageAttribute"</target>

0 commit comments

Comments
 (0)