private async Task<Document> FixAllAsync( Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var editor = new SyntaxEditor(root, document.Project.Solution.Workspace); var generator = editor.Generator; foreach (var diagnostic in diagnostics) { var ifStatement = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan); var throwStatementExpression = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan); var assignmentValue = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan); // First, remote the if-statement entirely. editor.RemoveNode(ifStatement); // Now, update the assignemnt value to go from 'a' to 'a ?? throw ...'. editor.ReplaceNode(assignmentValue, generator.CoalesceExpression(assignmentValue, generator.ThrowExpression(throwStatementExpression))); } var newRoot = editor.GetChangedRoot(); return document.WithSyntaxRoot(newRoot); }
private void AddEdits( SyntaxEditor editor, Diagnostic diagnostic, CancellationToken cancellationToken) { var localDeclarationLocation = diagnostic.AdditionalLocations[0]; var ifStatementLocation = diagnostic.AdditionalLocations[1]; var conditionLocation = diagnostic.AdditionalLocations[2]; var asExpressionLocation = diagnostic.AdditionalLocations[3]; var localDeclaration = (LocalDeclarationStatementSyntax)localDeclarationLocation.FindNode(cancellationToken); var ifStatement = (IfStatementSyntax)ifStatementLocation.FindNode(cancellationToken); var condition = (BinaryExpressionSyntax)conditionLocation.FindNode(cancellationToken); var asExpression = (BinaryExpressionSyntax)asExpressionLocation.FindNode(cancellationToken); var updatedCondition = SyntaxFactory.IsPatternExpression( asExpression.Left, SyntaxFactory.DeclarationPattern( ((TypeSyntax)asExpression.Right).WithoutTrivia(), SyntaxFactory.SingleVariableDesignation( localDeclaration.Declaration.Variables[0].Identifier.WithoutTrivia()))); var trivia = localDeclaration.GetLeadingTrivia().Concat(localDeclaration.GetTrailingTrivia()) .Where(t => t.IsSingleOrMultiLineComment()) .SelectMany(t => ImmutableArray.Create(t, SyntaxFactory.ElasticCarriageReturnLineFeed)) .ToImmutableArray(); var updatedIfStatement = ifStatement.ReplaceNode(condition, updatedCondition) .WithPrependedLeadingTrivia(trivia) .WithAdditionalAnnotations(Formatter.Annotation); editor.RemoveNode(localDeclaration); editor.ReplaceNode(ifStatement, updatedIfStatement); }
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var baseFieldDeclaration = (BaseFieldDeclarationSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan); List<BaseFieldDeclarationSyntax> newFieldDeclarations = SplitDeclaration(baseFieldDeclaration); if (newFieldDeclarations != null) { var editor = new SyntaxEditor(syntaxRoot, document.Project.Solution.Workspace); editor.InsertAfter(baseFieldDeclaration, newFieldDeclarations); editor.RemoveNode(baseFieldDeclaration); return document.WithSyntaxRoot(editor.GetChangedRoot().WithoutFormatting()); } return document; }
private void AddEdits( SyntaxEditor editor, Diagnostic diagnostic, CancellationToken cancellationToken) { var ifStatementLocation = diagnostic.AdditionalLocations[0]; var localDeclarationLocation = diagnostic.AdditionalLocations[1]; var ifStatement = (IfStatementSyntax)ifStatementLocation.FindNode(cancellationToken); var localDeclaration = (LocalDeclarationStatementSyntax)localDeclarationLocation.FindNode(cancellationToken); var isExpression = (BinaryExpressionSyntax)ifStatement.Condition; var updatedCondition = SyntaxFactory.IsPatternExpression( isExpression.Left, SyntaxFactory.DeclarationPattern( ((TypeSyntax)isExpression.Right).WithoutTrivia(), SyntaxFactory.SingleVariableDesignation( localDeclaration.Declaration.Variables[0].Identifier.WithoutTrivia()))); var trivia = localDeclaration.GetLeadingTrivia().Concat(localDeclaration.GetTrailingTrivia()) .Where(t => t.IsSingleOrMultiLineComment()) .SelectMany(t => ImmutableArray.Create(t, SyntaxFactory.ElasticCarriageReturnLineFeed)) .ToImmutableArray(); var updatedIfStatement = ifStatement.ReplaceNode(ifStatement.Condition, updatedCondition) .WithPrependedLeadingTrivia(trivia) .WithAdditionalAnnotations(Formatter.Annotation); editor.RemoveNode(localDeclaration); editor.ReplaceNode(ifStatement, (i, g) => { // Because the local declaration is *inside* the 'if', we need to get the 'if' // statement after it was already modified and *then* update the condition // portion of it. var currentIf = (IfStatementSyntax)i; return currentIf.ReplaceNode(currentIf.Condition, updatedCondition) .WithPrependedLeadingTrivia(trivia) .WithAdditionalAnnotations(Formatter.Annotation); }); }
protected override Task FixAllAsync( Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) { var generator = editor.Generator; var root = editor.OriginalRoot; foreach (var diagnostic in diagnostics) { var ifStatement = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan); var throwStatementExpression = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan); var assignmentValue = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan); // First, remote the if-statement entirely. editor.RemoveNode(ifStatement); // Now, update the assignemnt value to go from 'a' to 'a ?? throw ...'. editor.ReplaceNode(assignmentValue, generator.CoalesceExpression(assignmentValue, generator.ThrowExpression(throwStatementExpression))); } return SpecializedTasks.EmptyTask; }
private async Task AddEditsAsync( Document document, SyntaxEditor editor, Diagnostic diagnostic, bool useVarWhenDeclaringLocals, bool useImplicitTypeForIntrinsicTypes, CancellationToken cancellationToken) { var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); // Recover the nodes we care about. var declaratorLocation = diagnostic.AdditionalLocations[0]; var identifierLocation = diagnostic.AdditionalLocations[1]; var invocationOrCreationLocation = diagnostic.AdditionalLocations[2]; var outArgumentContainingStatementLocation = diagnostic.AdditionalLocations[3]; var root = declaratorLocation.SourceTree.GetRoot(cancellationToken); var declarator = (VariableDeclaratorSyntax)declaratorLocation.FindNode(cancellationToken); var identifier = (IdentifierNameSyntax)identifierLocation.FindNode(cancellationToken); var invocationOrCreation = (ExpressionSyntax)invocationOrCreationLocation.FindNode( getInnermostNodeForTie: true, cancellationToken: cancellationToken); var outArgumentContainingStatement = (StatementSyntax)outArgumentContainingStatementLocation.FindNode(cancellationToken); var declaration = (VariableDeclarationSyntax)declarator.Parent; var singleDeclarator = declaration.Variables.Count == 1; if (singleDeclarator) { // This was a local statement with a single variable in it. Just Remove // the entire local declaration statement. Note that comments belonging to // this local statement will be moved to be above the statement containing // the out-var. editor.RemoveNode(declaration.Parent); } else { // Otherwise, just remove the single declarator. Note: we'll move the comments // 'on' the declarator to the out-var location. This is a little bit trickier // than normal due to how our comment-association rules work. i.e. if you have: // // var /*c1*/ i /*c2*/, /*c3*/ j /*c4*/; // // In this case 'c1' is owned by the 'var' token, not 'i', and 'c3' is owned by // the comment token not 'j'. editor.RemoveNode(declarator); if (declarator == declaration.Variables[0]) { // If we're removing the first declarator, and it's on the same line // as the previous token, then we want to remove all the trivia belonging // to the previous token. We're going to move it along with this declarator. // If we don't, then the comment will stay with the previous token. // // Note that hte moving of the comment happens later on when we make the // declaration expression. if (sourceText.AreOnSameLine(declarator.GetFirstToken(), declarator.GetFirstToken().GetPreviousToken(includeSkipped: true))) { editor.ReplaceNode( declaration.Type, (t, g) => t.WithTrailingTrivia(SyntaxFactory.ElasticSpace).WithoutAnnotations(Formatter.Annotation)); } } } // get the type that we want to put in the out-var-decl based on the user's options. // i.e. prefer 'out var' if that is what the user wants. var newType = this.GetDeclarationType(declaration.Type, useVarWhenDeclaringLocals, useImplicitTypeForIntrinsicTypes); var declarationExpression = GetDeclarationExpression( sourceText, identifier, newType, singleDeclarator ? null : declarator); // Check if using out-var changed problem semantics. var semanticsChanged = await SemanticsChangedAsync( document, declaration, invocationOrCreation, newType, identifier, declarationExpression, cancellationToken).ConfigureAwait(false); if (semanticsChanged) { // Switching to 'var' changed semantics. Just use the original type of the local. var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var local = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator); // If the user originally wrote it something other than 'var', then use what they // wrote. Otherwise, synthesize the actual type of the local. var explicitType = declaration.Type.IsVar ? local.Type?.GenerateTypeSyntax() : declaration.Type; declarationExpression = GetDeclarationExpression( sourceText, identifier, explicitType, singleDeclarator ? null : declarator); } editor.ReplaceNode(identifier, declarationExpression); if (declaration.Variables.Count == 1) { // If we're removing the declaration entirely, move the leading/trailing comments it // had to sit above the statement containing the out-var declaration. var comments = declaration.Parent.GetLeadingTrivia().Concat(declaration.Parent.GetTrailingTrivia()) .Where(t => t.IsSingleOrMultiLineComment()) .SelectMany(t => ImmutableArray.Create(t, SyntaxFactory.ElasticCarriageReturnLineFeed)) .ToImmutableArray(); if (comments.Length > 0) { editor.ReplaceNode( outArgumentContainingStatement, (s, g) => s.WithPrependedLeadingTrivia(comments).WithAdditionalAnnotations(Formatter.Annotation)); } } }
private static Document HandVariableAndIfStatementFormAsync( Document document, SyntaxNode root, Diagnostic diagnostic, CancellationToken cancellationToken) { var localDeclarationLocation = diagnostic.AdditionalLocations[0]; var ifStatementLocation = diagnostic.AdditionalLocations[1]; var expressionStatementLocation = diagnostic.AdditionalLocations[2]; var localDeclarationStatement = (LocalDeclarationStatementSyntax)root.FindNode(localDeclarationLocation.SourceSpan); cancellationToken.ThrowIfCancellationRequested(); var ifStatement = (IfStatementSyntax)root.FindNode(ifStatementLocation.SourceSpan); cancellationToken.ThrowIfCancellationRequested(); var expressionStatement = (ExpressionStatementSyntax)root.FindNode(expressionStatementLocation.SourceSpan); cancellationToken.ThrowIfCancellationRequested(); var invocationExpression = (InvocationExpressionSyntax)expressionStatement.Expression; var parentBlock = (BlockSyntax)localDeclarationStatement.Parent; var newStatement = expressionStatement.WithExpression( SyntaxFactory.ConditionalAccessExpression( localDeclarationStatement.Declaration.Variables[0].Initializer.Value.Parenthesize(), SyntaxFactory.InvocationExpression( SyntaxFactory.MemberBindingExpression(SyntaxFactory.IdentifierName(nameof(Action.Invoke))), invocationExpression.ArgumentList))); newStatement = newStatement.WithAdditionalAnnotations(Formatter.Annotation); var editor = new SyntaxEditor(root, document.Project.Solution.Workspace); editor.ReplaceNode(ifStatement, newStatement); editor.RemoveNode(localDeclarationStatement, SyntaxRemoveOptions.KeepLeadingTrivia | SyntaxRemoveOptions.AddElasticMarker); cancellationToken.ThrowIfCancellationRequested(); var newRoot = editor.GetChangedRoot(); return document.WithSyntaxRoot(newRoot); }