Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude exception types from CA1515 #7234

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,49 +1,20 @@
// 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>
public sealed class CSharpMakeTypesInternal : MakeTypesInternal
{
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)
protected override SyntaxToken? GetIdentifier(SyntaxNode type)
{
if (modifiers.Any(SyntaxKind.PublicKeyword))
{
context.ReportDiagnostic(identifier.CreateDiagnostic(Rule));
}
return (type as TypeDeclarationSyntax)?.Identifier
?? (type as EnumDeclarationSyntax)?.Identifier
?? (type as DelegateDeclarationSyntax)?.Identifier;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)

protected abstract SyntaxNode MakeInternal(SyntaxNode node);

public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(MakeTypesInternal<SymbolKind>.RuleId);
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(MakeTypesInternal.RuleId);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// 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 Analyzer.Utilities.Extensions;
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
public abstract class MakeTypesInternal : DiagnosticAnalyzer
{
internal const string RuleId = "CA1515";

Expand Down Expand Up @@ -42,23 +41,31 @@ public override void Initialize(AnalysisContext context)
return;
}

context.RegisterSyntaxNodeAction(AnalyzeTypeDeclaration, TypeKinds);
context.RegisterSyntaxNodeAction(AnalyzeEnumDeclaration, EnumKind);
context.RegisterSyntaxNodeAction(AnalyzeDelegateDeclaration, DelegateKinds);
var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
INamedTypeSymbol? exceptionType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemException);
if (exceptionType is null)
{
return;
}

context.RegisterSymbolAction(ctx => AnalyzeType(ctx, exceptionType), SymbolKind.NamedType);
});
}

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);
// Don't warn for exception types, since that may conflict with CA1064.
/// <see cref="Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.ExceptionsShouldBePublicAnalyzer"/>
private void AnalyzeType(SymbolAnalysisContext context, INamedTypeSymbol exceptionType)
{
INamedTypeSymbol namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
if (namedTypeSymbol.IsPublic()
&& !namedTypeSymbol.GetBaseTypes().Any(t => SymbolEqualityComparer.Default.Equals(t, exceptionType))
&& GetIdentifier(namedTypeSymbol.DeclaringSyntaxReferences[0].GetSyntax()) is SyntaxToken identifier)
{
context.ReportDiagnostic(identifier.CreateDiagnostic(Rule));
}
}

