internal static async Task ComputeRefactoringAsync(RefactoringContext context, ExpressionSyntax expression) { SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false); foreach (Diagnostic diagnostic in semanticModel.GetCompilerDiagnostics(expression.Span, context.CancellationToken)) { if (diagnostic.Id == CSharpErrorCodes.CannotImplicitlyConvertTypeExplicitConversionExists) { if (context.Span.IsEmpty || diagnostic.Location.SourceSpan == expression.Span) { expression = expression .Ancestors() .FirstOrDefault(f => f.Span == diagnostic.Location.SourceSpan) as ExpressionSyntax; if (expression != null && semanticModel.GetTypeSymbol(expression, context.CancellationToken)?.IsNullableOf(SpecialType.System_Boolean) == true) { if (semanticModel.GetTypeInfo(expression, context.CancellationToken).ConvertedType?.IsBoolean() == true || IsCondition(expression)) { RegisterRefactoring(context, expression); } } } } else if (diagnostic.Id == CSharpErrorCodes.OperatorCannotBeAppliedToOperands) { if (context.Span.IsEmpty || diagnostic.Location.SourceSpan == expression.Span) { var binaryExpression = expression .Ancestors() .FirstOrDefault(f => f.Span == diagnostic.Location.SourceSpan) as BinaryExpressionSyntax; if (binaryExpression != null) { ExpressionSyntax left = binaryExpression.Left; if (left.Span.Contains(context.Span)) { if (semanticModel.GetTypeSymbol(left, context.CancellationToken)?.IsNullableOf(SpecialType.System_Boolean) == true) { RegisterRefactoring(context, left); } } else { ExpressionSyntax right = binaryExpression.Right; if (right.Span.Contains(context.Span) && semanticModel.GetTypeSymbol(right, context.CancellationToken)?.IsNullableOf(SpecialType.System_Boolean) == true) { RegisterRefactoring(context, right); } } } } } } }
private static SyntaxNode CreateRootWithUsingFromArgument(SyntaxNode root, ExpressionSyntax childOfArgumentNode, string identifierName) { var arg = childOfArgumentNode.Parent as ArgumentSyntax; var variableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName(@"var")) .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(identifierName)) .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), childOfArgumentNode)))); var args = arg.Parent as ArgumentListSyntax; var newArgs = args.ReplaceNode(arg, arg.WithExpression(SyntaxFactory.IdentifierName(identifierName))); StatementSyntax statement = childOfArgumentNode.FirstAncestorOfType <ExpressionStatementSyntax>(); if (statement != null) { var exprStatement = statement.ReplaceNode(args, newArgs); var newUsingStatment = CreateUsingStatement(exprStatement, SyntaxFactory.Block(exprStatement)) .WithDeclaration(variableDeclaration); return(root.ReplaceNode(statement, newUsingStatment)); } statement = (StatementSyntax)childOfArgumentNode.Ancestors().First(node => node is StatementSyntax); var newStatement = statement.ReplaceNode(args, newArgs); var statementsForUsing = new[] { newStatement }.Concat(GetChildStatementsAfter(statement)); var usingBlock = SyntaxFactory.Block(statementsForUsing); var usingStatement = CreateUsingStatement(newStatement, usingBlock) .WithDeclaration(variableDeclaration); var statementsToReplace = new List <StatementSyntax> { statement }; statementsToReplace.AddRange(statementsForUsing.Skip(1)); return(root.ReplaceNodes(statementsToReplace, (node, _) => node.Equals(statement) ? usingStatement : null)); }
private bool IsInsideConstructorOfTypeThatContainsSymbol(ExpressionSyntax node, IFieldSymbol fieldSymbol) { foreach (var currentNode in node.Ancestors()) { switch (currentNode.Kind()) { case SyntaxKind.ConstructorDeclaration: { var constructorSymbol = _semanticModel.GetDeclaredSymbol(currentNode); return(constructorSymbol.ContainingType == fieldSymbol.ContainingType && constructorSymbol.IsStatic == fieldSymbol.IsStatic); } case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.AnonymousMethodExpression: case SyntaxKind.MethodDeclaration: return(false); default: continue; } } return(false); }
internal static MemberDeclarationSyntax GetContainingMethodOrPropertyOrIndexer(ExpressionSyntax expression) { foreach (SyntaxNode ancestor in expression.Ancestors()) { switch (ancestor.Kind()) { case SyntaxKind.MethodDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.IndexerDeclaration: return((MemberDeclarationSyntax)ancestor); case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.AnonymousMethodExpression: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.DestructorDeclaration: case SyntaxKind.EventDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.OperatorDeclaration: case SyntaxKind.IncompleteMember: return(null); } } return(null); }
protected override async Task<Document> IntroduceLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var containerToGenerateInto = expression.Ancestors().FirstOrDefault(s => s is BlockSyntax || s is ArrowExpressionClauseSyntax || s is LambdaExpressionSyntax); var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant, containerToGenerateInto, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) : default; var declarationStatement = SyntaxFactory.LocalDeclarationStatement( modifiers, SyntaxFactory.VariableDeclaration( GetTypeSyntax(document, expression, cancellationToken), SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression.WithoutTrivia()))))); // If we're inserting into a multi-line parent, then add a newline after the local-var // we're adding. That way we don't end up having it and the starting statement be on // the same line (which will cause indentation to be computed incorrectly). var text = await document.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); if (!text.AreOnSameLine(containerToGenerateInto.GetFirstToken(), containerToGenerateInto.GetLastToken())) { declarationStatement = declarationStatement.WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); } switch (containerToGenerateInto) { case BlockSyntax block: return await IntroduceLocalDeclarationIntoBlockAsync( document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false); case ArrowExpressionClauseSyntax arrowExpression: // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol), // both of which are a shorthand for the getter and always return a value var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent) as IMethodSymbol; var createReturnStatement = !method?.ReturnsVoid ?? true; return RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( document, arrowExpression, expression, newLocalName, declarationStatement, allOccurrences, createReturnStatement, cancellationToken); case LambdaExpressionSyntax lambda: return IntroduceLocalDeclarationIntoLambda( document, lambda, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken); } throw new InvalidOperationException(); }
private static SelectQuerySyntax GetSelectQuery(ExpressionSyntax expression) { var selectQuery = expression.Ancestors().OfType <SelectQuerySyntax>().FirstOrDefault(); if (selectQuery != null) { return(selectQuery); } var orderedQuery = expression.Ancestors().OfType <OrderedQuerySyntax>().FirstOrDefault(); if (orderedQuery != null) { return(orderedQuery.GetAppliedSelectQuery()); } return(null); }
private static SyntaxNode GetNodeRootForAnalysis(ExpressionSyntax expression) { var parentNodeToSpeculate = expression .Ancestors(ascendOutOfTrivia: false) .FirstOrDefault(node => node.Kind() != SyntaxKind.Argument && node.Kind() != SyntaxKind.ArgumentList); return(parentNodeToSpeculate ?? expression); }
private static IEnumerable <ICodeAction> GetOrderByFixes(SelectQuerySyntax selectQuery, ExpressionSyntax expression) { var orderByColumn = expression.Ancestors().OfType <OrderByColumnSyntax>().FirstOrDefault(); if (orderByColumn == null) { return(Enumerable.Empty <ICodeAction>()); } return(GetExpressionFixes(selectQuery, orderByColumn.ColumnSelector, expression)); }
private static SyntaxList <StatementSyntax> InsertLocalVariableDeclarationBeforeCallSite(BlockSyntax block, VariableDeclarationSyntax varDecl, ExpressionSyntax callSite) { var stmts = block.Statements; var callSiteStatement = callSite.Ancestors().First(node => node.Parent.Kind() == SyntaxKind.Block); var i = stmts.IndexOf(stmt => stmt.IsEquivalentTo(callSiteStatement)); var newStmts = stmts.Insert(i, SyntaxFactory.LocalDeclarationStatement(varDecl).WithNewLine()); return(SyntaxFactory.List(newStmts.AsEnumerable())); }
protected override async Task <Document> IntroduceLocalAsync( SemanticDocument document, ExpressionSyntax expression, bool allOccurrences, bool isConstant, CancellationToken cancellationToken) { var containerToGenerateInto = expression.Ancestors().FirstOrDefault(s => s is BlockSyntax || s is ArrowExpressionClauseSyntax || s is LambdaExpressionSyntax); var newLocalNameToken = GenerateUniqueLocalName( document, expression, isConstant, containerToGenerateInto, cancellationToken); var newLocalName = SyntaxFactory.IdentifierName(newLocalNameToken); var modifiers = isConstant ? SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ConstKeyword)) : default; var options = await document.Document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var declarationStatement = SyntaxFactory.LocalDeclarationStatement( modifiers, SyntaxFactory.VariableDeclaration( this.GetTypeSyntax(document, options, expression, isConstant, cancellationToken), SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( newLocalNameToken.WithAdditionalAnnotations(RenameAnnotation.Create()), null, SyntaxFactory.EqualsValueClause(expression.WithoutTrivia()))))); switch (containerToGenerateInto) { case BlockSyntax block: return(await IntroduceLocalDeclarationIntoBlockAsync( document, block, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken).ConfigureAwait(false)); case ArrowExpressionClauseSyntax arrowExpression: // this will be null for expression-bodied properties & indexer (not for individual getters & setters, those do have a symbol), // both of which are a shorthand for the getter and always return a value var method = document.SemanticModel.GetDeclaredSymbol(arrowExpression.Parent) as IMethodSymbol; var createReturnStatement = !method?.ReturnsVoid ?? true; return(RewriteExpressionBodiedMemberAndIntroduceLocalDeclaration( document, arrowExpression, expression, newLocalName, declarationStatement, allOccurrences, createReturnStatement, cancellationToken)); case LambdaExpressionSyntax lambda: return(IntroduceLocalDeclarationIntoLambda( document, lambda, expression, newLocalName, declarationStatement, allOccurrences, cancellationToken)); } throw new InvalidOperationException(); }
private void RemoveLocal(ExpressionSyntax expression, DocumentEditor editor) { var variableDeclaration = expression.Ancestors().OfType <VariableDeclarationSyntax>().FirstOrDefault(); if (variableDeclaration == null) { return; } if (variableDeclaration.Variables.Count > 1) { // Remove the appropriate variabledeclarator var declaratorToRemove = expression.Ancestors().OfType <VariableDeclaratorSyntax>().First(); editor.RemoveNode(declaratorToRemove); } else { // Remove the entire variabledeclaration editor.RemoveNode(variableDeclaration.Ancestors().OfType <LocalDeclarationStatementSyntax>().First()); } }
private bool SkipFieldsFromItsOwnConstructor(TypeDeclarationWithSymbol type, ExpressionSyntax assignmentExpression, ISymbol assignmentSymbol) { var parentConstructor = assignmentExpression.Ancestors().OfType <ConstructorDeclarationSyntax>().FirstOrDefault(); if (parentConstructor == null) { return(true); } return (assignmentSymbol.ContainingType != type.NamedTypeSymbol || assignmentSymbol.IsStatic != parentConstructor.Modifiers.Any(p => p.IsKind(SyntaxKind.StaticKeyword))); }
/// <summary> /// Replaces a certain expression for an identifier if the context is appropriate. /// </summary> /// <param name="expression">The expression (cast or binary as) to be replaced</param> /// <param name="newIdentifier">The new identifier that refers to the extracted variable</param> /// <param name="editor">The <see cref="DocumentEditor" /> used to edit the tree</param> /// <param name="requiresNullableValueAccess"> /// <code>true</code> if a <code>.Value</code> property access needs to be added to the new identifier. /// This is needed in the case of a direct cast inside a larger invocation expression that needs to be replaced with an /// identifier. /// If this <code>.Value</code> wouldn't be added, it would create uncompilable code because you've changed the type /// from int to int?. /// Note that this should only happen in the case of a value type. /// </param> private void ReplaceIdentifier(ExpressionSyntax expression, SyntaxToken newIdentifier, DocumentEditor editor, bool requiresNullableValueAccess = false) { var newIdentifierName = SyntaxFactory.IdentifierName(newIdentifier); if (expression.Ancestors().OfType <InvocationExpressionSyntax>().Any()) { if (requiresNullableValueAccess) { var newAccess = SyntaxFactory.ParseExpression($"{newIdentifier.ValueText}.Value"); editor.ReplaceNode(expression, newAccess); } else { editor.ReplaceNode(expression, newIdentifierName); } return; } if (!expression.Ancestors().OfType <VariableDeclaratorSyntax>().Any()) { editor.ReplaceNode(expression, newIdentifierName); } }
private static bool IsInAsyncFunction(ExpressionSyntax expression) { foreach (var node in expression.Ancestors()) { switch (node.Kind()) { case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.AnonymousMethodExpression: return((node as AnonymousFunctionExpressionSyntax)?.AsyncKeyword.IsMissing == false); case SyntaxKind.MethodDeclaration: return((node as MethodDeclarationSyntax)?.Modifiers.Any(SyntaxKind.AsyncKeyword) == true); default: continue; } } return(false); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var document = context.Document; if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles) { return; } var span = context.Span; if (!span.IsEmpty) { return; } var cancellationToken = context.CancellationToken; if (cancellationToken.IsCancellationRequested) { return; } var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model.IsFromGeneratedCode(cancellationToken)) { return; } var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(span.Start); ExpressionSyntax identifier = token.Parent as IdentifierNameSyntax; if (identifier == null) { return; } // If identifier is a type name, this might be a static member access or similar, don't suggest null checks on it var identifierSymbol = model.GetSymbolInfo(identifier).Symbol; if ((identifierSymbol == null) || (identifierSymbol.IsType())) { return; } // Identifier might be part of a MemberAccessExpression and we need to check it for null as a whole identifier = GetOuterMemberAccessExpression(identifier) ?? identifier; if ((identifier.Parent is ExpressionSyntax) && ConditionContainsNullCheck((ExpressionSyntax)identifier.Parent, identifier)) { return; } var identifierAncestors = identifier.Ancestors(); // Don't surround Return statements with checks if (identifierAncestors.OfType <ReturnStatementSyntax>().Any()) { return; } // If identifier is in a conditional ternary expression, skip refactoring is case of present null check in its condition var conditionalExprParent = identifierAncestors.OfType <TernaryConditionalExpressionSyntax>().FirstOrDefault(); if ((conditionalExprParent != null) && ConditionContainsNullCheck(conditionalExprParent.Condition, identifier)) { return; } // Check identifier type, don't suggest null checks for value types! var identifierType = model.GetTypeInfo(identifier).Type; if ((identifierType == null) || (identifierType.IsValueType && !identifierType.IsNullableType())) { return; } SyntaxNode statementToWrap = identifierAncestors.OfType <ExecutableStatementSyntax>().FirstOrDefault(); if (statementToWrap == null) { return; } // No refactoring if statement is inside of a local variable declaration if (statementToWrap is LocalDeclarationStatementSyntax) { return; } bool wrapWithSingleLineIfStatement = false; SyntaxNode newWrappedStatement = null; var wrappedStatementAncestors = statementToWrap.Ancestors(); if (wrappedStatementAncestors.OfType <SingleLineLambdaExpressionSyntax>().Any()) { // Inside of a single-line lambda => wrap with single line If statement wrapWithSingleLineIfStatement = true; } // Check surrounding block var surroundingElseIfBlock = wrappedStatementAncestors.FirstOrDefault() as ElseIfBlockSyntax; if (surroundingElseIfBlock != null) { // Special handling for extension of Else If blocks if (StatementWithConditionContainsNullCheck(surroundingElseIfBlock, identifier)) { return; } statementToWrap = surroundingElseIfBlock; newWrappedStatement = ExtendIfConditionWithNullCheck(surroundingElseIfBlock, identifier); } else { var surroundingStatement = wrappedStatementAncestors.OfType <ExecutableStatementSyntax>().FirstOrDefault(); if (surroundingStatement != null) { if (StatementWithConditionContainsNullCheck(surroundingStatement, identifier)) { return; } if ((surroundingStatement is MultiLineIfBlockSyntax) || (surroundingStatement is SingleLineIfStatementSyntax)) { statementToWrap = surroundingStatement; newWrappedStatement = ExtendIfConditionWithNullCheck(surroundingStatement, identifier); } } else { if (StatementWithConditionContainsNullCheck(statementToWrap, identifier)) { return; } if ((statementToWrap is MultiLineIfBlockSyntax) || (statementToWrap is SingleLineIfStatementSyntax)) { newWrappedStatement = ExtendIfConditionWithNullCheck(statementToWrap, identifier); } } } if (newWrappedStatement == null) { if (wrapWithSingleLineIfStatement) { newWrappedStatement = SyntaxFactory.SingleLineIfStatement( SyntaxFactory.Token(SyntaxKind.IfKeyword), CreateIsNotNothingBinaryExpression(identifier), SyntaxFactory.Token(SyntaxKind.ThenKeyword), SyntaxFactory.List <StatementSyntax>(new[] { ((StatementSyntax)statementToWrap).WithoutLeadingTrivia().WithoutTrailingTrivia() }), null ).WithLeadingTrivia(statementToWrap.GetLeadingTrivia()).WithTrailingTrivia(statementToWrap.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation); } else { newWrappedStatement = SyntaxFactory.MultiLineIfBlock( SyntaxFactory.IfStatement( SyntaxFactory.Token(SyntaxKind.IfKeyword), CreateIsNotNothingBinaryExpression(identifier), SyntaxFactory.Token(SyntaxKind.ThenKeyword)), SyntaxFactory.List <StatementSyntax>(new[] { ((StatementSyntax)statementToWrap).WithoutLeadingTrivia().WithoutTrailingTrivia() }), SyntaxFactory.List <ElseIfBlockSyntax>(), null ).WithLeadingTrivia(statementToWrap.GetLeadingTrivia()).WithTrailingTrivia(statementToWrap.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation); } } context.RegisterRefactoring(CodeActionFactory.Create(token.Span, DiagnosticSeverity.Info, GettextCatalog.GetString("Add check for Nothing"), t2 => { var newRoot = root.ReplaceNode <SyntaxNode>(statementToWrap, newWrappedStatement); return(Task.FromResult(document.WithSyntaxRoot(newRoot))); })); }
private static SyntaxNode CreateRootWithUsingFromArgument(SyntaxNode root, ExpressionSyntax childOfArgumentNode, string identifierName) { var arg = childOfArgumentNode.Parent as ArgumentSyntax; var variableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.IdentifierName(@"var")) .WithVariables(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier(identifierName)) .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxKind.EqualsToken), childOfArgumentNode)))); var args = arg.Parent as ArgumentListSyntax; var newArgs = args.ReplaceNode(arg, arg.WithExpression(SyntaxFactory.IdentifierName(identifierName))); StatementSyntax statement = childOfArgumentNode.FirstAncestorOfType<ExpressionStatementSyntax>(); if (statement != null) { var exprStatement = statement.ReplaceNode(args, newArgs); var newUsingStatment = CreateUsingStatement(exprStatement, SyntaxFactory.Block(exprStatement)) .WithDeclaration(variableDeclaration); return root.ReplaceNode(statement, newUsingStatment); } statement = (StatementSyntax)childOfArgumentNode.Ancestors().First(node => node is StatementSyntax); var newStatement = statement.ReplaceNode(args, newArgs); var statementsForUsing = new[] { newStatement }.Concat(GetChildStatementsAfter(statement)); var usingBlock = SyntaxFactory.Block(statementsForUsing); var usingStatement = CreateUsingStatement(newStatement, usingBlock) .WithDeclaration(variableDeclaration); var statementsToReplace = new List<StatementSyntax> { statement }; statementsToReplace.AddRange(statementsForUsing.Skip(1)); return root.ReplaceNodes(statementsToReplace, (node, _) => node.Equals(statement) ? usingStatement : null); }
// To convert a null-check to pattern-matching, we should make sure of a few things: // // (1) The pattern variable may not be used before the point of declaration. // // { // var use = t; // if (x is T t) {} // } // // (2) The pattern variable may not be used outside of the new scope which // is determined by the parent statement. // // { // if (x is T t) {} // } // // var use = t; // // (3) The pattern variable may not be used before assignment in opposite // branches, if any. // // { // if (x is T t) {} // var use = t; // } // // We walk up the tree from the point of null-check and see if any of the above is violated. private bool CanSafelyConvertToPatternMatching() { // Keep track of whether the pattern variable is definitely assigned when false/true. // We start by the null-check itself, if it's compared with '==', the pattern variable // will be definitely assigned when false, because we wrap the is-operator in a !-operator. var defAssignedWhenTrue = _comparison.IsKind(SyntaxKind.NotEqualsExpression, SyntaxKind.IsExpression); foreach (var current in _comparison.Ancestors()) { // Checking for any conditional statement or expression that could possibly // affect or determine the state of definite-assignment of the pattern variable. switch (current.Kind()) { case SyntaxKind.LogicalAndExpression when !defAssignedWhenTrue: case SyntaxKind.LogicalOrExpression when defAssignedWhenTrue: // Since the pattern variable is only definitely assigned if the pattern // succeeded, in the following cases it would not be safe to use pattern-matching. // For example: // // if ((x = o as string) == null && SomeExpression) // if ((x = o as string) != null || SomeExpression) // // Here, x would never be definitely assigned if pattern-matching were used. return(false); case SyntaxKind.LogicalAndExpression: case SyntaxKind.LogicalOrExpression: // Parentheses and cast expressions do not contribute to the flow analysis. case SyntaxKind.ParenthesizedExpression: case SyntaxKind.CastExpression: // Skip over declaration parts to get to the parenting statement // which might be a for-statement or a local declaration statement. case SyntaxKind.EqualsValueClause: case SyntaxKind.VariableDeclarator: case SyntaxKind.VariableDeclaration: continue; case SyntaxKind.LogicalNotExpression: // The !-operator negates the definitive assignment state. defAssignedWhenTrue = !defAssignedWhenTrue; continue; case SyntaxKind.ConditionalExpression: var conditionalExpression = (ConditionalExpressionSyntax)current; if (LocalFlowsIn(defAssignedWhenTrue ? conditionalExpression.WhenFalse : conditionalExpression.WhenTrue)) { // In a conditional expression, the pattern variable // would not be definitely assigned in the opposite branch. return(false); } return(CheckExpression(conditionalExpression)); case SyntaxKind.ForStatement: var forStatement = (ForStatementSyntax)current; if (forStatement.Condition is null || !forStatement.Condition.Span.Contains(_comparison.Span)) { // In a for-statement, only the condition expression // can make this definitely assigned in the loop body. return(false); } return(CheckLoop(forStatement, forStatement.Statement, defAssignedWhenTrue)); case SyntaxKind.WhileStatement: var whileStatement = (WhileStatementSyntax)current; return(CheckLoop(whileStatement, whileStatement.Statement, defAssignedWhenTrue)); case SyntaxKind.IfStatement: var ifStatement = (IfStatementSyntax)current; var oppositeStatement = defAssignedWhenTrue ? ifStatement.Else?.Statement : ifStatement.Statement; if (oppositeStatement != null) { var dataFlow = _semanticModel.AnalyzeRequiredDataFlow(oppositeStatement); if (dataFlow.DataFlowsIn.Contains(_localSymbol)) { // Access before assignment is not safe in the opposite branch // as the variable is not definitely assigned at this point. // For example: // // if (o is string x) { } // else { Use(x); } // return(false); } if (dataFlow.AlwaysAssigned.Contains(_localSymbol)) { // If the variable is always assigned here, we don't need to check // subsequent statements as it's definitely assigned afterwards. // For example: // // if (o is string x) { } // else { x = null; } // return(true); } } if (!defAssignedWhenTrue && !_semanticModel.AnalyzeRequiredControlFlow(ifStatement.Statement).EndPointIsReachable) { // Access before assignment here is only valid if we have a negative // pattern-matching in an if-statement with an unreachable endpoint. // For example: // // if (!(o is string x)) { // return; // } // // // The 'return' statement above ensures x is definitely assigned here // Console.WriteLine(x); // return(true); } return(CheckStatement(ifStatement)); } switch (current) { case ExpressionSyntax expression: // If we reached here, it means we have a sub-expression that // does not guarantee definite assignment. We should make sure that // the pattern variable is not used outside of the expression boundaries. return(CheckExpression(expression)); case StatementSyntax statement: // If we reached here, it means that the null-check is appeared in // a statement. In that case, the variable would be actually in the // scope in subsequent statements, but not definitely assigned. // Therefore, we should ensure that there is no use before assignment. return(CheckStatement(statement)); } // Bail out for error cases and unhandled cases. break; } return(false); }
private static SyntaxNode GetNodeRootForAnalysis(ExpressionSyntax expression) { var parentNodeToSpeculate = expression .Ancestors(ascendOutOfTrivia: false) .FirstOrDefault(node => node.Kind() != SyntaxKind.Argument && node.Kind() != SyntaxKind.ArgumentList); return parentNodeToSpeculate ?? expression; }
public CodeRefactoring GetRefactoring(IDocument document, TextSpan textSpan, CancellationToken cancellationToken) { SyntaxNode root = (SyntaxNode)document.GetSyntaxRoot(cancellationToken); // Get nodes for highlighted code (only nodes of expression type can be extracted to method) IEnumerable <ExpressionSyntax> selectedNodes = root.DescendantNodes(textSpan).OfType <ExpressionSyntax>(); // Note: This does not work always. // Consider int a = 1 + 2 + 3; and extracting 2 + 3 // There is no such expression like 2 + 3, rather there is ((1 + 2) + 3) // Select the node that spans the selected text most ExpressionSyntax selectedNode = null; int bestSpan = -1; foreach (var node in selectedNodes) { if (textSpan.Contains(node.Span)) { int spanWidth = node.Span.End - node.Span.Start; if (spanWidth > bestSpan) { bestSpan = spanWidth; selectedNode = node; } } } // If no expression fits in selection, raise no refactoring if (selectedNode == null) { return(null); } // Verify is the expression within statement. IdentifierName within method's parameter list is also expression, but can not be extracted. if (selectedNode.FirstAncestorOrSelf <StatementSyntax>() == null) { return(null); } // (1) Only top most member access expression can be extracted if (selectedNode.Ancestors().OfType <MemberAccessExpressionSyntax>().Any(n => n.Name.DescendantNodesAndSelf().Contains(selectedNode))) { return(null); } // (2) Selected node must not be (note `Equals', not `Contains') an expression of method invocation if (selectedNode.Ancestors().OfType <InvocationExpressionSyntax>().Any(n => n.Expression.Equals(selectedNode))) { return(null); } // (3) Left hand side of assignment (=, +=, -=, etc.) cannot be extracted if (selectedNode.Ancestors().OfType <BinaryExpressionSyntax>().Any(n => IsComplexAssignment(n) && n.Left.Equals(selectedNode))) { return(null); } return(new CodeRefactoring( new[] { new ExtractMethodAction(document, selectedNode) } , selectedNode.Span)); }
private static bool SkipFieldsFromItsOwnConstructor(TypeDeclarationWithSymbol type, ExpressionSyntax assignmentExpression, ISymbol assignmentSymbol) { var parentConstructor = assignmentExpression.Ancestors().OfType<ConstructorDeclarationSyntax>().FirstOrDefault(); if (parentConstructor == null) return true; return assignmentSymbol.ContainingType != type.NamedTypeSymbol || assignmentSymbol.IsStatic != parentConstructor.Modifiers.Any(p => p.IsKind(SyntaxKind.StaticKeyword)); }
private static bool IsInsideIfDirective(ExpressionSyntax node) { return(node.Ancestors().Any(a => a is ElifDirectiveTriviaSyntax || a is IfDirectiveTriviaSyntax)); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var document = context.Document; if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles) return; var span = context.Span; if (!span.IsEmpty) return; var cancellationToken = context.CancellationToken; if (cancellationToken.IsCancellationRequested) return; var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model.IsFromGeneratedCode(cancellationToken)) return; var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(span.Start); ExpressionSyntax identifier = token.Parent as IdentifierNameSyntax; if (identifier == null) return; // If identifier is a type name, this might be a static member access or similar, don't suggest null checks on it var identifierSymbol = model.GetSymbolInfo(identifier).Symbol; if ((identifierSymbol == null) || (identifierSymbol.IsType())) return; // Identifier might be part of a MemberAccessExpression and we need to check it for null as a whole identifier = GetOuterMemberAccessExpression(identifier) ?? identifier; if ((identifier.Parent is ExpressionSyntax) && ConditionContainsNullCheck((ExpressionSyntax)identifier.Parent, identifier)) return; var identifierAncestors = identifier.Ancestors(); // Don't surround return statements with checks if (identifierAncestors.OfType<ReturnStatementSyntax>().Any()) return; // If identifier is in a conditional ternary expression, skip refactoring is case of present null check in its condition var conditionalExprParent = identifierAncestors.OfType<ConditionalExpressionSyntax>().FirstOrDefault(); if ((conditionalExprParent != null) && ConditionContainsNullCheck(conditionalExprParent.Condition, identifier)) return; // Check identifier type, don't suggest null checks for value types! var identifierType = model.GetTypeInfo(identifier).Type; if ((identifierType == null) || (identifierType.IsValueType && !identifierType.IsNullableType())) return; var statementToWrap = identifierAncestors.OfType<StatementSyntax>().FirstOrDefault(); if (statementToWrap == null) return; // No refactoring if statement is inside of a local variable declaration if ((statementToWrap is BlockSyntax) || (statementToWrap is LocalDeclarationStatementSyntax)) return; SyntaxNode newWrappedStatement = null; // Check surrounding block var surroundingStatement = statementToWrap.Ancestors().OfType<StatementSyntax>().FirstOrDefault(); if (surroundingStatement is BlockSyntax) surroundingStatement = surroundingStatement.Parent as StatementSyntax; if (surroundingStatement != null) { if (StatementWithConditionContainsNullCheck(surroundingStatement, identifier)) return; if (surroundingStatement is IfStatementSyntax surroundingIfStatement && surroundingIfStatement.Else == null) { statementToWrap = surroundingIfStatement; } } if (statementToWrap is IfStatementSyntax) { newWrappedStatement = ExtendIfConditionWithNullCheck((IfStatementSyntax)statementToWrap, identifier); } else { newWrappedStatement = SyntaxFactory.IfStatement( SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, identifier, SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), SyntaxFactory.Block(statementToWrap).WithLeadingTrivia(statementToWrap.GetLeadingTrivia()).WithTrailingTrivia(statementToWrap.GetTrailingTrivia()) ).WithAdditionalAnnotations(Formatter.Annotation); } context.RegisterRefactoring(CodeActionFactory.Create(token.Span, DiagnosticSeverity.Info, GettextCatalog.GetString("Add null check"), t2 => { var newRoot = root.ReplaceNode(statementToWrap, newWrappedStatement); return Task.FromResult(document.WithSyntaxRoot(newRoot)); })); }
public CodeRefactoring GetRefactoring(IDocument document, TextSpan textSpan, CancellationToken cancellationToken) { SyntaxNode root = (SyntaxNode)document.GetSyntaxRoot(cancellationToken); ISemanticModel model = document.GetSemanticModel(); // Get nodes for highlighted code (only nodes of expression type can be extracted) IEnumerable <ExpressionSyntax> selectedNodes = root.DescendantNodes(textSpan).OfType <ExpressionSyntax>(); // Select the node that spans the selected text most ExpressionSyntax selectedNode = null; int bestSpan = -1; foreach (var node in selectedNodes) { if (textSpan.Contains(node.Span)) { int spanWidth = node.Span.End - node.Span.Start; if (spanWidth > bestSpan) { bestSpan = spanWidth; selectedNode = node; } } } if (selectedNode == null) { return(null); } // Verify is the expression within statement. IdentifierName within method's parameter list is also expression, but can not be converted to variable. if (selectedNode.FirstAncestorOrSelf <StatementSyntax>() == null) { return(null); } // Special cases: // (1) a.b.c.d -> b.c is expression, but cannot be extracted. a.b.c can be extracted // (2) foo() -> foo is expression, but cannot be extracted (the name of method, not invocation!) // (3) a = b -> a is expression, but cannot be extracted // (4) foo() where foo() returns void -> void method cannot be extracted // (1) Only top most member access expression can be extracted // a.b.c -> a, a.b, and a.b.c can be extracted if (selectedNode.Ancestors().OfType <MemberAccessExpressionSyntax>().Any(n => n.Name.DescendantNodesAndSelf().Contains(selectedNode))) { return(null); } // (2) Selected node must not be (note `Equals', not `Contains') an expression of method invocation if (selectedNode.Ancestors().OfType <InvocationExpressionSyntax>().Any(n => n.Expression.Equals(selectedNode))) { return(null); } // (3) Left hand side of assignment (=, +=, -=, etc.) cannot be extracted if (selectedNode.Ancestors().OfType <BinaryExpressionSyntax>().Any(n => IsComplexAssignment(n) && n.Left.Equals(selectedNode))) { return(null); } // (4) void method cannot be extracted ITypeSymbol typeSymbol = model.GetTypeInfo(selectedNode, cancellationToken).Type; if (typeSymbol == null || typeSymbol.SpecialType == SpecialType.System_Void) { return(null); } return(new CodeRefactoring( new[] { new IntroduceLocalAction(document, selectedNode) } , selectedNode.Span)); }
private static bool IsInAsyncFunction(ExpressionSyntax expression) { foreach (var node in expression.Ancestors()) { switch (node.Kind()) { case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.AnonymousMethodExpression: return (node as AnonymousFunctionExpressionSyntax)?.AsyncKeyword.IsMissing == false; case SyntaxKind.MethodDeclaration: return (node as MethodDeclarationSyntax)?.Modifiers.Any(SyntaxKind.AsyncKeyword) == true; default: continue; } } return false; }
private bool IsSurroundedByInvocation(ExpressionSyntax expression) { return(expression.Ancestors().OfType <InvocationExpressionSyntax>().Any()); }
public CodeActionEdit GetEdit(CancellationToken cancellationToken) { SyntaxNode root = (SyntaxNode)this.document.GetSyntaxRoot(cancellationToken); ISemanticModel model = this.document.GetSemanticModel(cancellationToken); // New method's name const string methodName = "NewMethod"; // Get the resultant type of expression being extracted into method TypeSymbol typeSymbol = model.GetTypeInfo(this.expression, cancellationToken).Type as TypeSymbol; if (typeSymbol == null) { return(null); } // Get the container that the new method will be stored in // TypeDeclarationSyntax refers to classes, structs and interfaces TypeDeclarationSyntax containingType = this.expression.FirstAncestorOrSelf <TypeDeclarationSyntax>(); if (containingType == null) { return(null); } TypeSyntax returnType = Syntax.ParseTypeName(typeSymbol.ToMinimalDisplayString(containingType.GetLocation(), model)); // Create new method's body StatementSyntax methodStatement = null; if (typeSymbol.SpecialType == SpecialType.System_Void) { // If return type is void (foo();), no value is returned at all methodStatement = Syntax.ExpressionStatement(this.expression); } else { // Create return statement methodStatement = Syntax.ReturnStatement(this.expression); } BlockSyntax body = Syntax.Block(Syntax.List <StatementSyntax>(methodStatement)); // Create method declaration MethodDeclarationSyntax methodDeclaration = Syntax.MethodDeclaration(returnType, methodName).WithBody(body); SeparatedSyntaxList <ParameterSyntax> parametersList = Syntax.SeparatedList <ParameterSyntax>(); SeparatedSyntaxList <ArgumentSyntax> argumentsList = Syntax.SeparatedList <ArgumentSyntax>(); // Perform data flow analysis within expression var analysis = model.AnalyzeExpressionDataFlow(this.expression); // Analyze when to use `ref' and when to use `out' (`out' variable cannot be read before assignment in method, but variable cannot be passed as `ref' if is unassigned before expression in old place) // If variable is read in expression it is guaranteed that is has already value assigned. // Also, `out' variable can be written to before used as `out' argument. // // Conclusion: // Use `out' if variable is first written to (no matter if already has some value). // Otherwise, use `ref' (because variable is first read from and already has value). // // Consider: // void foo(out int i) { return (i=3) + i; }, can be used for both `int i;' and `int i = 1;', but not in `i + (i=3)' // void foo(ref int i) { return i + (i=3); }, can be used for `int i = 1;', but not in `int i;' and `(i=3) + i' // // Also note: // The first occurence of identifier can be used in `foo(out identifier)' context, it is not write to operation, but MUST remain as `out' or `ref' // Out/Ref parameters foreach (var variable in analysis.WrittenInside) { // Data flow analysis is applicable only for local variables (`this.a', or even `a' if it is field, both are flattened to `this' variable) // And `this' is of Parameter kind if (variable.Kind == CommonSymbolKind.Parameter) { // If this is `this', do not pass as argument, can be referenced directly from method scope if (((ParameterSymbol)variable).IsThis) { continue; } } // Find first identifier expression that refers to analyzed symbol ExpressionSyntax symbolExpression = this.expression.DescendantNodesAndSelf() .OfType <IdentifierNameSyntax>() .Where(n => variable.Equals(model.GetSymbolInfo(n, cancellationToken).Symbol)) .FirstOrDefault(); if (symbolExpression == null) { // Should not happen at all continue; } SyntaxToken refOrOut = Syntax.Token(SyntaxKind.RefKeyword); // The symbolExpression is first reference to variable (due to preorder search in DescendantNodes) // If it is assignment, then variable should be passed as `out' if (symbolExpression.Ancestors().OfType <BinaryExpressionSyntax>().Any(n => IsComplexAssignment(n) && n.Left.DescendantNodesAndSelf().Contains(symbolExpression))) { refOrOut = Syntax.Token(SyntaxKind.OutKeyword); } // If expression is used as argument (`ref sth', or `out sth'), the keyword must remain. ArgumentSyntax expressionAsArgument = symbolExpression.FirstAncestorOrSelf <ArgumentSyntax>(); if (expressionAsArgument != null) { refOrOut = expressionAsArgument.RefOrOutKeyword; } ArgumentSyntax argument = Syntax.Argument(symbolExpression) .WithRefOrOutKeyword(refOrOut); argumentsList = argumentsList.Add(argument); // Get type of new parameter TypeSymbol parameterType = null; switch (variable.Kind) { case CommonSymbolKind.Local: { LocalSymbol localSymbol = (LocalSymbol)variable; parameterType = localSymbol.Type; } break; case CommonSymbolKind.Parameter: { ParameterSymbol parameterSymbol = (ParameterSymbol)variable; parameterType = parameterSymbol.Type; } break; } if (parameterType == null) { // It can be Range type variable, used in Linq continue; } // Parse type name TypeSyntax parameterTypeSyntax = Syntax.ParseTypeName(parameterType.ToMinimalDisplayString(containingType.GetLocation(), model)); ParameterSyntax parameter = Syntax.Parameter(Syntax.Identifier(variable.Name)) .WithType(parameterTypeSyntax) .WithModifiers(Syntax.TokenList(refOrOut)); parametersList = parametersList.Add(parameter); } // In parameters foreach (var variable in analysis.ReadInside) { // Do not pass variable as `in', if it is already `out' variable if (analysis.WrittenInside.Contains(variable)) { continue; } // Data flow analysis is applicable only for local variables (`this.a', or even `a' if it is field, both are flattened to `this' variable) // And `this' is of Parameter kind if (variable.Kind == CommonSymbolKind.Parameter) { // If this is `this', do not pass as argument, can be referenced directly from method scope if (((ParameterSymbol)variable).IsThis) { continue; } } // Find first identifier expression that refers to analyzed symbol ExpressionSyntax symbolExpression = this.expression.DescendantNodesAndSelf() .OfType <IdentifierNameSyntax>() .Where(n => variable.Equals(model.GetSymbolInfo(n, cancellationToken).Symbol)) .FirstOrDefault(); if (symbolExpression == null) { // Should not happen at all continue; } // Create argument to be passed to method ArgumentSyntax argument = Syntax.Argument(symbolExpression); argumentsList = argumentsList.Add(argument); // Get type of new parameter TypeSymbol parameterType = null; switch (variable.Kind) { case CommonSymbolKind.Local: { LocalSymbol localSymbol = (LocalSymbol)variable; parameterType = localSymbol.Type; } break; case CommonSymbolKind.Parameter: { ParameterSymbol parameterSymbol = (ParameterSymbol)variable; parameterType = parameterSymbol.Type; } break; } if (parameterType == null) { // It can be Range type variable, used in Linq continue; } // Parse type name TypeSyntax parameterTypeSyntax = Syntax.ParseTypeName(parameterType.ToMinimalDisplayString(containingType.GetLocation(), model)); ParameterSyntax parameter = Syntax.Parameter(Syntax.Identifier(variable.Name)) .WithType(parameterTypeSyntax); parametersList = parametersList.Add(parameter); } // Add parameter list to method declaration methodDeclaration = methodDeclaration.WithParameterList(Syntax.ParameterList(parametersList)) .WithLeadingTrivia(Syntax.ElasticCarriageReturnLineFeed) .WithAdditionalAnnotations(CodeAnnotations.Formatting); // If the expression is within static method, the extracted method should be static as well MemberDeclarationSyntax memberDeclaration = this.expression.FirstAncestorOrSelf <MemberDeclarationSyntax>(); if (memberDeclaration == null) { return(null); } ISymbol memberSymbol = model.GetDeclaredSymbol(memberDeclaration); if (memberSymbol.IsStatic) { methodDeclaration = methodDeclaration.WithModifiers(methodDeclaration.Modifiers.Add(Syntax.Token(SyntaxKind.StaticKeyword))); } // Format arguments' list ArgumentListSyntax argumentListSyntax = Syntax.ArgumentList(argumentsList) .WithAdditionalAnnotations(CodeAnnotations.Formatting); // Replace selected expression with new method's invocation InvocationExpressionSyntax methodInvocation = Syntax.InvocationExpression(Syntax.IdentifierName(methodName)) .WithArgumentList(argumentListSyntax) .WithLeadingTrivia(this.expression.GetLeadingTrivia()) .WithTrailingTrivia(this.expression.GetTrailingTrivia()); // If containing method is a template, type parameters should be forwarded to new method as well, // together with type constraints. // Note: I am interested only in type parameters from MethodDeclaration. Other method-like syntax (lambdas, anonymous methods, indexers, operators) don't specify type parameters. // Additionally, type parameters of containing type are visible to new method, becuase it is also a member of this class MethodDeclarationSyntax containingMethodDeclaration = this.expression.FirstAncestorOrSelf <MethodDeclarationSyntax>(); if (containingMethodDeclaration != null && containingMethodDeclaration.TypeParameterList != null) { methodDeclaration = methodDeclaration.WithTypeParameterList(containingMethodDeclaration.TypeParameterList) .WithConstraintClauses(containingMethodDeclaration.ConstraintClauses); SeparatedSyntaxList <TypeSyntax> typeArguments = Syntax.SeparatedList <TypeSyntax>(); foreach (TypeParameterSyntax templateArg in containingMethodDeclaration.TypeParameterList.Parameters) { typeArguments = typeArguments.Add(Syntax.ParseTypeName(templateArg.Identifier.ValueText)); } TypeArgumentListSyntax typeArgumentList = Syntax.TypeArgumentList(typeArguments); GenericNameSyntax genericName = Syntax.GenericName(Syntax.Identifier(methodName), typeArgumentList) .WithAdditionalAnnotations(CodeAnnotations.Formatting); methodInvocation = methodInvocation.WithExpression(genericName); } TypeDeclarationSyntax newContainingType = containingType.ReplaceNode(this.expression, methodInvocation); // Insert method declaration to containing type if (containingType.Kind == SyntaxKind.ClassDeclaration) { ClassDeclarationSyntax classDeclaration = (ClassDeclarationSyntax)newContainingType; newContainingType = classDeclaration.AddMembers(methodDeclaration); } else if (containingType.Kind == SyntaxKind.StructDeclaration) { StructDeclarationSyntax structDeclaration = (StructDeclarationSyntax)newContainingType; newContainingType = structDeclaration.AddMembers(methodDeclaration); } else { return(null); } SyntaxNode newRoot = root.ReplaceNode(containingType, newContainingType); return(new CodeActionEdit(document.UpdateSyntaxRoot(newRoot))); }
private static bool IsSurroundedByInvocation(ExpressionSyntax expression) => expression.Ancestors().OfType <InvocationExpressionSyntax>().Any();