152 lines
4.6 KiB
C#
152 lines
4.6 KiB
C#
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;
|
|
}
|
|
}
|