Skip to content

Commit e5a21b2

Browse files
authored
Merge pull request #43077 from sharwell/batch-nested-fixes
Update BatchFixAllProvider to support nested code actions
2 parents e7b413e + 7ade389 commit e5a21b2

File tree

5 files changed

+198
-10
lines changed

5 files changed

+198
-10
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<!-- Versions used by several individual references below -->
3030
<RoslynDiagnosticsNugetPackageVersion>3.0.0-beta2.20169.3</RoslynDiagnosticsNugetPackageVersion>
3131
<CodeStyleLayerCodeAnalysisVersion>3.3.1</CodeStyleLayerCodeAnalysisVersion>
32-
<MicrosoftCodeAnalysisTestingVersion>1.0.1-beta1.20126.2</MicrosoftCodeAnalysisTestingVersion>
32+
<MicrosoftCodeAnalysisTestingVersion>1.0.1-beta1.20206.1</MicrosoftCodeAnalysisTestingVersion>
3333
<CodeStyleAnalyzerVersion>3.6.0-2.20157.5</CodeStyleAnalyzerVersion>
3434
<VisualStudioEditorPackagesVersion>16.4.248</VisualStudioEditorPackagesVersion>
3535
<ILToolsPackageVersion>5.0.0-alpha1.19409.1</ILToolsPackageVersion>

src/EditorFeatures/TestUtilities/Roslyn.Services.Test.Utilities.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@
5757
<PackageReference Include="Microsoft.VisualStudio.Utilities" Version="$(MicrosoftVisualStudioUtilitiesVersion)" />
5858
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="$(MicrosoftVisualStudioValidationVersion)" />
5959
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="$(SystemThreadingTasksDataflowVersion)" />
60-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpCodeFixTestingXUnitVersion)" />
61-
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit" Version="$(MicrosoftCodeAnalysisVisualBasicCodeFixTestingXUnitVersion)" />
6260
</ItemGroup>
6361
<ItemGroup>
6462
<Compile Include="..\..\VisualStudio\Core\Def\Implementation\Remote\JsonRpcConnection.cs">

