diff --git a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs index 74178f92..26dab4c6 100644 --- a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs +++ b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs @@ -62,6 +62,15 @@ CompilationStartAnalysisContext context OperationKind.Conversion ); + context.RegisterOperationAction( + ctx => AnalyzeCompoundAssignment( + ctx, + (ICompoundAssignmentOperation)ctx.Operation, + constantAttribute + ), + OperationKind.CompoundAssignment + ); + context.RegisterOperationAction( ctx => AnalyzeMethodReference( ctx, @@ -177,6 +186,39 @@ INamedTypeSymbol constantAttribute ); } + private static void AnalyzeCompoundAssignment( + OperationAnalysisContext context, + ICompoundAssignmentOperation compoundAssignment, + INamedTypeSymbol constantAttribute + ) { + + // Get implicit operator for the compound assignment + IMethodSymbol @operator = compoundAssignment.OutConversion.MethodSymbol; + if( @operator is null ) { + return; + } + if( @operator.Parameters.Length != 1 ) { + context.ReportDiagnostic( + descriptor: Diagnostics.UnexpectedNumberOfParametersForImplicitOperator, + location: compoundAssignment.Value.Syntax.GetLocation() + ); + return; + } + + // Operator parameter is not [Constant], so do nothing + IParameterSymbol parameter = @operator.Parameters[0]; + if( !HasAttribute( parameter, constantAttribute ) ) { + return; + } + + // Compound assignment with operator parameter that has [Constant] cannot be constant, so report it + context.ReportDiagnostic( + descriptor: Diagnostics.NonConstantPassedToConstantParameter, + location: compoundAssignment.Value.Syntax.GetLocation(), + messageArgs: new[] { parameter.Name } + ); + } + private static void AnalyzeMethodReference( OperationAnalysisContext context, IMethodReferenceOperation operation, diff --git a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs index 83e291eb..263daaa9 100644 --- a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs +++ b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs @@ -765,5 +765,14 @@ public static class Diagnostics { defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true ); + + public static readonly DiagnosticDescriptor UnexpectedNumberOfParametersForImplicitOperator = new DiagnosticDescriptor( + id: "D2L0104", + title: "Unexpected number of parameters for implicit operator", + messageFormat: "The implicit operator has an unexpected number of parameters. Update analyzer to handle this case.", + category: "Safety", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ); } } diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs index 255ffb53..29b2b1dd 100644 --- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs +++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs @@ -269,6 +269,22 @@ string untrusted { Types.ConstantStruct v = trusted; } { Types.ConstantStruct v = /* NonConstantPassedToConstantParameter(value) */ variable /**/; } { Types.ConstantStruct v = /* NonConstantPassedToConstantParameter(value) */ untrusted /**/; } + { + Types.ConstantStruct v = Constants.String; + v += /* NonConstantPassedToConstantParameter(value) */ "abc" /**/; + } + { + Types.ConstantStruct v = Constants.String; + v += /* NonConstantPassedToConstantParameter(value) */ trusted /**/; + } + { + Types.ConstantStruct v = Constants.String; + v += /* NonConstantPassedToConstantParameter(value) */ variable /**/; + } + { + Types.ConstantStruct v = Constants.String; + v += /* NonConstantPassedToConstantParameter(value) */ "a" + "b" /**/; + } { Types.NonConstantStruct v = "abc"; } { Types.NonConstantStruct v = Constants.String; } @@ -276,6 +292,22 @@ string untrusted { Types.NonConstantStruct v = trusted; } { Types.NonConstantStruct v = variable; } { Types.NonConstantStruct v = untrusted; } + { + Types.NonConstantStruct v = Constants.String; + v += "abc"; + } + { + Types.NonConstantStruct v = Constants.String; + v += trusted; + } + { + Types.NonConstantStruct v = Constants.String; + v += variable; + } + { + Types.NonConstantStruct v = Constants.String; + v += "a" + "b"; + } } #endregion