using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace InnovEnergy.Lib.SrcGen; public static class SyntaxUtils { // const String DebugFile = "/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/SrcGen/Debug.txt"; // // static SyntaxUtils() => File.WriteAllText(DebugFile, ""); public static Boolean HasExtendsAttribute(this TypeDeclarationSyntax td) { return td .GetAttributes() .Select(a => a.Name) .OfType<GenericNameSyntax>() .Any(n => n.Identifier.ValueText == "Extends"); } public static T GetAncestor<T>(this SyntaxNode sn) where T: SyntaxNode { return sn .Ancestors() .OfType<T>() .First(); } public static IEnumerable<AttributeSyntax> GetGenerateAttributes(this SyntaxNode sn) { return sn .DescendantNodes() .Where(n => n is AttributeSyntax { Name : GenericNameSyntax { Identifier.ValueText: "Generate" } }) .OfType<AttributeSyntax>(); } public static IEnumerable<AttributeSyntax> GetNestPropertiesAttributes(this SyntaxNode sn) { return sn .DescendantNodes() .Where(n => n is AttributeSyntax { Name : IdentifierNameSyntax { Identifier.ValueText: "NestProperties" } }) .OfType<AttributeSyntax>(); } public static IEnumerable<AttributeSyntax> GetAttributes(this MemberDeclarationSyntax member) { return member .AttributeLists .SelectMany(al => al.Attributes); } // public static T Debug<T>(this T t, String? title = null) // { // var prefix = title is null ? "" : title + ": "; // var contents = t is null ? "<null>" : t.ToString(); // // File.AppendAllText(DebugFile, prefix + contents + '\n'); // return t; // } public static IEnumerable<AttributeSyntax> GetAttributes(this SyntaxNode node, String attributeName) { return node .DescendantTokens() .Where(st => st.ValueText == attributeName) .Where(st => st.IsKind(SyntaxKind.IdentifierToken)) .Select(st => st.Parent?.Parent) .OfType<AttributeSyntax>(); } public static IEnumerable<T> ChildNodes<T>(this SyntaxNode node) where T : SyntaxNode { return node.ChildNodes().OfType<T>(); } public static IEnumerable<T> DescendantNodes<T>(this SyntaxNode node) where T : SyntaxNode { return node.DescendantNodes().OfType<T>(); } [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] public static T ReplaceIdentifiers<T>(this T root, String idToReplace, String replacementId) where T: SyntaxNode { var newIdentifier = SyntaxFactory.Identifier(replacementId); return root .EditNodes() .OfType<IdentifierNameSyntax>() .Where(n => n.Identifier.ValueText == idToReplace) .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n)) .EditNodes() .OfType<ConstructorDeclarationSyntax>() .Where(n => n.Identifier.ValueText == idToReplace) .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n)) .EditNodes() .OfType<TypeDeclarationSyntax>() .Where(n => n.Identifier.ValueText == idToReplace) .Replace(n => n.WithIdentifier(newIdentifier.WithTrailingTrivia(NewLine))); } private static readonly SyntaxTrivia NewLine = SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n"); private static readonly SyntaxTrivia WhiteSpace = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); private static readonly SyntaxToken PartialKeyword = SyntaxFactory .Token(SyntaxKind.PartialKeyword) .WithTrailingTrivia(WhiteSpace); public static CompilationUnitSyntax AddPartialModifier(this CompilationUnitSyntax baseFile) { return baseFile .EditNodes() .OfType<StructDeclarationSyntax>() .Where(s => !s.Modifiers.Contains(PartialKeyword)) .Replace(s => s.AddModifiers(PartialKeyword)); } public static T GetDefinition<T>(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode { return compilation .GetDefinitions(baseTypeDeclaration) .OfType<T>() .Single(); } public static IEnumerable<T> GetDefinitions<T>(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode { return compilation .GetDefinitions(baseTypeDeclaration) .OfType<T>(); } public static IEnumerable<SyntaxNode> GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration) { return compilation .GetSemanticModel(baseTypeDeclaration.SyntaxTree) .GetTypeInfo(baseTypeDeclaration) .Type? .DeclaringSyntaxReferences .Select(r => r.GetSyntax()) ?? Enumerable.Empty<SyntaxNode>(); } public static IEnumerable<TypeSyntax> GetGenericArguments(this AttributeSyntax attribute) => attribute.Name switch { GenericNameSyntax gns => gns.TypeArgumentList.Arguments, _ => Enumerable.Empty<TypeSyntax>() }; public static TypeSyntax GetGenericArgument(this AttributeSyntax attribute) => attribute.GetGenericArguments().Single(); public static CompilationUnitSyntax RemoveNotImplemented(this CompilationUnitSyntax baseFile) { return baseFile .EditNodes() .OfType<IdentifierNameSyntax>() .Where(i => i.Identifier.ValueText == nameof(NotImplementedException)) .HasAncestor<ThrowExpressionSyntax>() .GetAncestor<MemberDeclarationSyntax>() .Remove(); } public static String GetGeneratedPath(this String path, String postfix = "generated") { var ext = Path.GetExtension(path); var name = Path.GetFileNameWithoutExtension(path); var dir = Path.GetDirectoryName(path); return $"{dir}/{name}.{postfix}{ext}"; } }