-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
AbstractUseCollectionInitializerCodeFixProvider.cs
134 lines (115 loc) · 6.59 KB
/
AbstractUseCollectionInitializerCodeFixProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.UseCollectionInitializer
{
internal abstract class AbstractUseCollectionInitializerCodeFixProvider<
TSyntaxKind,
TExpressionSyntax,
TStatementSyntax,
TObjectCreationExpressionSyntax,
TMemberAccessExpressionSyntax,
TInvocationExpressionSyntax,
TExpressionStatementSyntax,
TVariableDeclaratorSyntax>
: SyntaxEditorBasedCodeFixProvider
where TSyntaxKind : struct
where TExpressionSyntax : SyntaxNode
where TStatementSyntax : SyntaxNode
where TObjectCreationExpressionSyntax : TExpressionSyntax
where TMemberAccessExpressionSyntax : TExpressionSyntax
where TInvocationExpressionSyntax : TExpressionSyntax
where TExpressionStatementSyntax : TStatementSyntax
where TVariableDeclaratorSyntax : SyntaxNode
{
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.UseCollectionInitializerDiagnosticId);
internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle;
protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic)
=> !diagnostic.Descriptor.ImmutableCustomTags().Contains(WellKnownDiagnosticTags.Unnecessary);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.RegisterCodeFix(
new MyCodeAction(c => FixAsync(context.Document, context.Diagnostics.First(), c)),
context.Diagnostics);
return Task.CompletedTask;
}
protected override async Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
// Fix-All for this feature is somewhat complicated. As Collection-Initializers
// could be arbitrarily nested, we have to make sure that any edits we make
// to one Collection-Initializer are seen by any higher ones. In order to do this
// we actually process each object-creation-node, one at a time, rewriting
// the tree for each node. In order to do this effectively, we use the '.TrackNodes'
// feature to keep track of all the object creation nodes as we make edits to
// the tree. If we didn't do this, then we wouldn't be able to find the
// second object-creation-node after we make the edit for the first one.
var workspace = document.Project.Solution.Workspace;
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var originalRoot = editor.OriginalRoot;
var originalObjectCreationNodes = new Stack<TObjectCreationExpressionSyntax>();
foreach (var diagnostic in diagnostics)
{
var objectCreation = (TObjectCreationExpressionSyntax)originalRoot.FindNode(
diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
originalObjectCreationNodes.Push(objectCreation);
}
// We're going to be continually editing this tree. Track all the nodes we
// care about so we can find them across each edit.
document = document.WithSyntaxRoot(originalRoot.TrackNodes(originalObjectCreationNodes));
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var currentRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
while (originalObjectCreationNodes.Count > 0)
{
var originalObjectCreation = originalObjectCreationNodes.Pop();
var objectCreation = currentRoot.GetCurrentNodes(originalObjectCreation).Single();
var matches = ObjectCreationExpressionAnalyzer<TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TVariableDeclaratorSyntax>.Analyze(
semanticModel, syntaxFacts, objectCreation, cancellationToken);
if (matches == null || matches.Value.Length == 0)
{
continue;
}
var statement = objectCreation.FirstAncestorOrSelf<TStatementSyntax>();
Contract.ThrowIfNull(statement);
var newStatement = GetNewStatement(statement, objectCreation, matches.Value)
.WithAdditionalAnnotations(Formatter.Annotation);
var subEditor = new SyntaxEditor(currentRoot, workspace);
subEditor.ReplaceNode(statement, newStatement);
foreach (var match in matches)
{
subEditor.RemoveNode(match, SyntaxRemoveOptions.KeepUnbalancedDirectives);
}
document = document.WithSyntaxRoot(subEditor.GetChangedRoot());
semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
currentRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
}
editor.ReplaceNode(originalRoot, currentRoot);
}
protected abstract TStatementSyntax GetNewStatement(
TStatementSyntax statement, TObjectCreationExpressionSyntax objectCreation,
ImmutableArray<TExpressionStatementSyntax> matches);
private class MyCodeAction : CustomCodeActions.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(AnalyzersResources.Collection_initialization_can_be_simplified, createChangedDocument, nameof(AnalyzersResources.Collection_initialization_can_be_simplified))
{
}
}
}
}