🐛 Show code actions from MEF default assemblies
This commit is contained in:
@@ -56,6 +56,7 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
|||||||
private static ICodeRefactoringService? _codeRefactoringService;
|
private static ICodeRefactoringService? _codeRefactoringService;
|
||||||
private static IDocumentMappingService? _documentMappingService;
|
private static IDocumentMappingService? _documentMappingService;
|
||||||
private static HashSet<CodeRefactoringProvider> _codeRefactoringProviders = [];
|
private static HashSet<CodeRefactoringProvider> _codeRefactoringProviders = [];
|
||||||
|
private static HashSet<CodeFixProvider> _codeFixProviders = [];
|
||||||
|
|
||||||
// Primarily used for getting the globs for a project
|
// Primarily used for getting the globs for a project
|
||||||
private Dictionary<ProjectId, ProjectFileInfo> _projectFileInfoMap = new();
|
private Dictionary<ProjectId, ProjectFileInfo> _projectFileInfoMap = new();
|
||||||
@@ -144,10 +145,15 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
|||||||
{
|
{
|
||||||
foreach (var assembly in MefHostServices.DefaultAssemblies)
|
foreach (var assembly in MefHostServices.DefaultAssemblies)
|
||||||
{
|
{
|
||||||
|
// These could be loaded from the composition via _workspace.CurrentSolution.Services.ExportProvider.GetExports<Lazy<CodeFixProvider, CodeChangeProviderMetadata>>().ToList(),
|
||||||
|
// however we need all the CodeFixProviders/CodeRefactoringProviders immediately on the first code action request, so I would prefer to do it here
|
||||||
|
var fixers = CodeFixProviderLoader.LoadCodeFixProviders([assembly], LanguageNames.CSharp);
|
||||||
|
_codeFixProviders.AddRange(fixers);
|
||||||
var refactoringProviders = CodeRefactoringProviderLoader.LoadCodeRefactoringProviders([assembly], LanguageNames.CSharp);
|
var refactoringProviders = CodeRefactoringProviderLoader.LoadCodeRefactoringProviders([assembly], LanguageNames.CSharp);
|
||||||
_codeRefactoringProviders.AddRange(refactoringProviders);
|
_codeRefactoringProviders.AddRange(refactoringProviders);
|
||||||
}
|
}
|
||||||
_codeRefactoringProviders = _codeRefactoringProviders.DistinctBy(s => s.GetType().Name).ToHashSet();
|
_codeRefactoringProviders = _codeRefactoringProviders.DistinctBy(s => s.GetType().Name).ToHashSet();
|
||||||
|
_codeFixProviders = _codeFixProviders.DistinctBy(s => s.GetType().Name).ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
// // TODO: Distinct on the assemblies first
|
// // TODO: Distinct on the assemblies first
|
||||||
@@ -597,6 +603,7 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
|||||||
return new IdeCompletionListResult(document, completions);
|
return new IdeCompletionListResult(document, completions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Pass in LinePositionSpan for refactorings that span multiple characters, e.g. extract method
|
||||||
public async Task<ImmutableArray<CodeAction>> GetCodeFixesForDocumentAtPosition(SharpIdeFile fileModel, LinePosition linePosition, CancellationToken cancellationToken = default)
|
public async Task<ImmutableArray<CodeAction>> GetCodeFixesForDocumentAtPosition(SharpIdeFile fileModel, LinePosition linePosition, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeFixesForDocumentAtPosition)}");
|
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeFixesForDocumentAtPosition)}");
|
||||||
@@ -606,15 +613,8 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
|||||||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
||||||
Guard.Against.Null(semanticModel, nameof(semanticModel));
|
Guard.Against.Null(semanticModel, nameof(semanticModel));
|
||||||
|
|
||||||
var projectAnalyzers = document.Project.AnalyzerReferences
|
// We don't need analyzer diagnostics, as ICodeFixService does not take diagnostics to find fixes for, it takes a document and span
|
||||||
.OfType<IsolatedAnalyzerFileReference>()
|
var diagnostics = semanticModel.Compilation.GetDiagnostics(cancellationToken);
|
||||||
.SelectMany(r => r.GetAnalyzers(document.Project.Language))
|
|
||||||
.ToImmutableArray();
|
|
||||||
|
|
||||||
var compilationWithAnalyzers = semanticModel.Compilation.WithAnalyzers(projectAnalyzers);
|
|
||||||
|
|
||||||
var analysisResult = await compilationWithAnalyzers.GetAnalysisResultAsync(semanticModel, null, cancellationToken);
|
|
||||||
var diagnostics = analysisResult.GetAllDiagnostics();
|
|
||||||
|
|
||||||
var sourceText = await document.GetTextAsync(cancellationToken);
|
var sourceText = await document.GetTextAsync(cancellationToken);
|
||||||
var position = sourceText.Lines.GetPosition(linePosition);
|
var position = sourceText.Lines.GetPosition(linePosition);
|
||||||
@@ -622,23 +622,48 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
|
|||||||
.Where(d => d.Location.IsInSource && d.Location.SourceSpan.Contains(position))
|
.Where(d => d.Location.IsInSource && d.Location.SourceSpan.Contains(position))
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
|
|
||||||
ImmutableArray<CodeAction> codeActions = [];
|
var arrayBuilder = ImmutableArray.CreateBuilder<CodeAction>();
|
||||||
foreach (var diagnostic in diagnosticsAtPosition)
|
foreach (var diagnostic in diagnosticsAtPosition)
|
||||||
{
|
{
|
||||||
var actions = await GetCodeFixesAsync(document, diagnostic, cancellationToken);
|
var actions = await GetCodeFixesAsync(document, diagnostic, cancellationToken);
|
||||||
codeActions = codeActions.AddRange(actions);
|
arrayBuilder.AddRange(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var textSpan = new TextSpan(position, 0);
|
||||||
|
var codeActionsFromProjectAnalyzers = await GetCodeFixesFromProjectAnalyzersAsync(document, textSpan, cancellationToken);
|
||||||
|
arrayBuilder.AddRange(codeActionsFromProjectAnalyzers);
|
||||||
|
|
||||||
var linePositionSpan = new LinePositionSpan(linePosition, new LinePosition(linePosition.Line, linePosition.Character + 1));
|
var linePositionSpan = new LinePositionSpan(linePosition, new LinePosition(linePosition.Line, linePosition.Character + 1));
|
||||||
var selectedSpan = sourceText.Lines.GetTextSpan(linePositionSpan);
|
var selectedSpan = sourceText.Lines.GetTextSpan(linePositionSpan);
|
||||||
codeActions = codeActions.AddRange(await GetCodeRefactoringsAsync(document, selectedSpan, cancellationToken));
|
arrayBuilder.AddRange(await GetCodeRefactoringsAsync(document, selectedSpan, cancellationToken));
|
||||||
return codeActions;
|
return arrayBuilder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixes from the MefHostServices.DefaultAssemblies
|
||||||
private static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken = default)
|
private static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var span = diagnostic.Location.SourceSpan;
|
var codeActions = new List<CodeAction>();
|
||||||
|
var context = new CodeFixContext(
|
||||||
|
document,
|
||||||
|
diagnostic,
|
||||||
|
(action, _) => codeActions.Add(action), // callback collects fixes
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
var relevantProviders = _codeFixProviders.Where(provider => provider.FixableDiagnosticIds.Contains(diagnostic.Id));
|
||||||
|
|
||||||
|
foreach (var provider in relevantProviders)
|
||||||
|
{
|
||||||
|
await provider.RegisterCodeFixesAsync(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeActions.ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<ImmutableArray<CodeAction>> GetCodeFixesFromProjectAnalyzersAsync(Document document, TextSpan span, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// We could get the CodeFixProviders from the project's IsolatedAnalyzerFileReferences. For now, ICodeFixService handles caching them for me
|
||||||
|
// I also do not know why the _codeFixService does not return fixes for MefHostServices.DefaultAssemblies - I have verified that there are Lazy<CodeFixProvider, CodeChangeProviderMetadata>>'s provided by the composition for them
|
||||||
var fixCollections = await _codeFixService!.GetFixesAsync(
|
var fixCollections = await _codeFixService!.GetFixesAsync(
|
||||||
document,
|
document,
|
||||||
span,
|
span,
|
||||||
|
|||||||
Reference in New Issue
Block a user