protected abstract void AnalyzeDelegateDeclaration(SyntaxNodeAnalysisContext context);
protected abstract SyntaxToken? GetIdentifier(SyntaxNode type);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -16,6 +18,16 @@ namespace Microsoft.CodeQuality.Analyzers.Maintainability.UnitTests
{
public sealed class MakeTypesInternalTests
{
private static readonly IEnumerable<OutputKind> OutputKinds =
[
OutputKind.ConsoleApplication,
OutputKind.WindowsRuntimeApplication,
OutputKind.WindowsApplication
];

public static readonly TheoryData<OutputKind> DiagnosticTriggeringOutputKinds = new(OutputKinds);
public static readonly TheoryData<OutputKind> NonDiagnosticTriggeringOutputKinds = new(Enum.GetValues<OutputKind>().Except(OutputKinds));

[Theory]
[MemberData(nameof(NonDiagnosticTriggeringOutputKinds))]
public async Task LibraryCode_NoDiagnostic(OutputKind outputKind)
Expand Down Expand Up @@ -675,6 +687,98 @@ End Class
""");
}

[Theory]
[MemberData(nameof(DiagnosticTriggeringOutputKinds))]
public async Task ExceptionDerivatives(OutputKind outputKind)
{
await VerifyCsAsync(outputKind,
"""
using System;

internal class Program
{
public static void Main() {}
}

public class MyException : Exception {}

public class MyBaseException : Exception {}

public class MyInheritedException : MyBaseException {}

public class MyArgumentException : ArgumentException {}
""");

await VerifyVbAsync(outputKind,
"""
Imports System

Friend Class Program
Public Shared Sub Main()
End Sub
End Class

Public Class MyException
Inherits Exception
End Class

Public Class MyBaseException
Inherits Exception
End Class

Public Class MyInheritedException
Inherits MyBaseException
End Class

Public Class MyArgumentException
Inherits ArgumentException
End Class
""");
}

[Theory]
[MemberData(nameof(DiagnosticTriggeringOutputKinds))]
public async Task FakeExceptions(OutputKind outputKind)
{
await VerifyCsAsync(outputKind,
"""
internal class Program
{
public static void Main() {}
}

public class [|MyException|] {}
""",
"""
internal class Program
{
public static void Main() {}
}

internal class MyException {}
""");

await VerifyVbAsync(outputKind,
"""
Friend Class Program
Public Shared Sub Main()
End Sub
End Class

Public Class [|MyException|]
End Class
""",
"""
Friend Class Program
Public Shared Sub Main()
End Sub
End Class

Friend Class MyException
End Class
""");
}

private Task VerifyCsAsync(OutputKind outputKind, string testCode, string fixedCode = null)
{
return new VerifyCS.Test
Expand All @@ -695,19 +799,5 @@ private Task VerifyVbAsync(OutputKind outputKind, string testCode, string fixedC
TestState = { OutputKind = outputKind }
}.RunAsync();
}

public static IEnumerable<object[]> DiagnosticTriggeringOutputKinds()
{
yield return new object[] { OutputKind.ConsoleApplication };
yield return new object[] { OutputKind.WindowsRuntimeApplication };
yield return new object[] { OutputKind.WindowsApplication };
}

public static IEnumerable<object[]> NonDiagnosticTriggeringOutputKinds()
{
yield return new object[] { OutputKind.NetModule };
yield return new object[] { OutputKind.DynamicallyLinkedLibrary };
yield return new object[] { OutputKind.WindowsRuntimeMetadata };
}
}
}
Original file line number Diff line number Diff line change
@@ -1,41 +1,32 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

Imports System.Collections.Immutable
Imports Analyzer.Utilities.Extensions
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeQuality.Analyzers.Maintainability

Namespace Microsoft.CodeQuality.VisualBasic.Analyzers.Maintainability
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Public NotInheritable Class BasicMakeTypesInternal
Inherits MakeTypesInternal(Of SyntaxKind)
Inherits MakeTypesInternal

Protected Overrides ReadOnly Property TypeKinds As ImmutableArray(Of SyntaxKind) = ImmutableArray.Create(SyntaxKind.ClassStatement, SyntaxKind.StructureStatement, SyntaxKind.InterfaceStatement)
Protected Overrides ReadOnly Property EnumKind As SyntaxKind = SyntaxKind.EnumStatement
Protected Overrides ReadOnly Property DelegateKinds As ImmutableArray(Of SyntaxKind) = ImmutableArray.Create(SyntaxKind.DelegateFunctionStatement, SyntaxKind.DelegateSubStatement)

Protected Overrides Sub AnalyzeTypeDeclaration(context As SyntaxNodeAnalysisContext)
Dim type = DirectCast(context.Node, TypeStatementSyntax)
ReportIfPublic(context, type.Modifiers, type.Identifier)
End Sub

Protected Overrides Sub AnalyzeEnumDeclaration(context As SyntaxNodeAnalysisContext)
Dim enumStatement = DirectCast(context.Node, EnumStatementSyntax)
ReportIfPublic(context, enumStatement.Modifiers, enumStatement.Identifier)
End Sub
Protected Overrides Function GetIdentifier(type As SyntaxNode) As SyntaxToken?
Dim typeStatement = TryCast(type, TypeStatementSyntax)
If typeStatement IsNot Nothing Then
Return typeStatement.Identifier
End If

Protected Overrides Sub AnalyzeDelegateDeclaration(context As SyntaxNodeAnalysisContext)
Dim delegateStatement = DirectCast(context.Node, DelegateStatementSyntax)
ReportIfPublic(context, delegateStatement.Modifiers, delegateStatement.Identifier)
End Sub
Dim enumStatement = TryCast(type, EnumStatementSyntax)
If enumStatement IsNot Nothing Then
Return enumStatement.Identifier
End If

Private Shared Sub ReportIfPublic(context As SyntaxNodeAnalysisContext, modifiers As SyntaxTokenList, identifier As SyntaxToken)
If modifiers.Any(SyntaxKind.PublicKeyword) Then
context.ReportDiagnostic(identifier.CreateDiagnostic(Rule))
Dim delegateStatement = TryCast(type, DelegateStatementSyntax)
If delegateStatement IsNot Nothing Then
Return delegateStatement.Identifier
End If
End Sub

Return Nothing
End Function
End Class
End Namespace