src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,27 @@ private async Task<CodeAction> GetFixAsync(
171171
private static Action<CodeAction, ImmutableArray<Diagnostic>> GetRegisterCodeFixAction(
172172
FixAllState fixAllState,
173173
ConcurrentBag<(Diagnostic diagnostic, CodeAction action)> result)
174-
=> (action, diagnostics) =>
175-
{
176-
if (action != null && action.EquivalenceKey == fixAllState.CodeActionEquivalenceKey)
177-
{
178-
result.Add((diagnostics.First(), action));
179-
}
180-
};
174+
{
175+
return (action, diagnostics) =>
176+
{
177+
using var _ = ArrayBuilder<CodeAction>.GetInstance(out var builder);
178+
builder.Push(action);
179+
while (builder.Count > 0)
180+
{
181+
var currentAction = builder.Pop();
182+
if (currentAction is { EquivalenceKey: var equivalenceKey }
183+
&& equivalenceKey == fixAllState.CodeActionEquivalenceKey)
184+
{
185+
result.Add((diagnostics.First(), currentAction));
186+
}
181187

188+
foreach (var nestedAction in currentAction.NestedCodeActions)
189+
{
190+
builder.Push(nestedAction);
191+
}
192+
}
193+
};
194+
}
182195

183196
protected virtual Task AddProjectFixesAsync(
184197
Project project, ImmutableArray<Diagnostic> diagnostics,
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Microsoft.CodeAnalysis.CodeActions;
13+
using Microsoft.CodeAnalysis.CodeFixes;
14+
using Microsoft.CodeAnalysis.CSharp;
15+
using Microsoft.CodeAnalysis.CSharp.Syntax;
16+
using Microsoft.CodeAnalysis.Diagnostics;
17+
using Microsoft.CodeAnalysis.Testing;
18+
using Microsoft.CodeAnalysis.Text;
19+
using Xunit;
20+
21+
namespace Microsoft.CodeAnalysis.UnitTests
22+
{
23+
public class BatchFixAllProviderTests
24+
{
25+
[Fact]
26+
public async Task TestDefaultSelectionNestedFixers()
27+
{
28+
var testCode = @"
29+
class TestClass {
30+
int field = [|0|];
31+
}
32+
";
33+
var fixedCode = $@"
34+
class TestClass {{
35+
int field = 1;
36+
}}
37+
";
38+
39+
// Three CodeFixProviders provide three actions
40+
var codeFixes = ImmutableArray.Create(
41+
ImmutableArray.Create(1),
42+
ImmutableArray.Create(2),
43+
ImmutableArray.Create(3));
44+
await new CSharpTest(codeFixes, nested: true)
45+
{
46+
TestCode = testCode,
47+
FixedCode = fixedCode,
48+
}.RunAsync();
49+
}
50+
51+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
52+
private class LiteralZeroAnalyzer : DiagnosticAnalyzer
53+
{
54+
internal static readonly DiagnosticDescriptor Descriptor =
55+
new DiagnosticDescriptor("LiteralZero", "title", "message", "category", DiagnosticSeverity.Warning, isEnabledByDefault: true);
56+
57+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor);
58+
59+
public override void Initialize(AnalysisContext context)
60+
{
61+
context.EnableConcurrentExecution();
62+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
63+
64+
context.RegisterSyntaxNodeAction(HandleNumericLiteralExpression, SyntaxKind.NumericLiteralExpression);
65+
}
66+
67+
private void HandleNumericLiteralExpression(SyntaxNodeAnalysisContext context)
68+
{
69+
var node = (LiteralExpressionSyntax)context.Node;
70+
if (node.Token.ValueText == "0")
71+
{
72+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, node.Token.GetLocation()));
73+
}
74+
}
75+
}
76+
77+
private class ReplaceZeroFix : CodeFixProvider
78+
{
79+
private readonly ImmutableArray<int> _replacements;
80+
private readonly bool _nested;
81+
82+
public ReplaceZeroFix(ImmutableArray<int> replacements, bool nested)
83+
{
84+
Debug.Assert(replacements.All(replacement => replacement >= 0), $"Assertion failed: {nameof(replacements)}.All(replacement => replacement >= 0)");
85+
_replacements = replacements;
86+
_nested = nested;
87+
}
88+
89+
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(LiteralZeroAnalyzer.Descriptor.Id);
90+
91+
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
92+
93+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
94+
{
95+
foreach (var diagnostic in context.Diagnostics)
96+
{
97+
var fixes = new List<CodeAction>();
98+
foreach (var replacement in _replacements)
99+
{
100+
fixes.Add(CodeAction.Create(
101+
"ThisToBase",
102+
cancellationToken => CreateChangedDocument(context.Document, diagnostic.Location.SourceSpan, replacement, cancellationToken),
103+
$"{nameof(ReplaceZeroFix)}_{replacement}"));
104+
}
105+
106+
if (_nested)
107+
{
108+
#if NETCOREAPP2_0 || NET472
109+
fixes = new List<CodeAction> { CodeAction.Create("Container", fixes.ToImmutableArray(), isInlinable: false) };
110+
#else
111+
throw new NotSupportedException("Nested code actions are not supported on this framework.");
112+
#endif
113+
}
114+
115+
foreach (var fix in fixes)
116+
{
117+
context.RegisterCodeFix(fix, diagnostic);
118+
}
119+
}
120+
121+
return Task.CompletedTask;
122+
}
123+
124+
private async Task<Document> CreateChangedDocument(Document document, TextSpan sourceSpan, int replacement, CancellationToken cancellationToken)
125+
{
126+
var tree = await document.GetSyntaxTreeAsync(cancellationToken);
127+
var root = await tree.GetRootAsync(cancellationToken);
128+
var token = root.FindToken(sourceSpan.Start);
129+
var newToken = SyntaxFactory.Literal(token.LeadingTrivia, replacement.ToString(), replacement, token.TrailingTrivia);
130+
return document.WithSyntaxRoot(root.ReplaceToken(token, newToken));
131+
}
132+
}
133+
134+
private class CSharpTest : CodeFixTest<DefaultVerifier>
135+
{
136+
private readonly ImmutableArray<ImmutableArray<int>> _replacementGroups;
137+
private readonly bool _nested;
138+
139+
public CSharpTest(ImmutableArray<ImmutableArray<int>> replacementGroups, bool nested = false)
140+
{
141+
_replacementGroups = replacementGroups;
142+
_nested = nested;
143+
}
144+
145+
public override string Language => LanguageNames.CSharp;
146+
147+
public override Type SyntaxKindType => typeof(SyntaxKind);
148+
149+
protected override string DefaultFileExt => "cs";
150+
151+
protected override CompilationOptions CreateCompilationOptions()
152+
{
153+
return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
154+
}
155+
156+
protected override ParseOptions CreateParseOptions()
157+
{
158+
return new CSharpParseOptions(LanguageVersion.Default, DocumentationMode.Diagnose);
159+
}
160+
161+
protected override IEnumerable<CodeFixProvider> GetCodeFixProviders()
162+
{
163+
foreach (var replacementGroup in _replacementGroups)
164+
{
165+
yield return new ReplaceZeroFix(replacementGroup, _nested);
166+
}
167+
}
168+
169+
protected override IEnumerable<DiagnosticAnalyzer> GetDiagnosticAnalyzers()
170+
{
171+
yield return new LiteralZeroAnalyzer();
172+
}
173+
}
174+
}
175+
}

src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
</ItemGroup>
2424
<ItemGroup>
2525
<PackageReference Include="Microsoft.CSharp" Version="$(MicrosoftCSharpVersion)" />
26+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpCodeFixTestingXUnitVersion)" />
27+
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit" Version="$(MicrosoftCodeAnalysisVisualBasicCodeFixTestingXUnitVersion)" />
2628
<PackageReference Include="Microsoft.VisualStudio.Composition" Version="$(MicrosoftVisualStudioCompositionVersion)" />
2729
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="$(MicrosoftVisualStudioValidationVersion)" />
2830
<PackageReference Include="System.Buffers" Version="$(SystemBuffersVersion)" />

0 commit comments

Comments
 (0)