-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Force document reparsing after applying code fixes
- Loading branch information
Showing
2 changed files
with
12 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,107 +1,18 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Microsoft.CodeAnalysis.Text; | ||
|
||
namespace AutoCodeFix | ||
{ | ||
public static class DocumentExtensions | ||
{ | ||
/// <summary> | ||
/// Applies the given named code fix to a document. | ||
/// </summary> | ||
public static async Task<Document> ApplyCodeFixAsync(this Document document, string codeFixName, ImmutableArray<DiagnosticAnalyzer> analyzers = default, CancellationToken cancellationToken = default) | ||
public static async Task<Document> RecreateDocumentAsync(this Document document, CancellationToken cancellationToken) | ||
{ | ||
// If we request and process ALL codefixes at once, we'll get one for each | ||
// diagnostics, which is one per non-implemented member of the interface/abstract | ||
// base class, so we'd be applying unnecessary fixes after the first one. | ||
// So we re-retrieve them after each Apply, which will leave only the remaining | ||
// ones. | ||
var codeFixes = await GetCodeFixes(document, codeFixName, analyzers, cancellationToken).ConfigureAwait(false); | ||
while (codeFixes.Length != 0) | ||
{ | ||
var operations = await codeFixes[0].Action.GetOperationsAsync(cancellationToken); | ||
ApplyChangesOperation operation; | ||
if ((operation = operations.OfType<ApplyChangesOperation>().FirstOrDefault()) != null) | ||
{ | ||
document = operation.ChangedSolution.GetDocument(document.Id); | ||
// Retrieve the codefixes for the updated doc again. | ||
codeFixes = await GetCodeFixes(document, codeFixName, analyzers, cancellationToken).ConfigureAwait(false); | ||
} | ||
else | ||
{ | ||
// If we got no applicable code fixes, exit the loop and move on to the next codefix. | ||
break; | ||
} | ||
} | ||
|
||
return document; | ||
} | ||
|
||
static async Task<ImmutableArray<CodeFix>> GetCodeFixes( | ||
Document document, string codeFixName, | ||
ImmutableArray<DiagnosticAnalyzer> analyzers = default, CancellationToken cancellationToken = default) | ||
{ | ||
var provider = GetCodeFixProvider(document, codeFixName); | ||
if (provider == null) | ||
return ImmutableArray<CodeFix>.Empty; | ||
|
||
var compilation = await document.Project.GetCompilationAsync(cancellationToken); | ||
var analyerCompilation = compilation.WithAnalyzers(analyzers, cancellationToken: cancellationToken); | ||
var allDiagnostics = await analyerCompilation.GetAllDiagnosticsAsync(cancellationToken); | ||
var diagnostics = allDiagnostics | ||
.Where(x => provider.FixableDiagnosticIds.Contains(x.Id)) | ||
// Only consider the diagnostics raised by the target document. | ||
.Where(d => | ||
d.Location.Kind == LocationKind.SourceFile && | ||
d.Location.GetLineSpan().Path == document.FilePath); | ||
|
||
var codeFixes = new List<CodeFix>(); | ||
foreach (var diagnostic in diagnostics) | ||
{ | ||
await provider.RegisterCodeFixesAsync( | ||
new CodeFixContext(document, diagnostic, | ||
(action, diag) => codeFixes.Add(new CodeFix(action, diag, codeFixName)), | ||
cancellationToken)); | ||
} | ||
|
||
return codeFixes.ToImmutableArray(); | ||
} | ||
|
||
static CodeFixProvider GetCodeFixProvider(Document document, string codeFixName) | ||
=> document.Project.Solution.Workspace.Services.HostServices.GetExports<CodeFixProvider, IDictionary<string, object>>() | ||
.Where(x => ((string[])x.Metadata["Languages"]).Contains(document.Project.Language) && | ||
((string)x.Metadata["Name"]) == codeFixName) | ||
.Select(x => x.Value) | ||
.FirstOrDefault(); | ||
|
||
class CodeFix | ||
{ | ||
public CodeFix(CodeAction action, ImmutableArray<Diagnostic> diagnostics, string providerName) | ||
{ | ||
Action = action; | ||
Diagnostics = diagnostics; | ||
ProviderName = providerName; | ||
} | ||
|
||
public CodeAction Action { get; } | ||
|
||
public ImmutableArray<Diagnostic> Diagnostics { get; } | ||
|
||
public string ProviderName { get; } | ||
|
||
public override string ToString() | ||
{ | ||
return Action.Title + " from '" + ProviderName + "'"; | ||
} | ||
|
||
var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); | ||
newText = newText.WithChanges(new TextChange(new TextSpan(0, 0), " ")); | ||
newText = newText.WithChanges(new TextChange(new TextSpan(0, 1), string.Empty)); | ||
return document.WithText(newText); | ||
} | ||
} | ||
} |