Initial commit

This commit is contained in:
2026-06-05 20:53:53 -05:00
commit f9a59e9a66
99 changed files with 15897 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ArtificersScrollwork.Sidecar.Models;
namespace ArtificersScrollwork.Sidecar.Parsing;
/// <summary>
/// Walks a syntax tree and extracts ClassInfo for every class declaration.
/// </summary>
public class ClassWalker : CSharpSyntaxWalker
{
private readonly SemanticModel _model;
private readonly string _filePath;
public List<ClassInfo> Classes { get; } = new();
public ClassWalker(SemanticModel model, string filePath)
{
_model = model;
_filePath = filePath;
}
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
var symbol = _model.GetDeclaredSymbol(node);
if (symbol is null)
{
base.VisitClassDeclaration(node);
return;
}
var baseClass = symbol.BaseType?.Name;
var interfaces = symbol.Interfaces.Select(i => i.Name).ToList();
var ns = symbol.ContainingNamespace?.ToDisplayString() ?? "";
var attrs = symbol.GetAttributes().Select(a => a.AttributeClass?.Name ?? "").ToList();
// Heuristics for class category
bool isGump = IsGump(symbol);
bool isMobile = IsMobile(symbol);
bool isItem = IsItem(symbol);
// Methods
var methods = node.Members
.OfType<MethodDeclarationSyntax>()
.Select(m => ParseMethod(m, symbol))
.ToList();
// Properties
var properties = node.Members
.OfType<PropertyDeclarationSyntax>()
.Select(p => new Models.PropertyInfo(
p.Identifier.Text,
p.Type.ToString(),
p.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)) ?? false,
p.AccessorList?.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration)) ?? false
))
.ToList();
Classes.Add(new ClassInfo(
symbol.Name,
ns,
_filePath,
baseClass,
interfaces,
properties,
methods,
attrs,
isGump,
isMobile,
isItem
));
base.VisitClassDeclaration(node);
}
private Models.MethodInfo ParseMethod(MethodDeclarationSyntax m, INamedTypeSymbol classSymbol)
{
var isOverride = m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.OverrideKeyword));
var isVirtual = m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.VirtualKeyword));
var parameters = m.ParameterList.Parameters
.Select(p => new ParameterInfo(p.Identifier.Text, p.Type?.ToString() ?? ""))
.ToList();
// Check if this method sends a Gump
bool callsGump = false;
string? gumpClass = null;
if (m.Body is not null)
{
var sendGumps = m.Body.DescendantNodes()
.OfType<InvocationExpressionSyntax>()
.Where(inv => inv.ToString().Contains("SendGump"))
.FirstOrDefault();
if (sendGumps is not null)
{
callsGump = true;
// Try to extract the Gump class name from `new XxxGump(...)`
var newExpr = sendGumps.DescendantNodes()
.OfType<ObjectCreationExpressionSyntax>()
.FirstOrDefault();
gumpClass = newExpr?.Type.ToString();
}
}
return new Models.MethodInfo(
m.Identifier.Text,
m.ReturnType.ToString(),
parameters,
isOverride,
isVirtual,
callsGump,
gumpClass
);
}
private static bool IsGump(INamedTypeSymbol symbol)
{
var current = symbol.BaseType;
while (current is not null)
{
if (current.Name is "Gump" or "GumpPlus") return true;
current = current.BaseType;
}
return false;
}
private static bool IsMobile(INamedTypeSymbol symbol)
{
var current = symbol.BaseType;
while (current is not null)
{
if (current.Name is "Mobile" or "BaseCreature" or "BaseVendor") return true;
current = current.BaseType;
}
return false;
}
private static bool IsItem(INamedTypeSymbol symbol)
{
var current = symbol.BaseType;
while (current is not null)
{
if (current.Name is "Item" or "BaseWeapon" or "BaseArmor" or "BaseClothing") return true;
current = current.BaseType;
}
return false;
}
}