diff --git a/D2L.CodeStyle.sln b/D2L.CodeStyle.sln index f1c91aba..eafd8ee7 100644 --- a/D2L.CodeStyle.sln +++ b/D2L.CodeStyle.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32112.339 +# Visual Studio Version 18 +VisualStudioVersion = 18.5.11626.173 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D2L.CodeStyle.Annotations", "src\D2L.CodeStyle.Annotations\D2L.CodeStyle.Annotations.csproj", "{1812AF7E-59B1-4764-8090-F84446A71C4E}" EndProject @@ -22,6 +22,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.build.props = Directory.build.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D2L.CodeStyle.Analyzers.Rule.Constant", "src\Rules\D2L.CodeStyle.Analyzers.Rule.Constant\D2L.CodeStyle.Analyzers.Rule.Constant\D2L.CodeStyle.Analyzers.Rule.Constant.csproj", "{9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rules", "Rules", "{1D7FCA55-CA25-4264-BB5D-331CF09C1A00}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D2L.CodeStyle.Analyzers.Rule", "src\Rules\D2L.CodeStyle.Analyzers.Rule\D2L.CodeStyle.Analyzers.Rule\D2L.CodeStyle.Analyzers.Rule.csproj", "{04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Constant", "Constant", "{FE0449B9-2A7B-48DD-BBDB-B6A0FB19A2A9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -78,6 +86,22 @@ Global {C7B3A703-A549-48AA-90A6-A145C91D4F85}.Release|Any CPU.Build.0 = Release|Any CPU {C7B3A703-A549-48AA-90A6-A145C91D4F85}.Release|x86.ActiveCfg = Release|Any CPU {C7B3A703-A549-48AA-90A6-A145C91D4F85}.Release|x86.Build.0 = Release|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Debug|x86.Build.0 = Debug|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Release|Any CPU.Build.0 = Release|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Release|x86.ActiveCfg = Release|Any CPU + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9}.Release|x86.Build.0 = Release|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Debug|x86.ActiveCfg = Debug|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Debug|x86.Build.0 = Debug|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Release|Any CPU.Build.0 = Release|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Release|x86.ActiveCfg = Release|Any CPU + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -85,6 +109,9 @@ Global GlobalSection(NestedProjects) = preSolution {1C54E78A-E12B-45C3-BA6E-31DB5C63B389} = {3873E5AD-A61F-4CE4-B2E1-5AD83D25BF79} {2F10EDAF-8585-4B4C-A82A-BABC5D23A186} = {3873E5AD-A61F-4CE4-B2E1-5AD83D25BF79} + {9F66950B-83B2-4A9E-8C26-ABAC97E55CA9} = {FE0449B9-2A7B-48DD-BBDB-B6A0FB19A2A9} + {04534CAC-57B7-4462-BE31-D8C1C6B7BAA7} = {1D7FCA55-CA25-4264-BB5D-331CF09C1A00} + {FE0449B9-2A7B-48DD-BBDB-B6A0FB19A2A9} = {1D7FCA55-CA25-4264-BB5D-331CF09C1A00} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B1068C74-1517-443E-99B8-CB4834D47628} diff --git a/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj b/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj index 22a53545..48d1e0ce 100644 --- a/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj +++ b/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj @@ -6,7 +6,7 @@ D2L.CodeStyle.Analyzers D2L.CodeStyle D2L.CodeStyle analyzers - 0.223.0 + 0.224.0 Apache-2.0 https://github.com/Brightspace/D2L.CodeStyle D2L @@ -27,7 +27,14 @@ + + + + + + + all @@ -49,4 +56,13 @@ + + + all + + + all + + + diff --git a/src/D2L.CodeStyle.SpecTests/D2L.CodeStyle.SpecTests.csproj b/src/D2L.CodeStyle.SpecTests/D2L.CodeStyle.SpecTests.csproj index 790fa362..9a804523 100644 --- a/src/D2L.CodeStyle.SpecTests/D2L.CodeStyle.SpecTests.csproj +++ b/src/D2L.CodeStyle.SpecTests/D2L.CodeStyle.SpecTests.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/D2L.CodeStyle.TestAnalyzers/D2L.CodeStyle.TestAnalyzers.csproj b/src/D2L.CodeStyle.TestAnalyzers/D2L.CodeStyle.TestAnalyzers.csproj index fc80de0b..9458317a 100644 --- a/src/D2L.CodeStyle.TestAnalyzers/D2L.CodeStyle.TestAnalyzers.csproj +++ b/src/D2L.CodeStyle.TestAnalyzers/D2L.CodeStyle.TestAnalyzers.csproj @@ -6,7 +6,7 @@ D2L.CodeStyle.TestAnalyzers D2L.CodeStyle D2L.CodeStyle test analyzers - 0.29.0 + 0.30.0 Apache-2.0 https://github.com/Brightspace/D2L.CodeStyle D2L @@ -26,8 +26,16 @@ + + + + + + + + all @@ -41,4 +49,13 @@ + + + all + + + all + + + diff --git a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs b/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs similarity index 76% rename from src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs rename to src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs index 79490eae..942b2359 100644 --- a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs +++ b/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs @@ -10,6 +10,7 @@ namespace D2L.CodeStyle.Analyzers.ApiUsage { [DiagnosticAnalyzer( LanguageNames.CSharp )] public sealed class ConstantAttributeAnalyzer : DiagnosticAnalyzer { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( Diagnostics.NonConstantPassedToConstantParameter, @@ -31,7 +32,7 @@ CompilationStartAnalysisContext context ); // The D2L.CodeStyle.Annotations reference is optional - if ( constantAttribute == null || constantAttribute .Kind == SymbolKind.ErrorType ) { + if( constantAttribute == null || constantAttribute.Kind == SymbolKind.ErrorType ) { return; } @@ -123,7 +124,7 @@ ISymbol constantAttribute var parameter = argument.Parameter; // Parameter is not [Constant], so do nothing - if( !HasAttribute( parameter, constantAttribute ) ) { + if( !HasAttribute( parameter, constantAttribute )) { return; } @@ -132,6 +133,16 @@ ISymbol constantAttribute return; } + // Argument is inside an expression tree (e.g. Moq Verify/Setup), so do nothing + if( IsInsideExpressionTree( argument ) ) { + return; + } + + // Argument is a mock argument constraint (e.g. Arg.Is.Anything), so do nothing + if( IsMockArgumentConstraint( argument.Value ) ) { + return; + } + // Argument was defined as [Constant] already, so trust it var argumentSymbol = argument.SemanticModel.GetSymbolInfo( argument.Value.Syntax, context.CancellationToken ).Symbol; if( argumentSymbol != null && HasAttribute( argumentSymbol, constantAttribute ) ) { @@ -161,7 +172,7 @@ INamedTypeSymbol constantAttribute } // Operator parameter is not [Constant], so do nothing - IParameterSymbol parameter = @operator.Parameters[ 0 ]; + IParameterSymbol parameter = @operator.Parameters[0]; if( !HasAttribute( parameter, constantAttribute ) ) { return; } @@ -258,6 +269,58 @@ ISymbol attributeSymbol ); } + private static bool IsInsideExpressionTree( IOperation operation ) { + IOperation current = operation; + while( current != null ) { + if( current is IConversionOperation conversion + && conversion.Type is INamedTypeSymbol namedType + && namedType.BaseType != null + && namedType.BaseType.Name == "LambdaExpression" + ) { + return true; + } + current = current.Parent; + } + return false; + } + + private static bool IsMockArgumentConstraint( IOperation operation ) { + // Matches patterns like Arg.Is.Anything or Arg.Is.Equal(...) (Rhino Mocks) + // Arg.Is.Anything is a property chain: Arg.Is (static) -> .Anything (instance) + // Arg.Is.Equal(...) is a method call on the .Is property result + if( operation is IInvocationOperation invocation ) { + var containingType = invocation.TargetMethod.ContainingType; + if( IsArgType( containingType ) ) { + return true; + } + // Check if the instance receiver is an Arg property chain + if( invocation.Instance != null && IsMockArgumentConstraint( invocation.Instance ) ) { + return true; + } + } + + IOperation current = operation; + while( current is IPropertyReferenceOperation propertyRef ) { + var containingType = propertyRef.Property.ContainingType; + if( IsArgType( containingType ) ) { + return true; + } + current = propertyRef.Instance; + } + return false; + } + + private static bool IsArgType( INamedTypeSymbol type ) { + if( type == null ) { + return false; + } + if( type.Name == "Arg" && type.IsGenericType ) { + return true; + } + // Check containing types for nested types within Arg + return IsArgType( type.ContainingType ); + } + private static bool IsStringEmpty( IOperation operation ) { if( operation is IFieldReferenceOperation fieldRef ) { return fieldRef.Field.ContainingType.SpecialType == SpecialType.System_String diff --git a/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant.csproj b/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant.csproj new file mode 100644 index 00000000..c48bb3e7 --- /dev/null +++ b/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + true + true + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule.csproj b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule.csproj new file mode 100644 index 00000000..404aae11 --- /dev/null +++ b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + false + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/Diagnostics.cs similarity index 100% rename from src/D2L.CodeStyle.Analyzers/Diagnostics.cs rename to src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/Diagnostics.cs diff --git a/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/GlobalSuppressions.cs b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/GlobalSuppressions.cs new file mode 100644 index 00000000..dded2a97 --- /dev/null +++ b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/GlobalSuppressions.cs @@ -0,0 +1,12 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "MicrosoftCodeAnalysisReleaseTracking", + "RS2008:Enable analyzer release tracking", + Justification = "Not maintaing a releases file at this time." +)] diff --git a/src/D2L.CodeStyle.Analyzers/Extensions/ReportDiagnosticExtensions.cs b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/ReportDiagnosticExtensions.cs similarity index 93% rename from src/D2L.CodeStyle.Analyzers/Extensions/ReportDiagnosticExtensions.cs rename to src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/ReportDiagnosticExtensions.cs index 77feaf86..128cc544 100644 --- a/src/D2L.CodeStyle.Analyzers/Extensions/ReportDiagnosticExtensions.cs +++ b/src/Rules/D2L.CodeStyle.Analyzers.Rule/D2L.CodeStyle.Analyzers.Rule/ReportDiagnosticExtensions.cs @@ -2,7 +2,7 @@ namespace Microsoft.CodeAnalysis { - internal static class ReportDiagnosticExtensions { + public static class ReportDiagnosticExtensions { public static void ReportDiagnostic( this SourceProductionContext context, @@ -28,7 +28,7 @@ public static void ReportDiagnostic( namespace Microsoft.CodeAnalysis.Diagnostics { - internal static class ReportDiagnosticExtensions { + public static class ReportDiagnosticExtensions { public static void ReportDiagnostic( this CompilationAnalysisContext context, diff --git a/tests/D2L.CodeStyle.Analyzers.Test/D2L.CodeStyle.Analyzers.Tests.csproj b/tests/D2L.CodeStyle.Analyzers.Test/D2L.CodeStyle.Analyzers.Tests.csproj index 0722e33f..f699d725 100644 --- a/tests/D2L.CodeStyle.Analyzers.Test/D2L.CodeStyle.Analyzers.Tests.csproj +++ b/tests/D2L.CodeStyle.Analyzers.Test/D2L.CodeStyle.Analyzers.Tests.csproj @@ -1,6 +1,7 @@  net48 + false @@ -16,6 +17,8 @@ + + diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs index bc39f0af..08655493 100644 --- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs +++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs @@ -1,4 +1,4 @@ -// analyzer: D2L.CodeStyle.Analyzers.ApiUsage.ConstantAttributeAnalyzer, D2L.CodeStyle.Analyzers +// analyzer: D2L.CodeStyle.Analyzers.ApiUsage.ConstantAttributeAnalyzer, D2L.CodeStyle.Analyzers.Rule.Constant using System; diff --git a/tests/D2L.CodeStyle.TestAnalyzers.Tests/D2L.CodeStyle.TestAnalyzers.Tests.csproj b/tests/D2L.CodeStyle.TestAnalyzers.Tests/D2L.CodeStyle.TestAnalyzers.Tests.csproj index 1b993201..7dbfb0a3 100644 --- a/tests/D2L.CodeStyle.TestAnalyzers.Tests/D2L.CodeStyle.TestAnalyzers.Tests.csproj +++ b/tests/D2L.CodeStyle.TestAnalyzers.Tests/D2L.CodeStyle.TestAnalyzers.Tests.csproj @@ -1,6 +1,7 @@  net48 + false