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" /**/;