using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Linq; namespace XFEExtension.NetCore.AutoPath.Analyzer.Generator { [Generator] public class PathPropertyAutoGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new AutoPathSyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { if (!(context.SyntaxReceiver is AutoPathSyntaxReceiver receiver)) return; var syntaxTrees = context.Compilation.SyntaxTrees; foreach (var syntaxTree in syntaxTrees) { var root = syntaxTree.GetRoot(); var classDeclarations = GetClassDeclarations(root); var fileScopedNamespaceDeclarationSyntax = GetFileScopedNamespaceDeclaration(root); foreach (var classDeclaration in classDeclarations) { var fieldDeclarationSyntaxes = GetFieldDeclarations(classDeclaration); if (fieldDeclarationSyntaxes is null || !fieldDeclarationSyntaxes.Any()) { continue; } var className = classDeclaration.Identifier.ValueText; var properties = new List(); var methods = new List(); var enableCheckProperties = new List(); foreach (var fieldDeclarationSyntax in fieldDeclarationSyntaxes) { var variableDeclaration = fieldDeclarationSyntax.Declaration.Variables.First(); var fieldName = variableDeclaration.Identifier.Text; var propertyName = fieldName[0] == '_' ? fieldName[1].ToString().ToUpper() + fieldName.Substring(2) : fieldName[0].ToString().ToUpper() + fieldName.Substring(1); var getMethodName = $"Get{propertyName}Property"; var enableCheckPropertyName = $"{propertyName}EnableCheck"; GetAutoPathAttributeList(fieldDeclarationSyntax).ForEach(attribute => { if (attribute.ArgumentList is null) { return; } var argument = attribute.ArgumentList.Arguments.First(); if (argument.Expression is LiteralExpressionSyntax literalExpressionSyntax) { propertyName = literalExpressionSyntax.Token.ValueText; } }); var propertyType = fieldDeclarationSyntax.Declaration.Type; var triviaText = $@"/// /// /// 是根据 自动生成的路径属性

///
"; var checkEnableTriviaText = $@"/// /// 是否为 启用检测路径

///
"; var property = SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))) .WithAccessorList(SyntaxFactory.AccessorList( SyntaxFactory.List(new[] { SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithBody(SyntaxFactory.Block( SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression($"Options ??= new {className}()")), SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression($"global::XFEExtension.NetCore.AutoPath.XFEAutoPath.CheckPathExistAndCreate({fieldName}, Options.{enableCheckPropertyName})")), SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression($"{getMethodName}()")), SyntaxFactory.ReturnStatement(SyntaxFactory.ParseExpression($"{fieldName}")))) }))) .WithLeadingTrivia(SyntaxFactory.ParseLeadingTrivia(triviaText)); var enableCheckProperty = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("bool"), enableCheckPropertyName) .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) .WithAccessorList(SyntaxFactory.AccessorList( SyntaxFactory.List(new[] { SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) }))) .WithLeadingTrivia(SyntaxFactory.ParseLeadingTrivia(checkEnableTriviaText)) .WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("true"))) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); var getMethod = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("void"), getMethodName) .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword))) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); properties.Add(property.NormalizeWhitespace()); methods.Add(getMethod); enableCheckProperties.Add(enableCheckProperty.NormalizeWhitespace()); } var profileClassSyntaxTree = GeneratePathClassSyntaxTree(classDeclaration, properties, enableCheckProperties, methods, fileScopedNamespaceDeclarationSyntax); context.AddSource($"{className}.g.cs", profileClassSyntaxTree.ToString()); } } } public static bool IsAutoPathAttribute(AttributeListSyntax attributeList) => attributeList.Attributes.Any(attribute => attribute.Name.ToString() == "AutoPath"); public static List GetAutoPathAttributeList(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.AttributeLists.Where(IsAutoPathAttribute).SelectMany(attributeList => attributeList.Attributes).ToList(); public static FileScopedNamespaceDeclarationSyntax GetFileScopedNamespaceDeclaration(SyntaxNode rootNode) { var namespaceResults = rootNode.DescendantNodes().OfType(); if (namespaceResults != null && namespaceResults.Count() > 0) return namespaceResults.First(); return null; } public static IEnumerable GetFieldDeclarations(ClassDeclarationSyntax classDeclaration) => classDeclaration.DescendantNodes() .OfType() .Where(fieldDeclarationSyntax => fieldDeclarationSyntax.AttributeLists.Any(IsAutoPathAttribute) && fieldDeclarationSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)); public static IEnumerable GetClassDeclarations(SyntaxNode rootNode) => rootNode.DescendantNodes() .OfType() .Where(classDeclaration => classDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)); private static SyntaxTree GeneratePathClassSyntaxTree(ClassDeclarationSyntax classDeclaration, List propertyDeclarationSyntaxes, List enableCheckDeclarationSyntaxes, List methodDeclarationSyntaxes, FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDeclarationSyntax) { var className = classDeclaration.Identifier.ValueText; var triviaText = $@"/// /// 已生成以下路径:
/// "; triviaText += string.Join("
\n", propertyDeclarationSyntaxes.Select(propertyDeclarationSyntax => $"/// ○ ")) + "\n///

\n/// 来自\n///
\n"; var memberDeclarations = new List() { SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName(className), "Options") .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))) .WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List( new[] { SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) }))) .WithLeadingTrivia(SyntaxFactory.ParseLeadingTrivia($@"/// /// 配置选项
/// 类的配置选项 ///
")) }; memberDeclarations.AddRange(propertyDeclarationSyntaxes); memberDeclarations.AddRange(methodDeclarationSyntaxes); memberDeclarations.AddRange(enableCheckDeclarationSyntaxes); var pathClass = SyntaxFactory.ClassDeclaration(className) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)) .AddMembers(memberDeclarations.ToArray()) .WithLeadingTrivia(SyntaxFactory.ParseLeadingTrivia(triviaText)) .NormalizeWhitespace(); MemberDeclarationSyntax memberDeclaration; if (fileScopedNamespaceDeclarationSyntax is null) { var namespaceDeclaration = classDeclaration.FirstAncestorOrSelf(); if (namespaceDeclaration is null) memberDeclaration = pathClass; else memberDeclaration = SyntaxFactory.NamespaceDeclaration(namespaceDeclaration.Name) .AddMembers(pathClass); } else { memberDeclaration = SyntaxFactory.FileScopedNamespaceDeclaration(fileScopedNamespaceDeclarationSyntax.Name) .AddMembers(pathClass); } var profileClassCompilationUnit = SyntaxFactory.CompilationUnit() .AddMembers(memberDeclaration) .NormalizeWhitespace(); return SyntaxFactory.SyntaxTree(profileClassCompilationUnit); } } }