Initial commit
This commit is contained in:
151
sidecar/Parsing/RoslynParser.cs
Normal file
151
sidecar/Parsing/RoslynParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user