|
| 1 | +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. |
| 2 | +// Licensed under the MIT License. See LICENSE in the project root for license information. |
| 3 | + |
| 4 | +namespace StyleCop.Analyzers.CodeGeneration |
| 5 | +{ |
| 6 | + using System; |
| 7 | + using System.Collections.Generic; |
| 8 | + using System.Collections.Immutable; |
| 9 | + using System.IO; |
| 10 | + using System.Linq; |
| 11 | + using System.Text; |
| 12 | + using System.Xml.Linq; |
| 13 | + using System.Xml.XPath; |
| 14 | + using Microsoft.CodeAnalysis; |
| 15 | + using Microsoft.CodeAnalysis.CSharp; |
| 16 | + using Microsoft.CodeAnalysis.CSharp.Syntax; |
| 17 | + using Microsoft.CodeAnalysis.Text; |
| 18 | + |
| 19 | + [Generator] |
| 20 | + internal sealed class SyntaxLightupGenerator : ISourceGenerator |
| 21 | + { |
| 22 | + private enum NodeKind |
| 23 | + { |
| 24 | + Predefined, |
| 25 | + Abstract, |
| 26 | + Concrete, |
| 27 | + } |
| 28 | + |
| 29 | + public void Initialize(GeneratorInitializationContext context) |
| 30 | + { |
| 31 | + } |
| 32 | + |
| 33 | + public void Execute(GeneratorExecutionContext context) |
| 34 | + { |
| 35 | + var syntaxFile = context.AdditionalFiles.Single(x => Path.GetFileName(x.Path) == "Syntax.xml"); |
| 36 | + var syntaxText = syntaxFile.GetText(context.CancellationToken); |
| 37 | + if (syntaxText is null) |
| 38 | + { |
| 39 | + throw new InvalidOperationException("Failed to read Syntax.xml"); |
| 40 | + } |
| 41 | + |
| 42 | + var syntaxData = new SyntaxData(in context, XDocument.Parse(syntaxText.ToString())); |
| 43 | + this.GenerateSyntaxWrappers(in context, syntaxData); |
| 44 | + } |
| 45 | + |
| 46 | + private void GenerateSyntaxWrappers(in GeneratorExecutionContext context, SyntaxData syntaxData) |
| 47 | + { |
| 48 | + foreach (var node in syntaxData.Nodes) |
| 49 | + { |
| 50 | + if (node.WrapperName is not null) |
| 51 | + { |
| 52 | + this.GenerateSyntaxWrapper(in context, syntaxData, node); |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + private void GenerateSyntaxWrapper(in GeneratorExecutionContext context, SyntaxData syntaxData, NodeData nodeData) |
| 58 | + { |
| 59 | + var concreteBase = syntaxData.TryGetConcreteBase(nodeData)?.Name ?? nameof(SyntaxNode); |
| 60 | + |
| 61 | + var members = SyntaxFactory.List<MemberDeclarationSyntax>(); |
| 62 | + |
| 63 | + // internal const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax"; |
| 64 | + members = members.Add(SyntaxFactory.FieldDeclaration( |
| 65 | + attributeLists: default, |
| 66 | + modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.InternalKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)), |
| 67 | + declaration: SyntaxFactory.VariableDeclaration( |
| 68 | + type: SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)), |
| 69 | + variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( |
| 70 | + identifier: SyntaxFactory.Identifier("WrappedTypeName"), |
| 71 | + argumentList: null, |
| 72 | + initializer: SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("Microsoft.CodeAnalysis.CSharp.Syntax." + nodeData.Name)))))))); |
| 73 | + |
| 74 | + // private static readonly Type WrappedType; |
| 75 | + members = members.Add(SyntaxFactory.FieldDeclaration( |
| 76 | + attributeLists: default, |
| 77 | + modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), |
| 78 | + declaration: SyntaxFactory.VariableDeclaration( |
| 79 | + type: SyntaxFactory.IdentifierName("Type"), |
| 80 | + variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator("WrappedType"))))); |
| 81 | + |
| 82 | + // private readonly SyntaxNode node; |
| 83 | + members = members.Add(SyntaxFactory.FieldDeclaration( |
| 84 | + attributeLists: default, |
| 85 | + modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), |
| 86 | + declaration: SyntaxFactory.VariableDeclaration( |
| 87 | + type: SyntaxFactory.IdentifierName(concreteBase), |
| 88 | + variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator("node"))))); |
| 89 | + |
| 90 | + // private SyntaxNodeWrapper(SyntaxNode node) |
| 91 | + // { |
| 92 | + // this.node = node; |
| 93 | + // } |
| 94 | + members = members.Add(SyntaxFactory.ConstructorDeclaration( |
| 95 | + attributeLists: default, |
| 96 | + modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)), |
| 97 | + identifier: SyntaxFactory.Identifier(nodeData.WrapperName), |
| 98 | + parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( |
| 99 | + attributeLists: default, |
| 100 | + modifiers: default, |
| 101 | + type: SyntaxFactory.IdentifierName(concreteBase), |
| 102 | + identifier: SyntaxFactory.Identifier("node"), |
| 103 | + @default: null))), |
| 104 | + initializer: null, |
| 105 | + body: SyntaxFactory.Block( |
| 106 | + SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( |
| 107 | + SyntaxKind.SimpleAssignmentExpression, |
| 108 | + left: SyntaxFactory.MemberAccessExpression( |
| 109 | + SyntaxKind.SimpleMemberAccessExpression, |
| 110 | + expression: SyntaxFactory.ThisExpression(), |
| 111 | + name: SyntaxFactory.IdentifierName("node")), |
| 112 | + right: SyntaxFactory.IdentifierName("node")))))); |
| 113 | + |
| 114 | + // public SyntaxNode SyntaxNode => this.node; |
| 115 | + members = members.Add(SyntaxFactory.PropertyDeclaration( |
| 116 | + attributeLists: default, |
| 117 | + modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), |
| 118 | + type: SyntaxFactory.IdentifierName(concreteBase), |
| 119 | + explicitInterfaceSpecifier: null, |
| 120 | + identifier: SyntaxFactory.Identifier("SyntaxNode"), |
| 121 | + accessorList: null, |
| 122 | + expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.MemberAccessExpression( |
| 123 | + SyntaxKind.SimpleMemberAccessExpression, |
| 124 | + expression: SyntaxFactory.ThisExpression(), |
| 125 | + name: SyntaxFactory.IdentifierName("node"))), |
| 126 | + initializer: null, |
| 127 | + semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); |
| 128 | + |
| 129 | + var wrapperStruct = SyntaxFactory.StructDeclaration( |
| 130 | + attributeLists: default, |
| 131 | + modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.InternalKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword)), |
| 132 | + identifier: SyntaxFactory.Identifier(nodeData.WrapperName), |
| 133 | + typeParameterList: null, |
| 134 | + baseList: SyntaxFactory.BaseList(SyntaxFactory.SingletonSeparatedList<BaseTypeSyntax>( |
| 135 | + SyntaxFactory.SimpleBaseType(SyntaxFactory.GenericName( |
| 136 | + identifier: SyntaxFactory.Identifier("ISyntaxWrapper"), |
| 137 | + typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList<TypeSyntax>(SyntaxFactory.IdentifierName(concreteBase))))))), |
| 138 | + constraintClauses: default, |
| 139 | + members: members); |
| 140 | + var wrapperNamespace = SyntaxFactory.NamespaceDeclaration( |
| 141 | + name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"), |
| 142 | + externs: default, |
| 143 | + usings: SyntaxFactory.List<UsingDirectiveSyntax>() |
| 144 | + .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))) |
| 145 | + .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Immutable"))) |
| 146 | + .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))) |
| 147 | + .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis.CSharp"))) |
| 148 | + .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis.CSharp.Syntax"))), |
| 149 | + members: SyntaxFactory.SingletonList<MemberDeclarationSyntax>(wrapperStruct)); |
| 150 | + |
| 151 | + wrapperNamespace = wrapperNamespace |
| 152 | + .NormalizeWhitespace() |
| 153 | + .WithLeadingTrivia( |
| 154 | + SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."), |
| 155 | + SyntaxFactory.CarriageReturnLineFeed, |
| 156 | + SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."), |
| 157 | + SyntaxFactory.CarriageReturnLineFeed, |
| 158 | + SyntaxFactory.CarriageReturnLineFeed) |
| 159 | + .WithTrailingTrivia( |
| 160 | + SyntaxFactory.CarriageReturnLineFeed); |
| 161 | + |
| 162 | + context.AddSource(nodeData.WrapperName + ".g.cs", SourceText.From(wrapperNamespace.ToFullString(), Encoding.UTF8)); |
| 163 | + } |
| 164 | + |
| 165 | + private sealed class SyntaxData |
| 166 | + { |
| 167 | + private readonly Dictionary<string, NodeData> nameToNode; |
| 168 | + |
| 169 | + public SyntaxData(in GeneratorExecutionContext context, XDocument document) |
| 170 | + { |
| 171 | + var nodesBuilder = ImmutableArray.CreateBuilder<NodeData>(); |
| 172 | + foreach (var element in document.XPathSelectElement("/Tree[@Root='SyntaxNode']").XPathSelectElements("PredefinedNode|AbstractNode|Node")) |
| 173 | + { |
| 174 | + nodesBuilder.Add(new NodeData(in context, element)); |
| 175 | + } |
| 176 | + |
| 177 | + this.Nodes = nodesBuilder.ToImmutable(); |
| 178 | + this.nameToNode = this.Nodes.ToDictionary(node => node.Name); |
| 179 | + } |
| 180 | + |
| 181 | + public ImmutableArray<NodeData> Nodes { get; } |
| 182 | + |
| 183 | + public NodeData TryGetConcreteBase(NodeData node) |
| 184 | + { |
| 185 | + for (var current = this.TryGetNode(node.BaseName); current is not null; current = this.TryGetNode(current.BaseName)) |
| 186 | + { |
| 187 | + if (current.WrapperName is null) |
| 188 | + { |
| 189 | + // This is not a wrapper |
| 190 | + return current; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + return null; |
| 195 | + } |
| 196 | + |
| 197 | + private NodeData TryGetNode(string name) |
| 198 | + { |
| 199 | + this.nameToNode.TryGetValue(name, out var node); |
| 200 | + return node; |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + private sealed class NodeData |
| 205 | + { |
| 206 | + public NodeData(in GeneratorExecutionContext context, XElement element) |
| 207 | + { |
| 208 | + this.Kind = element.Name.LocalName switch |
| 209 | + { |
| 210 | + "PredefinedNode" => NodeKind.Predefined, |
| 211 | + "AbstractNode" => NodeKind.Abstract, |
| 212 | + "Node" => NodeKind.Concrete, |
| 213 | + _ => throw new NotSupportedException($"Unknown element name '{element.Name}'"), |
| 214 | + }; |
| 215 | + |
| 216 | + this.Name = element.Attribute("Name").Value; |
| 217 | + |
| 218 | + var existingType = context.Compilation.GetTypeByMetadataName($"Microsoft.CodeAnalysis.CSharp.Syntax.{this.Name}") |
| 219 | + ?? context.Compilation.GetTypeByMetadataName($"Microsoft.CodeAnalysis.CSharp.{this.Name}") |
| 220 | + ?? context.Compilation.GetTypeByMetadataName($"Microsoft.CodeAnalysis.{this.Name}"); |
| 221 | + if (existingType?.DeclaredAccessibility == Accessibility.Public) |
| 222 | + { |
| 223 | + this.WrapperName = null; |
| 224 | + } |
| 225 | + else |
| 226 | + { |
| 227 | + this.WrapperName = this.Name + "Wrapper"; |
| 228 | + } |
| 229 | + |
| 230 | + this.BaseName = element.Attribute("Base").Value; |
| 231 | + this.Fields = element.XPathSelectElements("Field").Select(field => new FieldData(field)).ToImmutableArray(); |
| 232 | + } |
| 233 | + |
| 234 | + public NodeKind Kind { get; } |
| 235 | + |
| 236 | + public string Name { get; } |
| 237 | + |
| 238 | + public string WrapperName { get; } |
| 239 | + |
| 240 | + public string BaseName { get; } |
| 241 | + |
| 242 | + public ImmutableArray<FieldData> Fields { get; } |
| 243 | + } |
| 244 | + |
| 245 | + private sealed class FieldData |
| 246 | + { |
| 247 | + public FieldData(XElement element) |
| 248 | + { |
| 249 | + this.Name = element.Attribute("Name").Value; |
| 250 | + this.Type = element.Attribute("Type").Value; |
| 251 | + this.IsOverride = element.Attribute("Override")?.Value == "true"; |
| 252 | + } |
| 253 | + |
| 254 | + public string Name { get; } |
| 255 | + |
| 256 | + public string Type { get; } |
| 257 | + |
| 258 | + public bool IsOverride { get; } |
| 259 | + } |
| 260 | + } |
| 261 | +} |
0 commit comments