private static void RemoveExpression(DocumentEditor editor, ArgumentSyntax argument, IMethodSymbol invoker, bool usesUnderscoreNames, CancellationToken cancellationToken) { var invocation = argument.FirstAncestorOrSelf <InvocationExpressionSyntax>(); if (PropertyChanged.TryGetInvokedPropertyChangedName(invocation, editor.SemanticModel, cancellationToken, out var name) == AnalysisResult.Yes) { var property = editor.SemanticModel.GetDeclaredSymbolSafe(argument.FirstAncestorOrSelf <PropertyDeclarationSyntax>(), cancellationToken); if (property?.Name == name) { editor.ReplaceNode( invocation, SyntaxFactory.ParseExpression(Snippet.OnPropertyChanged(invoker, property?.Name, usesUnderscoreNames).TrimEnd(';')) .WithSimplifiedNames() .WithLeadingElasticLineFeed() .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation)); } else { editor.ReplaceNode( invocation, SyntaxFactory.ParseExpression(Snippet.OnOtherPropertyChanged(invoker, name, usesUnderscoreNames).TrimEnd(';')) .WithSimplifiedNames() .WithLeadingElasticLineFeed() .WithTrailingElasticLineFeed() .WithAdditionalAnnotations(Formatter.Annotation)); } } }
private static bool VariableIsNotUsedBeforePassedAsOutArgument(SemanticModel semanticModel, VariableDeclaratorSyntax variableDeclarator, ArgumentSyntax argument) { var firstStatement = variableDeclarator.FirstAncestorOrSelf <StatementSyntax>(); var lastStatement = argument.FirstAncestorOrSelf <StatementSyntax>().PrecedingSyblingOrSelf(); DataFlowAnalysis dataFlow; try { // TODO-IG: Implement a proprietary data flow analysis that will work over statements that do not have same parent. // The AnalyzeDataFlow() method will throw exception if the firstStatement and the lastStatement are // not within the same immediate parent statement. We have to implement a data flow analysis that will // accept statements that have a common root parent statement, but are not necessarily within the same // immediate parent statement. dataFlow = semanticModel.AnalyzeDataFlow(firstStatement, lastStatement); } catch { // TODO-IG: Remove the try-catch block once the data flow analysis is implemented. // At the moment, if the firstStatement and the lastStatement are not within the same immediate parent // we will simply assume that the variable is used, although this must not actually be the case. // Such cases will anyway appear rarely, so we can live with it so far. return(false); } var outVariableName = variableDeclarator.Identifier.ValueText; return(dataFlow.ReadInside.Union(dataFlow.WrittenInside).All(symbol => symbol.Name != outVariableName)); }
private static void ApplyFixGU0005(DocumentEditor editor, ArgumentSyntax nameArgument, CancellationToken cancellationToken) { var argumentListSyntax = nameArgument.FirstAncestorOrSelf <ArgumentListSyntax>(); var arguments = new ArgumentSyntax[argumentListSyntax.Arguments.Count]; var method = editor.SemanticModel.GetSymbolSafe(argumentListSyntax.Parent, cancellationToken) as IMethodSymbol; var messageIndex = ParameterIndex(method, "message"); var nameIndex = ParameterIndex(method, "paramName"); for (var i = 0; i < argumentListSyntax.Arguments.Count; i++) { if (i == messageIndex) { arguments[nameIndex] = argumentListSyntax.Arguments[i]; continue; } if (i == nameIndex) { arguments[messageIndex] = argumentListSyntax.Arguments[i]; continue; } arguments[i] = argumentListSyntax.Arguments[i]; } var updated = argumentListSyntax.WithArguments(SyntaxFactory.SeparatedList(arguments, argumentListSyntax.Arguments.GetSeparators())); editor.ReplaceNode(argumentListSyntax, updated); }
private static Task <Document> ApplyFixGU0005Async(CancellationToken cancellationToken, CodeFixContext context, SemanticModel semanticModel, ArgumentSyntax nameArgument) { var argumentListSyntax = nameArgument.FirstAncestorOrSelf <ArgumentListSyntax>(); var arguments = new ArgumentSyntax[argumentListSyntax.Arguments.Count]; var method = semanticModel.GetSymbolSafe(argumentListSyntax.Parent, cancellationToken) as IMethodSymbol; var messageIndex = ParameterIndex(method, "message"); var nameIndex = ParameterIndex(method, "paramName"); for (var i = 0; i < argumentListSyntax.Arguments.Count; i++) { if (i == messageIndex) { arguments[nameIndex] = argumentListSyntax.Arguments[i]; continue; } if (i == nameIndex) { arguments[messageIndex] = argumentListSyntax.Arguments[i]; continue; } arguments[i] = argumentListSyntax.Arguments[i]; } var updated = argumentListSyntax.WithArguments(SyntaxFactory.SeparatedList(arguments, argumentListSyntax.Arguments.GetSeparators())); return(Task.FromResult(context.Document.WithSyntaxRoot(semanticModel.SyntaxTree.GetRoot().ReplaceNode(argumentListSyntax, updated)))); }
internal static bool TryGetRegisteredName(ArgumentSyntax callback, SemanticModel semanticModel, CancellationToken cancellationToken, out string registeredName) { registeredName = null; if (callback == null) { return(false); } var fieldDeclaration = callback.FirstAncestorOrSelf <VariableDeclaratorSyntax>(); var dependencyProperty = semanticModel.GetDeclaredSymbol(fieldDeclaration) as IFieldSymbol; return(DependencyProperty.TryGetRegisteredName(dependencyProperty, semanticModel, cancellationToken, out registeredName)); }
internal static bool TryGetRegisteredName(ArgumentSyntax callback, SemanticModel semanticModel, CancellationToken cancellationToken, out string registeredName) { registeredName = null; var invocation = callback?.FirstAncestorOrSelf <InvocationExpressionSyntax>(); var memberAccess = invocation?.Expression as MemberAccessExpressionSyntax; if (memberAccess == null) { return(false); } var method = semanticModel.GetSymbolSafe(invocation, cancellationToken) as IMethodSymbol; if (method == KnownSymbol.DependencyProperty.OverrideMetadata || method == KnownSymbol.DependencyProperty.AddOwner) { var dependencyProperty = semanticModel.GetSymbolSafe(memberAccess.Expression, cancellationToken) as IFieldSymbol; if (dependencyProperty?.Type != KnownSymbol.DependencyProperty) { return(false); } return(DependencyProperty.TryGetRegisteredName(dependencyProperty, semanticModel, cancellationToken, out registeredName)); } if (method == KnownSymbol.DependencyProperty.Register || method == KnownSymbol.DependencyProperty.RegisterReadOnly || method == KnownSymbol.DependencyProperty.RegisterAttached || method == KnownSymbol.DependencyProperty.RegisterAttachedReadOnly) { var fieldDeclaration = callback.FirstAncestorOrSelf <VariableDeclaratorSyntax>(); if (fieldDeclaration == null) { return(false); } var dependencyProperty = semanticModel.GetDeclaredSymbolSafe(fieldDeclaration, cancellationToken) as IFieldSymbol; if (dependencyProperty == null) { return(false); } return(DependencyProperty.TryGetRegisteredName(dependencyProperty, semanticModel, cancellationToken, out registeredName)); } return(false); }
internal static int ArgumentIndex(ArgumentSyntax argument) { var argumentListExpression = argument.FirstAncestorOrSelf <ArgumentListSyntax>(); if (argumentListExpression == null) { return(-1); } for (var i = 0; i < argumentListExpression.Arguments.Count; i++) { if (argumentListExpression.Arguments[i] == argument) { return(i); } } return(-1); }
internal static bool TryGetParameter(this ArgumentSyntax argument, SemanticModel semanticModel, CancellationToken cancellationToken, out IParameterSymbol result) { var invocation = (SyntaxNode)argument.FirstAncestor <InvocationExpressionSyntax>() ?? argument.FirstAncestor <ConstructorInitializerSyntax>(); var method = (IMethodSymbol)semanticModel.GetSymbolSafe(invocation, cancellationToken); result = null; if (argument == null || method?.Parameters == null) { return(false); } if (argument.NameColon == null) { var index = argument.FirstAncestorOrSelf <ArgumentListSyntax>() .Arguments.IndexOf(argument); if (method.Parameters.TryElementAt(index, out result)) { return(true); } var temp = method.Parameters[method.Parameters.Length - 1]; if (temp.IsParams) { result = temp; return(true); } return(false); } foreach (var candidate in method.Parameters) { if (candidate.Name == argument.NameColon.Name.Identifier.ValueText) { result = candidate; return(true); } } return(false); }
public override void VisitArgument(ArgumentSyntax node) { if (this.visitedLocations.Add(node) && (node.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword) || node.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword))) { var invocation = node.FirstAncestorOrSelf <InvocationExpressionSyntax>(); var argSymbol = this.semanticModel.GetSymbolSafe(node.Expression, this.cancellationToken); if (invocation != null && (SymbolComparer.Equals(this.CurrentSymbol, argSymbol) || this.refParameters.Contains(argSymbol as IParameterSymbol))) { var method = this.semanticModel.GetSymbolSafe(invocation, this.cancellationToken); if (method != null && method.DeclaringSyntaxReferences.Length > 0) { foreach (var reference in method.DeclaringSyntaxReferences) { var methodDeclaration = reference.GetSyntax(this.cancellationToken) as MethodDeclarationSyntax; if (methodDeclaration.TryGetMatchingParameter(node, out var parameterSyntax)) { var parameterSymbol = this.semanticModel.GetDeclaredSymbolSafe(parameterSyntax, this.cancellationToken); if (parameterSymbol != null) { if (node.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword)) { this.refParameters.Add(parameterSymbol).IgnoreReturnValue(); } if (node.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword)) { this.values.Add(node.Expression); } } } } } } } base.VisitArgument(node); }
internal static bool TryGetMatchingParameter(this BaseMethodDeclarationSyntax method, ArgumentSyntax argument, out ParameterSyntax parameter) { parameter = null; if (argument == null || method?.ParameterList == null) { return(false); } if (argument.NameColon == null) { var index = argument.FirstAncestorOrSelf <ArgumentListSyntax>() .Arguments.IndexOf(argument); if (method.ParameterList.Parameters.TryElementAt(index, out parameter)) { return(true); } parameter = method.ParameterList.Parameters.Last(); foreach (var modifier in parameter.Modifiers) { if (modifier.IsKind(SyntaxKind.ParamsKeyword)) { return(true); } } parameter = null; return(false); } foreach (var candidate in method.ParameterList.Parameters) { if (candidate.Identifier.ValueText == argument.NameColon.Name.Identifier.ValueText) { parameter = candidate; return(true); } } return(false); }
private static int?FindParameterIndexCorrespondingToIndex(IMethodSymbol method, ArgumentSyntax argument) { if (argument.NameColon == null) { var index = argument.FirstAncestorOrSelf <ArgumentListSyntax>() .Arguments.IndexOf(argument); return(index); } for (int i = 0; i < method.Parameters.Length; ++i) { var candidate = method.Parameters[i]; if (candidate.Name == argument.NameColon.Name.Identifier.ValueText) { return(i); } } return(null); }
internal static Result IsArgumentDisposedByReturnValue(ArgumentSyntax argument, SemanticModel semanticModel, CancellationToken cancellationToken, PooledHashSet <SyntaxNode> visited = null) { if (argument?.Parent is ArgumentListSyntax argumentList) { if (argumentList.Parent is InvocationExpressionSyntax invocation && semanticModel.GetSymbolSafe(invocation, cancellationToken) is IMethodSymbol method) { if (method.ContainingType.DeclaringSyntaxReferences.Length == 0) { if (method == KnownSymbol.CompositeDisposable.Add) { return(Result.Yes); } return(method.ReturnsVoid || !IsAssignableTo(method.ReturnType) ? Result.No : Result.AssumeYes); } if (invocation.TryGetMatchingParameter(argument, semanticModel, cancellationToken, out var parameter)) { return(CheckReturnValues(parameter, invocation, semanticModel, cancellationToken, visited)); } return(Result.Unknown); } if (argumentList.Parent is ObjectCreationExpressionSyntax || argumentList.Parent is ConstructorInitializerSyntax) { if (TryGetAssignedFieldOrProperty(argument, semanticModel, cancellationToken, out var member, out var ctor) && member != null) { var initializer = argument.FirstAncestorOrSelf <ConstructorInitializerSyntax>(); if (initializer != null) { if (semanticModel.GetDeclaredSymbolSafe(initializer.Parent, cancellationToken) is IMethodSymbol chainedCtor && chainedCtor.ContainingType != member.ContainingType) { if (TryGetDelRefMethod(chainedCtor.ContainingType, Search.TopLevel, out var disposeMethod)) { return(IsMemberDisposed(member, disposeMethod, semanticModel, cancellationToken) ? Result.Yes : Result.No); } } } return(IsMemberDisposed(member, ctor.ContainingType, semanticModel, cancellationToken)); } if (ctor == null) { return(Result.AssumeYes); } if (ctor.ContainingType.DeclaringSyntaxReferences.Length == 0) { return(IsAssignableTo(ctor.ContainingType) ? Result.AssumeYes : Result.No); } return(Result.No); } } return(Result.Unknown); }
private static bool OutArgumentCanBecomeOutVariableOrCanBeDiscarded(SemanticModel semanticModel, ArgumentSyntax outArgument, bool outArgumentCanBeDiscarded) { var enclosingStatement = outArgument.LastAncestorOrSelf <StatementSyntax>(); // E.g. method body block. var outArgumentIdentifier = (IdentifierNameSyntax)outArgument.Expression; var outVariableName = outArgumentIdentifier.Identifier.ValueText; // 1. The enclosing expression (method/constructor call) must correspond to the TEnclosingExpressionSyntax type. var enclosingExpression = GetEnclosingExpression(); if (enclosingExpression == null) { return(false); } // 2. The out argument must be a local variable. var variableDeclarator = GetLocalVariableDeclaratorForOutArgument(); if (variableDeclarator == null) { return(false); } // 3. If the local variable is initialized within the declaration, it means that it is used. if (variableDeclarator.Initializer != null) { return(false); } // 4. The local variable must not be used before it is passed to the method/constructor as an out argument. // Also, if it is a requirement that the out argument can be discarded, it must not be used // anywhere in code after the out argument. var localVariableSymbol = semanticModel.GetSymbolInfo(outArgumentIdentifier).Symbol; if (localVariableSymbol == null) { return(false); } var localVariableTextSpan = variableDeclarator.Identifier.Span; var outArgumentTextSpan = outArgumentIdentifier.Span; // Find all the usages of the local variable within the enclosing e.g. method body block // that are different then its declaration and the usage in the out variable itself. // Those usage then appear either between the variable declaration and the out argument // or after the out argument. var usagesOfTheLocalVariable = enclosingStatement .DescendantNodes() .OfType <IdentifierNameSyntax>() .Where(identifier => identifier.Identifier.ValueText == outVariableName && identifier.Span != localVariableTextSpan && // It is not the declaration of the local variable itself. identifier != outArgumentIdentifier && // It is not the out argument itself. semanticModel.GetSymbolInfo(identifier).Symbol?.Equals(localVariableSymbol) == true ) .ToList(); var(numberOfUsagesBeforeOutArgument, numberOfUsagesAfterOutArgument) = usagesOfTheLocalVariable .CountMany ( identifier => identifier.Identifier.Span.IsBetween(localVariableTextSpan, outArgumentTextSpan), identifier => identifier.Identifier.Span.IsAfter(outArgumentTextSpan) ); var localVariableCouldBecomeOutVariableOrDiscarded = numberOfUsagesBeforeOutArgument == 0 && ( outArgumentCanBeDiscarded && numberOfUsagesAfterOutArgument == 0 || !outArgumentCanBeDiscarded && numberOfUsagesAfterOutArgument > 0 ); if (!localVariableCouldBecomeOutVariableOrDiscarded) { return(false); } // 5. If turned into out variable, the local variable identifier still has the // scope that contains all of the usages of that identifier that originally // appear in the code after the out variable declaration. // If it can be discarded there is no usages after the out argument. // Thus, there cannot be any issues with the scope. if (outArgumentCanBeDiscarded && numberOfUsagesAfterOutArgument == 0) { return(true); } var outVariableScope = GetOutVariableScope(); // If we have reached here, all the usages of the local variable are actually // after the out argument. We have to check that they are all in the scope // of the out variable. return(usagesOfTheLocalVariable .All(identifier => outVariableScope.IsAnyAncestorOfOrSelf(identifier, enclosingStatement.Parent))); // The enclosing expression can be only a method or constructor call. // We have to additionally check for the case where we have them nested ;-) ExpressionSyntax GetEnclosingExpression() { var result = outArgument.FirstAncestorOrSelf <TEnclosingExpressionSyntax>(); if (result == null) { return(null); } // Check the possibly nested calls. E.g. Method(out j, new Constructor(out i)); var potentialOtherEnclosingExpression = typeof(TEnclosingExpressionSyntax) == typeof(InvocationExpressionSyntax) ? (ExpressionSyntax)outArgument.FirstAncestorOrSelf <ObjectCreationExpressionSyntax>() : outArgument.FirstAncestorOrSelf <InvocationExpressionSyntax>(); if (potentialOtherEnclosingExpression == null) { // No nesting, just return the result. return(result); } // Nesting. Check that the invocation of the type TEnclosingExpressionSyntax comes first. return(potentialOtherEnclosingExpression.IsAncestorOfOrSelf(result, enclosingStatement) ? result : null); } // Why an array here and not a single StatementSyntax node? // We have rare cases like e.g. for loop incrementors // where we do not have a single syntax node that would enclose // the scope but rather a list of mutually "disconnected" nodes. SyntaxNode[] GetOutVariableScope() { var potentialIfStatement = GetPotentialEnclosingIfStatement(); if (potentialIfStatement != null) { return new SyntaxNode[] { potentialIfStatement.FirstAncestorOrSelf <BlockSyntax>() } } ; var potentialSwitchStatement = GetPotentialEnclosingSwitchStatement(); if (potentialSwitchStatement != null) { return new SyntaxNode[] { potentialSwitchStatement.FirstAncestorOrSelf <BlockSyntax>() } } ; var potentialSwitchSection = GetPotentialEnclosingSwitchSection(); if (potentialSwitchSection != null) { return new SyntaxNode[] { (SwitchStatementSyntax)potentialSwitchSection.Parent } } ; var potentialWhileStatement = GetPotentialEnclosingWhileStatement(); if (potentialWhileStatement != null) { return new SyntaxNode[] { potentialWhileStatement } } ; // If the method/constructor invocation is within the while part of // a do-while statement the declared variable will not be visible anywhere // else but within that while part. Also, if we are checking that that method/constructor // invocation as a candidate to have an out variable, we know already that the // out variable is not used anywhere else in the do-while body. // In other words, the returned do-while statement will not contain any access // to the local variable that we want to turn into out variable. // Also, if the out variable is declared in the while part of the do-while loop, // it will not be visible after the while loop. // In other words, its new scope will be just the while part which is effectively // a kind of a "empty" scope. // Also, we ignore the usages like Method(out x, out x); var potentialDoStatement = GetPotentialEnclosingDoStatement(); if (potentialDoStatement != null) { return(new SyntaxNode[0]); } var potentialForStatement = GetPotentialEnclosingForStatement(); if (potentialForStatement != null) { return new SyntaxNode[] { potentialForStatement } } ; var potentialForIncrementors = GetPotentialEnclosingForIncrementors(); if (potentialForIncrementors != default) { return(potentialForIncrementors.Cast <SyntaxNode>().ToArray()); } var potentialForEachStatement = GetPotentialEnclosingForEachStatement(); if (potentialForEachStatement != null) { return new SyntaxNode[] { potentialForEachStatement } } ; var potentialAnonymousFunction = GetPotentialEnclosingAnonymousFunction(); if (potentialAnonymousFunction != null) { return new SyntaxNode[] { potentialAnonymousFunction.Body } } ; // If it is nothing of the above, the method/constructor is called within some // "regular" block. return(new SyntaxNode[] { enclosingExpression.FirstAncestorOrSelf <BlockSyntax>() }); AnonymousFunctionExpressionSyntax GetPotentialEnclosingAnonymousFunction() { var anonymousFunction = enclosingExpression.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>(); if (anonymousFunction == null) { return(null); } // Method/constructor invocation is within an anonymous delegate or lambda expression. // But we have to make sure that it is not within a separate block within that // anonymous delegate or lambda expression. var block = enclosingExpression.FirstAncestorOrSelf <BlockSyntax>(); // Why enclosingStatement.Parent? // Anonymous functions in general do not have to have a block. // So the above search for the first enclosing block could return // the enclosingStatement itself. // So in general case we have to step one step out of it. return(anonymousFunction.IsAncestorOfOrSelf(block, enclosingStatement.Parent) ? null : anonymousFunction); } ForEachStatementSyntax GetPotentialEnclosingForEachStatement() { var forEachStatement = enclosingExpression.FirstAncestorOrSelf <ForEachStatementSyntax>(); if (forEachStatement == null) { return(null); } return(forEachStatement.Expression.IsAncestorOfOrSelf(enclosingExpression, forEachStatement) ? forEachStatement : null); } SeparatedSyntaxList <ExpressionSyntax> GetPotentialEnclosingForIncrementors() { // Is the method/constructor invocation a part of incrementors of a for loop? var forStatement = enclosingExpression.FirstAncestorOrSelf <ForStatementSyntax>(); if (forStatement == null) { return(default);