Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public sealed class ConstantAttributeAnalyzer : DiagnosticAnalyzer {
ImmutableArray.Create(
Diagnostics.NonConstantPassedToConstantParameter,
Diagnostics.InvalidConstantType,
Diagnostics.ReferenceToMethodWithConstantParameterNotSupport
Diagnostics.ReferenceToMethodWithConstantParameterNotSupport,
Diagnostics.UnexpectedNumberOfParametersForImplicitOperator
);

public override void Initialize( AnalysisContext context ) {
Expand Down Expand Up @@ -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<string>.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 ) ) {
Expand Down Expand Up @@ -288,5 +299,59 @@ private static bool TypeCanBeConstant( SpecialType specialType ) {
return false;
}
}

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<T>.Is.Anything or Arg<T>.Is.Equal(...) (Rhino Mocks)
// Arg<T>.Is.Anything is a property chain: Arg<string>.Is (static) -> .Anything (instance)
// Arg<T>.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<T> 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<T>
return IsArgType( type.ContainingType );
}
}
}
Loading
Loading