176 lines
6.7 KiB
C#
176 lines
6.7 KiB
C#
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using Ardalis.GuardClauses;
|
|
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;
|
|
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
|
|
|
|
namespace SharpIDE.Application.Features.Analysis;
|
|
|
|
public static class RoslynAnalysis
|
|
{
|
|
private static MSBuildWorkspace? _workspace;
|
|
private static HashSet<CodeFixProvider> _codeFixProviders = [];
|
|
private static HashSet<CodeRefactoringProvider> _codeRefactoringProviders = [];
|
|
private static TaskCompletionSource _solutionLoadedTcs = new();
|
|
public static void StartSolutionAnalysis(string solutionFilePath)
|
|
{
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await Analyse(solutionFilePath);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine($"RoslynAnalysis: Error during analysis: {e}");
|
|
}
|
|
});
|
|
}
|
|
public static async Task Analyse(string solutionFilePath)
|
|
{
|
|
Console.WriteLine($"RoslynAnalysis: Loading solution");
|
|
var timer = Stopwatch.StartNew();
|
|
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.RegisterWorkspaceFailedHandler(o => throw new InvalidOperationException($"Workspace failed: {o.Diagnostic.Message}"));
|
|
}
|
|
var solution = await _workspace.OpenSolutionAsync(solutionFilePath, new Progress());
|
|
timer.Stop();
|
|
Console.WriteLine($"RoslynAnalysis: Solution loaded in {timer.ElapsedMilliseconds}ms");
|
|
_solutionLoadedTcs.SetResult();
|
|
|
|
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));
|
|
|
|
var diagnostics = compilation.GetDiagnostics();
|
|
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 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<Diagnostic>> GetProjectDiagnostics(SharpIdeProjectModel projectModel)
|
|
{
|
|
await _solutionLoadedTcs.Task;
|
|
var cancellationToken = CancellationToken.None;
|
|
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == projectModel.FilePath);
|
|
var compilation = await project.GetCompilationAsync(cancellationToken);
|
|
Guard.Against.Null(compilation, nameof(compilation));
|
|
|
|
var diagnostics = compilation.GetDiagnostics(cancellationToken);
|
|
diagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToImmutableArray();
|
|
return diagnostics;
|
|
}
|
|
|
|
public static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Diagnostic diagnostic)
|
|
{
|
|
var cancellationToken = CancellationToken.None;
|
|
var document = _workspace!.CurrentSolution.GetDocument(diagnostic.Location.SourceTree);
|
|
Guard.Against.Null(document, nameof(document));
|
|
var result = await GetCodeFixesAsync(document, diagnostic);
|
|
return result;
|
|
}
|
|
private 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();
|
|
}
|
|
}
|