using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using ArtificersScrollwork.Sidecar.Models; namespace ArtificersScrollwork.Sidecar.Parsing; /// /// Walks a ServUO Scripts directory, parses all .cs files with Roslyn, /// and builds a searchable in-memory index of classes and methods. /// public class ScriptIndexer { private readonly Dictionary _classes = new(StringComparer.OrdinalIgnoreCase); /// The Roslyn compilation built during indexing. Used by CallChainTracer. 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 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(); }