private static void AnalyzeArgument( SyntaxNodeAnalysisContext context, ArgumentSyntax argument, ISymbol constantAttribute ) { // Get the associated parameter var parameter = argument.DetermineParameter( context.SemanticModel, allowParams: false ); // Parameter is somehow null, so do nothing if (parameter == null) { return; } // Parameter is not [Constant], so do nothing if (!HasAttribute(parameter, constantAttribute)) { return; } // Argument is a constant value, so do nothing if (context.SemanticModel.GetConstantValue(argument.Expression).HasValue) { return; } // Argument was defined as [Constant] already, so trust it var argumentSymbol = context.SemanticModel.GetSymbolInfo(argument.Expression).Symbol; if (argumentSymbol != null && HasAttribute(argumentSymbol, constantAttribute)) { return; } // Argument is not constant, so report it context.ReportDiagnostic( Diagnostic.Create( descriptor: Diagnostics.NonConstantPassedToConstantParameter, location: argument.GetLocation(), messageArgs: parameter.Name ) ); }
private static void AddCastAccordingToParameterType( CodeRefactoringContext context, ArgumentSyntax argument, SemanticModel semanticModel) { ITypeSymbol typeSymbol = semanticModel.GetTypeInfo(argument.Expression).ConvertedType; if (typeSymbol == null) { return; } IParameterSymbol parameterSymbol = argument.DetermineParameter( semanticModel, allowParams: false, allowCandidate: true, cancellationToken: context.CancellationToken); if (parameterSymbol == null) { return; } if (typeSymbol.Equals(parameterSymbol.Type)) { return; } context.RegisterRefactoring( $"Add cast to '{parameterSymbol.Type.ToDisplayString(TypeSyntaxRefactoring.SymbolDisplayFormat)}'", cancellationToken => { return(AddCastRefactoring.RefactorAsync( context.Document, argument.Expression, parameterSymbol.Type, cancellationToken)); }); }
private static ArgumentSyntax AddParameterName( ArgumentSyntax argument, SemanticModel semanticModel, CancellationToken cancellationToken = default(CancellationToken)) { if (argument.NameColon == null || argument.NameColon.IsMissing) { IParameterSymbol parameterSymbol = argument.DetermineParameter( semanticModel, allowParams: false, cancellationToken: cancellationToken); if (parameterSymbol != null) { return(argument .WithNameColon( NameColon(parameterSymbol.ToDisplayString(_symbolDisplayFormat)) .WithTrailingTrivia(Space)) .WithTriviaFrom(argument)); } } return(argument); }
public static void AddOrRemoveArgumentName( CodeRefactoringContext context, ArgumentSyntax argument, SemanticModel semanticModel) { if (argument == null) { throw new ArgumentNullException(nameof(argument)); } if (semanticModel == null) { throw new ArgumentNullException(nameof(semanticModel)); } if (argument.NameColon == null) { IParameterSymbol parameterSymbol = argument.DetermineParameter( semanticModel, allowParams: false, cancellationToken: context.CancellationToken); if (parameterSymbol != null) { context.RegisterRefactoring( "Add parameter name", cancellationToken => AddParameterNameAsync(context.Document, argument, parameterSymbol, cancellationToken)); } } else if (!argument.NameColon.IsMissing) { context.RegisterRefactoring( "Remove parameter name", cancellationToken => RemoveParameterNameAsync(context.Document, argument, cancellationToken)); } }
public static ArgumentSyntax AddParameterName( ArgumentSyntax argument, SemanticModel semanticModel, CancellationToken cancellationToken = default(CancellationToken)) { if (argument == null) { throw new ArgumentNullException(nameof(argument)); } if (semanticModel == null) { throw new ArgumentNullException(nameof(semanticModel)); } if (argument.NameColon != null && !argument.NameColon.IsMissing) { return(argument); } IParameterSymbol parameterSymbol = argument.DetermineParameter( semanticModel, allowParams: false, cancellationToken: cancellationToken); if (parameterSymbol == null) { return(argument); } return(argument .WithNameColon( NameColon(parameterSymbol.ToDisplayString(_symbolDisplayFormat)) .WithTrailingTrivia(Space)) .WithTriviaFrom(argument)); }
private static void AnalyzeArgument( SyntaxNodeAnalysisContext context, ISymbol statelessFuncAttribute, ImmutableHashSet <ISymbol> statelessFuncs ) { ArgumentSyntax syntax = context.Node as ArgumentSyntax; SemanticModel model = context.SemanticModel; IParameterSymbol param = syntax.DetermineParameter(model); if (param == null) { return; } ImmutableArray <AttributeData> paramAttributes = param.GetAttributes(); if (!paramAttributes.Any(a => a.AttributeClass == statelessFuncAttribute)) { return; } ExpressionSyntax argument = syntax.Expression; /** * Even though we haven't specifically accounted for its * source if it's a StatelessFunc<T> we're reasonably * certain its been analyzed. */ ISymbol type = model.GetTypeInfo(argument).Type; if (type != null && IsStatelessFunc(type, statelessFuncs)) { return; } Diagnostic diag; SyntaxKind kind = argument.Kind(); switch (kind) { // this is the case when a method reference is used // eg Func<string, int> func = int.Parse case SyntaxKind.SimpleMemberAccessExpression: if (IsStaticMemberAccess(context, argument)) { return; } // non-static member access means that state could // be used / held. // TODO: Look for [Immutable] on the member's type // to determine if the non-static member is safe diag = Diagnostic.Create( Diagnostics.StatelessFuncIsnt, argument.GetLocation(), $"{ argument.ToString() } is not static" ); break; // this is the case when a "delegate" is used // eg delegate( int x, int y ) { return x + y; } case SyntaxKind.AnonymousMethodExpression: // this is the case when the left hand side of the // lambda has parens // eg () => 1, (x, y) => x + y case SyntaxKind.ParenthesizedLambdaExpression: // this is the case when the left hand side of the // lambda does not have parens // eg x => x + 1 case SyntaxKind.SimpleLambdaExpression: bool hasCaptures = TryGetCaptures( context, argument, out ImmutableArray <ISymbol> captures ); if (!hasCaptures) { return; } string captured = string.Join(", ", captures.Select(c => c.Name)); diag = Diagnostic.Create( Diagnostics.StatelessFuncIsnt, argument.GetLocation(), $"Captured variable(s): { captured }" ); break; // this is the case where an expression is invoked, // which returns a Func<T> // eg ( () => { return () => 1 } )() case SyntaxKind.InvocationExpression: // we are rejecting this because it is tricky to // analyze properly, but also a bit ugly and should // never really be necessary diag = Diagnostic.Create( Diagnostics.StatelessFuncIsnt, argument.GetLocation(), $"Invocations are not allowed: { argument.ToString() }" ); break; /** * This is the case where a variable is passed in * eg Foo( f ) * Where f might be a local variable, a parameter, or field * * class C<T> { * StatelessFunc<T> m_f; * * void P( StatelessFunc<T> f ) { Foo( f ); } * void Q( [StatelessFunc] Func<T> f ) { Foo( f ); } * void R() { StatelessFunc<T> f; Foo( f ); } * void S() { Foo( m_f ); } * void T() : this( StaticMemberMethod ) {} * } */ case SyntaxKind.IdentifierName: /** * If it's a local parameter marked with [StatelessFunc] we're reasonably * certain it was analyzed on the caller side. */ if (IsParameterMarkedStateless( model, statelessFuncAttribute, argument as IdentifierNameSyntax, context.CancellationToken )) { return; } if (IsStaticMemberAccess( context, argument as IdentifierNameSyntax )) { return; } /** * If it's any other variable. We're not sure */ diag = Diagnostic.Create( Diagnostics.StatelessFuncIsnt, argument.GetLocation(), $"Unable to determine if { argument.ToString() } is stateless." ); break; default: // we need StatelessFunc<T> to be ultra safe, so we'll // reject usages we do not understand yet diag = Diagnostic.Create( Diagnostics.StatelessFuncIsnt, argument.GetLocation(), $"Unable to determine safety of { argument.ToString() }. This is an unexpectes usage of StatelessFunc<T>" ); break; } context.ReportDiagnostic(diag); }
/// <summary> /// Get the arguments which are unnamed and not "params" /// </summary> private static IEnumerable <ArgParamBinding> GetUnnamedArgs( SemanticModel model, ArgumentListSyntax args ) { for (int idx = 0; idx < args.Arguments.Count; idx++) { ArgumentSyntax arg = args.Arguments[idx]; // Ignore args that already have names if (arg.NameColon != null) { continue; } IParameterSymbol param = arg.DetermineParameter( model, // Don't map params arguments. It's okay that they are // unnamed. Some things like ImmutableArray.Create() could // take a large number of args and we don't want anything to // be named. Named params really suck so we may still // encourage it but APIs that take params and many other // args would suck anyway. allowParams: false ); // Not sure if this can happen but it'd be hard to name this // param so ignore it. if (param == null) { continue; } // IParameterSymbol.Name is documented to be possibly empty in // which case it is "unnamed", so ignore it. if (param.Name == "") { continue; } // C# allows us to create variables with the same names as reserved keywords, // as long as we prefix with @ (e.g. @int is a valid identifier) // So any parameters which are reserved must have the @ prefix string paramName; SyntaxKind paramNameKind = SyntaxFacts.GetKeywordKind(param.Name); if (SyntaxFacts.GetReservedKeywordKinds().Any(reservedKind => reservedKind == paramNameKind)) { paramName = "@" + param.Name; } else { paramName = param.Name; } string psuedoName = GetPsuedoName(arg); if (psuedoName != null) { bool matchesParamName = string.Equals( psuedoName, param.Name, StringComparison.OrdinalIgnoreCase ); if (matchesParamName) { continue; } } yield return(new ArgParamBinding( position: idx, paramName: paramName, // Use the verbatim parameter name if applicable syntax: arg )); } }