diff --git a/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj b/src/D2L.CodeStyle.Analyzers/D2L.CodeStyle.Analyzers.csproj
index b98ce3a9..a2962efb 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.229.0
+ 0.230.0
Apache-2.0
https://github.com/Brightspace/D2L.CodeStyle
D2L
diff --git a/src/D2L.CodeStyle.TestAnalyzers/D2L.CodeStyle.TestAnalyzers.csproj b/src/D2L.CodeStyle.TestAnalyzers/D2L.CodeStyle.TestAnalyzers.csproj
index 211dcfed..f94b35e1 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.32.0
+ 0.33.0
Apache-2.0
https://github.com/Brightspace/D2L.CodeStyle
D2L
diff --git a/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs b/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs
index c5885805..b5b4a97a 100644
--- a/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs
+++ b/src/Rules/D2L.CodeStyle.Analyzers.Rule.Constant/D2L.CodeStyle.Analyzers.Rule.Constant/ConstantAttributeAnalyzer.cs
@@ -162,39 +162,84 @@ private static void AnalyzeConversion(
IConversionOperation conversion,
INamedTypeSymbol constantAttribute
) {
+ // Operand is a constant value or string.Empty, so trust it
+ IOperation operand = conversion.Operand;
+ if( operand.ConstantValue.HasValue || IsStringEmpty( operand ) ) {
+ return;
+ }
- IMethodSymbol @operator = conversion.OperatorMethod;
- if( @operator is null ) {
+ // Operand was defined as [Constant] already, so trust it
+ ISymbol operandSymbol = conversion.SemanticModel.GetSymbolInfo( operand.Syntax ).Symbol;
+ if( operandSymbol is not null && HasAttribute( operandSymbol, constantAttribute ) ) {
return;
}
- if( @operator.Parameters.Length != 1 ) {
+
+ AnalyzeConversion(
+ context,
+ constantAttribute,
+ sourceType: conversion.Operand.Type,
+ targetType: conversion.Type,
+ conversion.Operand.Syntax
+ );
+ }
+
+ private static void AnalyzeConversion(
+ OperationAnalysisContext context,
+ INamedTypeSymbol constantAttribute,
+ ITypeSymbol sourceType,
+ ITypeSymbol targetType,
+ SyntaxNode diagnosticSyntax
+ ) {
+ Conversion conversion = context.Compilation.ClassifyConversion(
+ source: sourceType,
+ destination: targetType
+ );
+
+ if( !conversion.Exists ) {
return;
}
- // Operator parameter is not [Constant], so do nothing
- IParameterSymbol parameter = @operator.Parameters[0];
- if( !HasAttribute( parameter, constantAttribute ) ) {
+ if( conversion.MethodSymbol is { } @operator ) {
+ // Operator parameter is not [Constant], so do nothing
+ IParameterSymbol parameter = @operator.Parameters[ 0 ];
+ if( !HasAttribute( parameter, constantAttribute ) ) {
+ return;
+ }
+
+ // Operand is not constant, so report it
+ context.ReportDiagnostic(
+ descriptor: Diagnostics.NonConstantPassedToConstantParameter,
+ location: diagnosticSyntax.GetLocation(),
+ messageArgs: [ parameter.Name ]
+ );
return;
}
- // Operand is a constant value or string.Empty, so trust it
- IOperation operand = conversion.Operand;
- if( operand.ConstantValue.HasValue || IsStringEmpty( operand ) ) {
+ if( !conversion.IsTupleConversion ) {
return;
}
- // Operand was defined as [Constant] already, so trust it
- ISymbol operandSymbol = conversion.SemanticModel.GetSymbolInfo( operand.Syntax ).Symbol;
- if( operandSymbol is not null && HasAttribute( operandSymbol, constantAttribute ) ) {
+ if( sourceType is not INamedTypeSymbol namedSourceType ) {
return;
}
- // Operand is not constant, so report it
- context.ReportDiagnostic(
- descriptor: Diagnostics.NonConstantPassedToConstantParameter,
- location: operand.Syntax.GetLocation(),
- messageArgs: new[] { parameter.Name }
- );
+ if( targetType is not INamedTypeSymbol namedTargetType ) {
+ return;
+ }
+
+ if( namedSourceType.TypeArguments.Length != namedTargetType.TypeArguments.Length ) {
+ return;
+ }
+
+ for( int i = 0; i < namedSourceType.TypeArguments.Length; i++ ) {
+ AnalyzeConversion(
+ context,
+ constantAttribute,
+ sourceType: namedSourceType.TypeArguments[ i ],
+ targetType: namedTargetType.TypeArguments[ i ],
+ diagnosticSyntax
+ );
+ }
}
private static void AnalyzeCompoundAssignment(
diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs
index 20d09e29..72b31270 100644
--- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs
+++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs
@@ -275,6 +275,12 @@ string untrusted
{ Types.ConstantStruct v = trusted; }
{ Types.ConstantStruct v = /* NonConstantPassedToConstantParameter(value) */ variable /**/; }
{ Types.ConstantStruct v = /* NonConstantPassedToConstantParameter(value) */ untrusted /**/; }
+ { (Types.ConstantStruct X, Types.ConstantStruct Y) v = (Constants.String, Constants.String); }
+ { (Types.ConstantStruct X, Types.ConstantStruct Y) v = (Constants.String, trusted); }
+ { (Types.ConstantStruct X, Types.ConstantStruct Y) v = (Constants.String, /* NonConstantPassedToConstantParameter(value) */ variable /**/); }
+ { (Types.ConstantStruct X, Types.ConstantStruct Y) v = (Constants.String, /* NonConstantPassedToConstantParameter(value) */ untrusted /**/); }
+ { (Types.ConstantStruct X, Types.ConstantStruct Y) v = /* NonConstantPassedToConstantParameter(value) */ true ? (Constants.String, Constants.String) : (Constants.String, Constants.String) /**/; }
+ { (Types.ConstantStruct X, Types.ConstantStruct Y) v = /* NonConstantPassedToConstantParameter(value) */ true ? (Constants.String, variable) : (Constants.String, Constants.String) /**/; }
{
Types.ConstantStruct v = Constants.String;
v += /* NonConstantPassedToConstantParameter(value) */ "abc" /**/;