Get Code Fixes (#4)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Composition.Hosting;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
|
||||
namespace SharpIDE.Application.Features.Analysis;
|
||||
|
||||
public static class CodeFixProviderLoader
|
||||
{
|
||||
// Alterative way, appears to be slower
|
||||
private static List<CodeFixProvider> LoadProviders(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
// Create an assembly catalog containing your current assembly
|
||||
var configuration = new ContainerConfiguration().WithAssemblies(assemblies);
|
||||
|
||||
using var container = configuration.CreateContainer();
|
||||
// Get all exported CodeFixProviders
|
||||
var providers = container.GetExports<CodeFixProvider>().ToList();
|
||||
return providers;
|
||||
}
|
||||
|
||||
// https://github.com/KirillOsenkov/CodeCleanupTools/blob/main/CodeFixer/FixerLoader.cs
|
||||
public static ImmutableArray<CodeFixProvider> LoadCodeFixProviders(IEnumerable<Assembly> assemblies, string language)
|
||||
{
|
||||
return assemblies
|
||||
.SelectMany(GetConcreteTypes)
|
||||
.Where(t => typeof(CodeFixProvider).IsAssignableFrom(t))
|
||||
.Where(t => IsExportedForLanguage(t, language))
|
||||
.Select(CreateInstanceOfCodeFix)
|
||||
.OfType<CodeFixProvider>()
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
private static bool IsExportedForLanguage(Type codeFixProvider, string language)
|
||||
{
|
||||
var exportAttribute = codeFixProvider.GetCustomAttribute<ExportCodeFixProviderAttribute>(inherit: false);
|
||||
return exportAttribute is not null && exportAttribute.Languages.Contains(language);
|
||||
}
|
||||
|
||||
private static CodeFixProvider? CreateInstanceOfCodeFix(Type codeFixProvider)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (CodeFixProvider)Activator.CreateInstance(codeFixProvider)!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetConcreteTypes(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
var concreteTypes = assembly
|
||||
.GetTypes()
|
||||
.Where(type => !type.GetTypeInfo().IsInterface
|
||||
&& !type.GetTypeInfo().IsAbstract
|
||||
&& !type.GetTypeInfo().ContainsGenericParameters);
|
||||
|
||||
// Realize the collection to ensure exceptions are caught
|
||||
return concreteTypes.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Type.EmptyTypes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis.CodeRefactorings;
|
||||
|
||||
namespace SharpIDE.Application.Features.Analysis;
|
||||
|
||||
public static class CodeRefactoringProviderLoader
|
||||
{
|
||||
public static ImmutableArray<CodeRefactoringProvider> LoadCodeRefactoringProviders(IEnumerable<Assembly> assemblies, string language)
|
||||
{
|
||||
return assemblies
|
||||
.SelectMany(GetConcreteTypes)
|
||||
.Where(t => typeof(CodeRefactoringProvider).IsAssignableFrom(t))
|
||||
.Where(t => IsExportedForLanguage(t, language))
|
||||
.Select(CreateInstanceOfCodeRefactoring)
|
||||
.OfType<CodeRefactoringProvider>()
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
private static bool IsExportedForLanguage(Type refactoringProvider, string language)
|
||||
{
|
||||
var exportAttribute = refactoringProvider.GetCustomAttribute<ExportCodeRefactoringProviderAttribute>(inherit: false);
|
||||
return exportAttribute is not null && exportAttribute.Languages.Contains(language);
|
||||
}
|
||||
|
||||
private static CodeRefactoringProvider? CreateInstanceOfCodeRefactoring(Type refactoringProvider)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (CodeRefactoringProvider)Activator.CreateInstance(refactoringProvider)!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetConcreteTypes(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
var concreteTypes = assembly
|
||||
.GetTypes()
|
||||
.Where(type => !type.GetTypeInfo().IsInterface
|
||||
&& !type.GetTypeInfo().IsAbstract
|
||||
&& !type.GetTypeInfo().ContainsGenericParameters);
|
||||
|
||||
// Realize the collection to ensure exceptions are caught
|
||||
return concreteTypes.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Type.EmptyTypes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,23 @@
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using Ardalis.GuardClauses;
|
||||
using Microsoft.CodeAnalysis.Classification;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CodeRefactorings;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Host.Mef;
|
||||
using Microsoft.CodeAnalysis.MSBuild;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using NuGet.Packaging;
|
||||
|
||||
namespace SharpIDE.Application.Features.Analysis;
|
||||
|
||||
public static class RoslynAnalysis
|
||||
{
|
||||
private static MSBuildWorkspace? _workspace;
|
||||
private static HashSet<CodeFixProvider> _codeFixProviders = [];
|
||||
private static HashSet<CodeRefactoringProvider> _codeRefactoringProviders = [];
|
||||
public static void StartSolutionAnalysis(string solutionFilePath)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
@@ -26,40 +36,117 @@ public static class RoslynAnalysis
|
||||
{
|
||||
Console.WriteLine($"RoslynAnalysis: Loading solution");
|
||||
var timer = Stopwatch.StartNew();
|
||||
_workspace ??= MSBuildWorkspace.Create();
|
||||
_workspace.WorkspaceFailed += (o, e) => throw new InvalidOperationException($"Workspace failed: {e.Diagnostic.Message}");
|
||||
if (_workspace is null)
|
||||
{
|
||||
// is this hostServices necessary? test without it - just getting providers from assemblies instead
|
||||
var host = MefHostServices.Create(MefHostServices.DefaultAssemblies);
|
||||
_workspace ??= MSBuildWorkspace.Create(host);
|
||||
_workspace.WorkspaceFailed += (o, e) => throw new InvalidOperationException($"Workspace failed: {e.Diagnostic.Message}");
|
||||
}
|
||||
var solution = await _workspace.OpenSolutionAsync(solutionFilePath, new Progress());
|
||||
timer.Stop();
|
||||
Console.WriteLine($"RoslynAnalysis: Solution loaded in {timer.ElapsedMilliseconds}ms");
|
||||
Console.WriteLine();
|
||||
|
||||
foreach (var assembly in MefHostServices.DefaultAssemblies)
|
||||
{
|
||||
//var assembly = analyzer.GetAssembly();
|
||||
var fixers = CodeFixProviderLoader.LoadCodeFixProviders([assembly], LanguageNames.CSharp);
|
||||
_codeFixProviders.AddRange(fixers);
|
||||
var refactoringProviders = CodeRefactoringProviderLoader.LoadCodeRefactoringProviders([assembly], LanguageNames.CSharp);
|
||||
_codeRefactoringProviders.AddRange(refactoringProviders);
|
||||
}
|
||||
|
||||
// // TODO: Distinct on the assemblies first
|
||||
// foreach (var project in solution.Projects)
|
||||
// {
|
||||
// var relevantAnalyzerReferences = project.AnalyzerReferences.OfType<AnalyzerFileReference>().ToArray();
|
||||
// var assemblies = relevantAnalyzerReferences.Select(a => a.GetAssembly()).ToArray();
|
||||
// var language = project.Language;
|
||||
// //var analyzers = relevantAnalyzerReferences.SelectMany(a => a.GetAnalyzers(language));
|
||||
// var fixers = CodeFixProviderLoader.LoadCodeFixProviders(assemblies, language);
|
||||
// _codeFixProviders.AddRange(fixers);
|
||||
// var refactoringProviders = CodeRefactoringProviderLoader.LoadCodeRefactoringProviders(assemblies, language);
|
||||
// _codeRefactoringProviders.AddRange(refactoringProviders);
|
||||
// }
|
||||
|
||||
_codeFixProviders = _codeFixProviders.DistinctBy(s => s.GetType().Name).ToHashSet();
|
||||
_codeRefactoringProviders = _codeRefactoringProviders.DistinctBy(s => s.GetType().Name).ToHashSet();
|
||||
|
||||
foreach (var project in solution.Projects)
|
||||
{
|
||||
//Console.WriteLine($"Project: {project.Name}");
|
||||
var compilation = await project.GetCompilationAsync();
|
||||
Guard.Against.Null(compilation, nameof(compilation));
|
||||
|
||||
// Get diagnostics (built-in or custom analyzers)
|
||||
var diagnostics = compilation.GetDiagnostics();
|
||||
var nonHiddenDiagnostics = diagnostics.Where(d => d.Severity is not Microsoft.CodeAnalysis.DiagnosticSeverity.Hidden).ToList();
|
||||
|
||||
var nonHiddenDiagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToList();
|
||||
//
|
||||
foreach (var diagnostic in nonHiddenDiagnostics)
|
||||
{
|
||||
Console.WriteLine(diagnostic);
|
||||
// Optionally run CodeFixProviders here
|
||||
}
|
||||
foreach (var document in project.Documents)
|
||||
{
|
||||
// var syntaxTree = await document.GetSyntaxTreeAsync();
|
||||
// var root = await syntaxTree!.GetRootAsync();
|
||||
// var classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, root.FullSpan);
|
||||
// foreach (var span in classifiedSpans)
|
||||
// {
|
||||
// var classifiedSpan = root.GetText().GetSubText(span.TextSpan);
|
||||
// Console.WriteLine($"{span.TextSpan}: {span.ClassificationType}");
|
||||
// Console.WriteLine(classifiedSpan);
|
||||
// }
|
||||
}
|
||||
// foreach (var document in project.Documents)
|
||||
// {
|
||||
// var semanticModel = await document.GetSemanticModelAsync();
|
||||
// Guard.Against.Null(semanticModel, nameof(semanticModel));
|
||||
// var documentDiagnostics = semanticModel.GetDiagnostics().Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToList();
|
||||
// foreach (var diagnostic in documentDiagnostics)
|
||||
// {
|
||||
// var test = await GetCodeFixesAsync(document, diagnostic);
|
||||
// }
|
||||
// // var syntaxTree = await document.GetSyntaxTreeAsync();
|
||||
// // var root = await syntaxTree!.GetRootAsync();
|
||||
// // var classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, root.FullSpan);
|
||||
// // foreach (var span in classifiedSpans)
|
||||
// // {
|
||||
// // var classifiedSpan = root.GetText().GetSubText(span.TextSpan);
|
||||
// // Console.WriteLine($"{span.TextSpan}: {span.ClassificationType}");
|
||||
// // Console.WriteLine(classifiedSpan);
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
Console.WriteLine("RoslynAnalysis: Analysis completed.");
|
||||
}
|
||||
|
||||
public static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Document document, Diagnostic diagnostic)
|
||||
{
|
||||
var cancellationToken = CancellationToken.None;
|
||||
var position = diagnostic.Location.SourceSpan.Start;
|
||||
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);
|
||||
}
|
||||
|
||||
var refactorContext = new CodeRefactoringContext(
|
||||
document,
|
||||
diagnostic.Location.SourceSpan,
|
||||
action => codeActions.Add(action),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
foreach (var provider in _codeRefactoringProviders)
|
||||
{
|
||||
await provider.ComputeRefactoringsAsync(refactorContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (codeActions.Count is not 0)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
return codeActions.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="1.9.1" />
|
||||
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.14.8" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.14.8" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Features" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.14.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.SolutionPersistence" Version="1.0.52" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.14.0" />
|
||||
|
||||
Reference in New Issue
Block a user