From b90fdd856a4687036fed4555732eccc5a292f18c Mon Sep 17 00:00:00 2001 From: Muaaz Mahmud Date: Thu, 2 Apr 2026 10:20:39 -0400 Subject: [PATCH 1/3] SPLA-3307: Update ConstantAttributeAnalyzer to check for compound assignment --- .../ApiUsage/ConstantAttributeAnalyzer.cs | 38 +++++++++++++++++++ .../Specs/ConstantAttributeAnalyzer.cs | 32 ++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs index 74178f92..8c6e3c2e 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,35 @@ 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 ) { + 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/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 From 5b4d7ae0940b242b145df942587a14bd4839e684 Mon Sep 17 00:00:00 2001 From: Muaaz Mahmud Date: Thu, 2 Apr 2026 11:14:23 -0400 Subject: [PATCH 2/3] SPLA-3307: Remove check for num of parameters --- .../ApiUsage/ConstantAttributeAnalyzer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs index 8c6e3c2e..f4012370 100644 --- a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs +++ b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs @@ -197,9 +197,6 @@ INamedTypeSymbol constantAttribute if( @operator is null ) { return; } - if( @operator.Parameters.Length != 1 ) { - return; - } // Operator parameter is not [Constant], so do nothing IParameterSymbol parameter = @operator.Parameters[0]; From 7ab3224654448b11b7ceb7ba62cac3c230e807ba Mon Sep 17 00:00:00 2001 From: Muaaz Mahmud Date: Thu, 2 Apr 2026 11:51:53 -0400 Subject: [PATCH 3/3] SPLA-3307: Add report diagnostic for unexpected case --- .../ApiUsage/ConstantAttributeAnalyzer.cs | 7 +++++++ src/D2L.CodeStyle.Analyzers/Diagnostics.cs | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs index f4012370..26dab4c6 100644 --- a/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs +++ b/src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs @@ -197,6 +197,13 @@ INamedTypeSymbol constantAttribute 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]; 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 + ); } }