Initial commit
This commit is contained in:
86
sidecar/Parsing/ScriptIndexer.cs
Normal file
86
sidecar/Parsing/ScriptIndexer.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using ArtificersScrollwork.Sidecar.Models;
|
||||
|
||||
namespace ArtificersScrollwork.Sidecar.Parsing;
|
||||
|
||||
/// <summary>
|
||||
/// Walks a ServUO Scripts directory, parses all .cs files with Roslyn,
|
||||
/// and builds a searchable in-memory index of classes and methods.
|
||||
/// </summary>
|
||||
public class ScriptIndexer
|
||||
{
|
||||
private readonly Dictionary<string, ClassInfo> _classes =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>The Roslyn compilation built during indexing. Used by CallChainTracer.</summary>
|
||||
public CSharpCompilation? Compilation { get; private set; }
|
||||
|
||||
public ScriptIndex IndexDirectory(string path)
|
||||
{
|
||||
var files = Directory
|
||||
.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories)
|
||||
.ToList();
|
||||
|
||||
var syntaxTrees = files
|
||||
.Select(f =>
|
||||
{
|
||||
try { return CSharpSyntaxTree.ParseText(File.ReadAllText(f), path: f); }
|
||||
catch { return null; }
|
||||
})
|
||||
.Where(t => t is not null)
|
||||
.Select(t => t!)
|
||||
.ToList();
|
||||
|
||||
Compilation = CSharpCompilation.Create(
|
||||
"ServUOAnalysis",
|
||||
syntaxTrees,
|
||||
references: null,
|
||||
options: new CSharpCompilationOptions(
|
||||
OutputKind.DynamicallyLinkedLibrary,
|
||||
reportSuppressedDiagnostics: false
|
||||
)
|
||||
);
|
||||
|
||||
_classes.Clear();
|
||||
|
||||
foreach (var tree in syntaxTrees)
|
||||
{
|
||||
SemanticModel model;
|
||||
try { model = Compilation.GetSemanticModel(tree); }
|
||||
catch { continue; }
|
||||
|
||||
var walker = new ClassWalker(model, tree.FilePath);
|
||||
try { walker.Visit(tree.GetRoot()); }
|
||||
catch { continue; }
|
||||
|
||||
foreach (var cls in walker.Classes)
|
||||
_classes[cls.Name] = cls;
|
||||
}
|
||||
|
||||
var summaries = _classes.Values
|
||||
.Select(c => new ClassSummary(
|
||||
c.Name, c.Namespace, c.FilePath, c.BaseClass,
|
||||
c.IsGump, c.IsMobile, c.IsItem))
|
||||
.OrderBy(c => c.Namespace)
|
||||
.ThenBy(c => c.Name)
|
||||
.ToList();
|
||||
|
||||
return new ScriptIndex(_classes.Count, files.Count, summaries);
|
||||
}
|
||||
|
||||
public ClassInfo? GetClass(string name) =>
|
||||
_classes.TryGetValue(name, out var cls) ? cls : null;
|
||||
|
||||
public List<ClassSummary> Search(string query) =>
|
||||
_classes.Values
|
||||
.Where(c =>
|
||||
c.Name.Contains(query, StringComparison.OrdinalIgnoreCase) ||
|
||||
c.Namespace.Contains(query, StringComparison.OrdinalIgnoreCase) ||
|
||||
c.Methods.Any(m => m.Name.Contains(query, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(c => new ClassSummary(
|
||||
c.Name, c.Namespace, c.FilePath, c.BaseClass,
|
||||
c.IsGump, c.IsMobile, c.IsItem))
|
||||
.Take(100)
|
||||
.ToList();
|
||||
}
|
||||
Reference in New Issue
Block a user