diff --git a/DotNetAstGen/DotNetAstGen.csproj b/DotNetAstGen/DotNetAstGen.csproj
index 4b2c629..f103ed2 100644
--- a/DotNetAstGen/DotNetAstGen.csproj
+++ b/DotNetAstGen/DotNetAstGen.csproj
@@ -7,6 +7,7 @@
enable
true
true
+ true
diff --git a/DotNetAstGen/Program.cs b/DotNetAstGen/Program.cs
index 19b80b3..977fe56 100644
--- a/DotNetAstGen/Program.cs
+++ b/DotNetAstGen/Program.cs
@@ -1,4 +1,4 @@
-using System.Reflection;
+using System.Diagnostics;
using System.Text;
using CommandLine;
using DotNetAstGen.Utils;
@@ -34,52 +34,70 @@ public static void Main(string[] args)
_logger = LoggerFactory.CreateLogger();
_logger.LogDebug("Show verbose output.");
- _RunAstGet(options.InputFilePath);
+ _RunAstGet(options.InputFilePath, new DirectoryInfo(options.OutputDirectory));
});
}
- private static void _RunAstGet(string inputPath)
+ private static void _RunAstGet(string inputPath, DirectoryInfo rootOutputPath)
{
+ if (!rootOutputPath.Exists)
+ {
+ rootOutputPath.Create();
+ }
+
if (Directory.Exists(inputPath))
{
_logger?.LogInformation("Parsing directory {dirName}", inputPath);
- foreach (FileInfo fileInfo in new DirectoryInfo(inputPath).EnumerateFiles("*.cs"))
+ var rootDirectory = new DirectoryInfo(inputPath);
+ foreach (var inputFile in new DirectoryInfo(inputPath).EnumerateFiles("*.cs"))
{
- _AstForFile(fileInfo);
+ _AstForFile(rootDirectory, rootOutputPath, inputFile);
}
}
else if (File.Exists(inputPath))
{
_logger?.LogInformation("Parsing file {fileName}", inputPath);
- _AstForFile(new FileInfo(inputPath));
+ var file = new FileInfo(inputPath);
+ Debug.Assert(file.Directory != null, "Given file has a null parent directory!");
+ _AstForFile(file.Directory, rootOutputPath, file);
}
else
{
_logger?.LogError("The path {inputPath} does not exist!", inputPath);
Environment.Exit(1);
}
- _logger?.LogInformation("Parsing successful!");
+
+ _logger?.LogInformation("AST generation complete");
}
- private static void _AstForFile(FileInfo filePath)
+ private static void _AstForFile(FileSystemInfo rootInputPath, FileSystemInfo rootOutputPath, FileInfo filePath)
{
var fullPath = filePath.FullName;
_logger?.LogDebug("Parsing file: {filePath}", fullPath);
- using var streamReader = new StreamReader(fullPath, Encoding.UTF8);
- var programText = streamReader.ReadToEnd();
- var tree = CSharpSyntaxTree.ParseText(programText);
- _logger?.LogDebug("Successfully parsed: {filePath}", fullPath);
- var root = tree.GetCompilationUnitRoot();
- var rootType = root.GetType();
- IList props = new List(rootType.GetProperties());
- var jsonString = JsonConvert.SerializeObject(root, Formatting.Indented, new JsonSerializerSettings
+ try
{
- ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
- ContractResolver = new IgnorePropertiesResolver() // Comment this to see the unfiltered parser output
- });
- var outputName = Path.Combine(filePath.DirectoryName ?? "./", $"{Path.GetFileNameWithoutExtension(fullPath)}.json");
- _logger?.LogDebug("Writing AST to {astJsonPath}", outputName);
- File.WriteAllText(outputName, jsonString);
+ using var streamReader = new StreamReader(fullPath, Encoding.UTF8);
+ var programText = streamReader.ReadToEnd();
+ var tree = CSharpSyntaxTree.ParseText(programText);
+ _logger?.LogDebug("Successfully parsed: {filePath}", fullPath);
+ var root = tree.GetCompilationUnitRoot();
+ var jsonString = JsonConvert.SerializeObject(root, Formatting.Indented, new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+ ContractResolver =
+ new IgnorePropertiesResolver() // Comment this to see the unfiltered parser output
+ });
+ var outputName = Path.Combine(filePath.DirectoryName ?? "./",
+ $"{Path.GetFileNameWithoutExtension(fullPath)}.json")
+ .Replace(rootInputPath.FullName, rootOutputPath.FullName);
+
+ File.WriteAllText(outputName, jsonString);
+ _logger?.LogInformation("Successfully wrote AST to '{astJsonPath}'", outputName);
+ }
+ catch (Exception e)
+ {
+ _logger?.LogError("Error encountered while parsing '{filePath}': {errorMsg}", fullPath, e.Message);
+ }
}
}
@@ -87,9 +105,12 @@ private static void _AstForFile(FileInfo filePath)
internal class Options
{
[Option('v', "verbose", Required = false, HelpText = "Enable verbose output.")]
- public bool Verbose { get; set; }
+ public bool Verbose { get; set; } = false;
[Option('i', "input", Required = true, HelpText = "Input file or directory.")]
- public string InputFilePath { get; set; }
+ public string InputFilePath { get; set; } = "";
+
+ [Option('o', "input", Required = false, HelpText = "Output directory. (default `./.ast`)")]
+ public string OutputDirectory { get; set; } = ".ast";
}
}
\ No newline at end of file
diff --git a/DotNetAstGen/Utils/JsonResolver.cs b/DotNetAstGen/Utils/JsonResolver.cs
index da5e224..36c1e8f 100644
--- a/DotNetAstGen/Utils/JsonResolver.cs
+++ b/DotNetAstGen/Utils/JsonResolver.cs
@@ -1,34 +1,54 @@
using System.Reflection;
+using System.Text.RegularExpressions;
+using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace DotNetAstGen.Utils
{
- class IgnorePropertiesResolver : DefaultContractResolver
+ internal class IgnorePropertiesResolver : DefaultContractResolver
{
- private readonly string[] _propsToIgnore =
+ private static readonly ILogger? Logger = Program.LoggerFactory?.CreateLogger("IgnorePropertiesResolver");
+
+
+ private readonly HashSet _propsToAllow = new(new[]
{
- "Parent", "ParentTrivia", "ContainsSkippedText", "SyntaxTree", "SpanStart", "IsMissing",
- "IsStructuredTrivia", "HasStructuredTrivia", "ContainsDiagnostics", "ContainsDirectives",
- "HasLeadingTrivia", "HasTrailingTrivia", "ContainsAnnotations", "Span", "FullSpan", "LeadingTrivia",
- "TrailingTrivia"
- };
+ "Value", "Externs", "Usings", "Name", "Identifier", "Left", "Right", "Members", "ConstraintClauses",
+ "Alias", "NamespaceOrType", "Arguments", "Expression", "Declaration", "ElementType", "Initializer", "Else",
+ "Condition", "Statement", "Statements", "Variables", "WhenNotNull", "AllowsAnyExpression", "Expressions",
+ "Modifiers", "ReturnType", "IsUnboundGenericName", "Default", "IsConst", "Parameters", "Types",
+ "ExplicitInterfaceSpecifier", "Text", "Length", "Location"
+ });
- private readonly HashSet _ignoreProps;
+ private readonly List _regexToAllow = new(new[]
+ {
+ ".*Token$", ".*Keyword$", ".*Lists?$", ".*Body$", "(Span)?Start"
+ });
+
+ private readonly List _regexToIgnore = new(new[]
+ {
+ ".*(Semicolon|Brace|Bracket|EndOfFile|Paren|Dot)Token$"
+ });
- public IgnorePropertiesResolver()
+ private bool MatchesAllow(string input)
{
- _ignoreProps = new HashSet(this._propsToIgnore);
+ return _regexToAllow.Any(regex => Regex.IsMatch(input, regex));
+ }
+
+ private bool MatchesIgnore(string input)
+ {
+ return _regexToIgnore.Any(regex => Regex.IsMatch(input, regex));
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
- if (_ignoreProps.Contains(property.PropertyName ?? ""))
- {
- property.ShouldSerialize = _ => false;
- }
-
+ var propertyName = property.PropertyName ?? "";
+ var shouldSerialize = propertyName != "" &&
+ (_propsToAllow.Contains(propertyName) || MatchesAllow(propertyName)) &&
+ !MatchesIgnore(propertyName);
+ Logger?.LogDebug(shouldSerialize ? $"Allowing {propertyName}" : $"Ignoring {propertyName}");
+ property.ShouldSerialize = _ => shouldSerialize;
return property;
}
}
diff --git a/DotNetAstGen/Utils/MapSerializer.cs b/DotNetAstGen/Utils/MapSerializer.cs
index 25d580a..550148a 100644
--- a/DotNetAstGen/Utils/MapSerializer.cs
+++ b/DotNetAstGen/Utils/MapSerializer.cs
@@ -1,19 +1,16 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Newtonsoft.Json.Linq;
-using System.Data;
-using System.Runtime.InteropServices;
-using DotNetAstGen.Utils;
using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
namespace DotNetAstGen.Utils
{
public class MapSerializer
{
- private static ILogger? _logger = Program.LoggerFactory?.CreateLogger("MapSerializer");
-
+ private static readonly ILogger? Logger = Program.LoggerFactory?.CreateLogger("MapSerializer");
+
public static JArray ProcessSyntaxTokenList(SyntaxTokenList list)
{
JArray syntaxTokenList = new();