-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAnalysisContext.cs
More file actions
82 lines (72 loc) · 3.54 KB
/
Copy pathAnalysisContext.cs
File metadata and controls
82 lines (72 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Concurrent;
using System.Diagnostics;
record AnalysisContext(
Solution Solution,
ConcurrentDictionary<ProjectId, Compilation> Compilations,
HashSet<ProjectId> ProjectsWithErrors,
ConcurrentDictionary<string, ConcurrentDictionary<string, byte>> IdentifierToFiles)
{
/// <summary>
/// Shared infrastructure: compiles all projects, detects errors, builds identifier index.
/// </summary>
public static async Task<AnalysisContext> BuildAsync(Solution solution, StreamWriter resultWriter)
{
var compilations = new ConcurrentDictionary<ProjectId, Compilation>();
var identifierToFiles = new ConcurrentDictionary<string, ConcurrentDictionary<string, byte>>();
var sw = Stopwatch.StartNew();
// Phase 1: Pre-compile all projects in parallel
Console.WriteLine($"[INFO] Pre-compiling {solution.ProjectIds.Count} projects in parallel...");
await Parallel.ForEachAsync(solution.Projects, async (project, ct) =>
{
try
{
var compilation = await project.GetCompilationAsync(ct);
if (compilation != null && compilation.SyntaxTrees.Any())
{
compilations[project.Id] = compilation;
Console.WriteLine($"[INFO] Compiled: {project.Name} ({compilation.SyntaxTrees.Count()} files)");
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to compile project {project.Name}: {ex.Message}");
}
});
Console.WriteLine($"[INFO] Compilation done in {sw.Elapsed.TotalSeconds:F1}s");
// Pre-compute error set to avoid calling GetDiagnostics() multiple times
var projectsWithErrors = new HashSet<ProjectId>();
foreach (var kvp in compilations)
{
var diags = kvp.Value.GetDeclarationDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
if (diags.Count > 0)
{
var project = solution.GetProject(kvp.Key);
Console.WriteLine($"[WARN] {project?.Name}: {diags.Count} compilation errors");
projectsWithErrors.Add(kvp.Key);
}
}
if (projectsWithErrors.Count > 0)
Console.WriteLine($"[WARN] {projectsWithErrors.Count} projects have compilation errors — reference detection may be incomplete");
// Phase 2.5: Build identifier-to-files index for fast pre-filtering
Console.WriteLine($"[INFO] Building identifier index...");
await Parallel.ForEachAsync(compilations, async (kvp, ct) =>
{
var compilation = kvp.Value;
foreach (var tree in compilation.SyntaxTrees)
{
var root = await tree.GetRootAsync(ct);
var fp = tree.FilePath;
var seen = new HashSet<string>();
foreach (var token in root.DescendantTokens())
{
if (token.IsKind(SyntaxKind.IdentifierToken) && seen.Add(token.Text))
identifierToFiles.GetOrAdd(token.Text, _ => new ConcurrentDictionary<string, byte>(StringComparer.OrdinalIgnoreCase))[fp] = 0;
}
}
});
Console.WriteLine($"[INFO] Index built: {identifierToFiles.Count} unique identifiers in {sw.Elapsed.TotalSeconds:F1}s");
return new AnalysisContext(solution, compilations, projectsWithErrors, identifierToFiles);
}
}