Skip to content

Commit 1d35607

Browse files
committed
Generate OperationKindEx
1 parent 26ee217 commit 1d35607

File tree

6 files changed

+393
-28
lines changed

6 files changed

+393
-28
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeGeneration/OperationLightupGenerator.cs

Lines changed: 167 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.CodeGeneration
77
using System.Collections.Generic;
88
using System.Collections.Immutable;
99
using System.Collections.ObjectModel;
10+
using System.Globalization;
1011
using System.IO;
1112
using System.Linq;
1213
using System.Text;
@@ -52,6 +53,7 @@ private void GenerateOperationInterfaces(in GeneratorExecutionContext context, X
5253
}
5354

5455
this.GenerateOperationWrapperHelper(in context, documentData.Interfaces.Values.ToImmutableArray());
56+
this.GenerateOperationKindEx(in context, documentData.Interfaces.Values.ToImmutableArray());
5557
}
5658

5759
private void GenerateOperationInterface(in GeneratorExecutionContext context, InterfaceData node)
@@ -760,10 +762,65 @@ private void GenerateOperationWrapperHelper(in GeneratorExecutionContext context
760762
context.AddSource("OperationWrapperHelper.g.cs", SourceText.From(wrapperNamespace.ToFullString(), Encoding.UTF8));
761763
}
762764

765+
private void GenerateOperationKindEx(in GeneratorExecutionContext context, ImmutableArray<InterfaceData> wrapperTypes)
766+
{
767+
var operationKinds = wrapperTypes
768+
.SelectMany(type => type.OperationKinds)
769+
.OrderBy(kind => kind.value)
770+
.ToImmutableArray();
771+
772+
var members = SyntaxFactory.List<MemberDeclarationSyntax>();
773+
foreach (var operationKind in operationKinds)
774+
{
775+
// public const OperationKind FieldReference = (OperationKind)26;
776+
members = members.Add(SyntaxFactory.FieldDeclaration(
777+
attributeLists: default,
778+
modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)),
779+
declaration: SyntaxFactory.VariableDeclaration(
780+
type: SyntaxFactory.IdentifierName("OperationKind"),
781+
variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(
782+
identifier: SyntaxFactory.Identifier(operationKind.name),
783+
argumentList: null,
784+
initializer: SyntaxFactory.EqualsValueClause(SyntaxFactory.CastExpression(
785+
type: SyntaxFactory.IdentifierName("OperationKind"),
786+
expression: SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal($"0x{operationKind.value:x}", operationKind.value)))))))));
787+
}
788+
789+
var operationKindExClass = SyntaxFactory.ClassDeclaration(
790+
attributeLists: default,
791+
modifiers: SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.InternalKeyword)).Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)),
792+
identifier: SyntaxFactory.Identifier("OperationKindEx"),
793+
typeParameterList: null,
794+
baseList: null,
795+
constraintClauses: default,
796+
members: members);
797+
var wrapperNamespace = SyntaxFactory.NamespaceDeclaration(
798+
name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"),
799+
externs: default,
800+
usings: SyntaxFactory.List<UsingDirectiveSyntax>()
801+
.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))),
802+
members: SyntaxFactory.SingletonList<MemberDeclarationSyntax>(operationKindExClass));
803+
804+
wrapperNamespace = wrapperNamespace
805+
.NormalizeWhitespace()
806+
.WithLeadingTrivia(
807+
SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."),
808+
SyntaxFactory.CarriageReturnLineFeed,
809+
SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."),
810+
SyntaxFactory.CarriageReturnLineFeed,
811+
SyntaxFactory.CarriageReturnLineFeed)
812+
.WithTrailingTrivia(
813+
SyntaxFactory.CarriageReturnLineFeed);
814+
815+
context.AddSource("OperationKindEx.g.cs", SourceText.From(wrapperNamespace.ToFullString(), Encoding.UTF8));
816+
}
817+
763818
private sealed class DocumentData
764819
{
765820
public DocumentData(XDocument document)
766821
{
822+
var operationKinds = GetOperationKinds(document);
823+
767824
var interfaces = new Dictionary<string, InterfaceData>();
768825
foreach (var node in document.XPathSelectElements("/Tree/AbstractNode"))
769826
{
@@ -772,7 +829,12 @@ public DocumentData(XDocument document)
772829
continue;
773830
}
774831

775-
var interfaceData = new InterfaceData(this, node);
832+
if (!operationKinds.TryGetValue(node.Attribute("Name").Value, out var kinds))
833+
{
834+
kinds = ImmutableArray<(string name, int value, string extraDescription)>.Empty;
835+
}
836+
837+
var interfaceData = new InterfaceData(this, node, kinds);
776838
interfaces.Add(interfaceData.InterfaceName, interfaceData);
777839
}
778840

@@ -783,33 +845,135 @@ public DocumentData(XDocument document)
783845
continue;
784846
}
785847

786-
var interfaceData = new InterfaceData(this, node);
848+
if (!operationKinds.TryGetValue(node.Attribute("Name").Value, out var kinds))
849+
{
850+
kinds = ImmutableArray<(string name, int value, string extraDescription)>.Empty;
851+
}
852+
853+
var interfaceData = new InterfaceData(this, node, kinds);
787854
interfaces.Add(interfaceData.InterfaceName, interfaceData);
788855
}
789856

790857
this.Interfaces = new ReadOnlyDictionary<string, InterfaceData>(interfaces);
791858
}
792859

793860
public ReadOnlyDictionary<string, InterfaceData> Interfaces { get; }
861+
862+
private static ImmutableDictionary<string, ImmutableArray<(string name, int value, string extraDescription)>> GetOperationKinds(XDocument document)
863+
{
864+
var skippedOperationKinds = GetSkippedOperationKinds(document);
865+
866+
var builder = ImmutableDictionary.CreateBuilder<string, ImmutableArray<(string name, int value, string extraDescription)>>();
867+
868+
int operationKind = 0;
869+
foreach (var node in document.XPathSelectElements("/Tree/AbstractNode|/Tree/Node"))
870+
{
871+
if (node.Attribute("Internal")?.Value == "true")
872+
{
873+
continue;
874+
}
875+
876+
if (node.XPathSelectElement("OperationKind") is { } explicitKind)
877+
{
878+
if (node.Name == "AbstractNode" && explicitKind.Attribute("Include")?.Value != "true")
879+
{
880+
continue;
881+
}
882+
else if (explicitKind.Attribute("Include")?.Value == "false")
883+
{
884+
// The node is explicitly excluded
885+
continue;
886+
}
887+
else if (explicitKind.XPathSelectElements("Entry").Any())
888+
{
889+
var nodeBuilder = ImmutableArray.CreateBuilder<(string name, int value, string extraDescription)>();
890+
foreach (var entry in explicitKind.XPathSelectElements("Entry"))
891+
{
892+
if (entry.Attribute("EditorBrowsable")?.Value == "false")
893+
{
894+
// Skip code generation for this operation kind
895+
continue;
896+
}
897+
898+
int parsedValue = ParsePrefixHexValue(entry.Attribute("Value").Value);
899+
nodeBuilder.Add((entry.Attribute("Name").Value, parsedValue, entry.Attribute("ExtraDescription")?.Value));
900+
}
901+
902+
builder.Add(node.Attribute("Name").Value, nodeBuilder.ToImmutable());
903+
continue;
904+
}
905+
}
906+
else if (node.Name == "AbstractNode")
907+
{
908+
// Abstract nodes without explicit Include="true" are skipped
909+
continue;
910+
}
911+
912+
// Implicit operation kind
913+
operationKind++;
914+
while (skippedOperationKinds.Contains(operationKind))
915+
{
916+
operationKind++;
917+
}
918+
919+
var nodeName = node.Attribute("Name").Value;
920+
var kindName = nodeName.Substring("I".Length, nodeName.Length - "I".Length - "Operation".Length);
921+
builder.Add(nodeName, ImmutableArray.Create((kindName, operationKind, (string)null)));
922+
}
923+
924+
return builder.ToImmutable();
925+
}
926+
927+
private static ImmutableHashSet<int> GetSkippedOperationKinds(XDocument document)
928+
{
929+
var builder = ImmutableHashSet.CreateBuilder<int>();
930+
foreach (var skippedKind in document.XPathSelectElements("/Tree/UnusedOperationKinds/Entry"))
931+
{
932+
builder.Add(ParsePrefixHexValue(skippedKind.Attribute("Value").Value));
933+
}
934+
935+
foreach (var explicitKind in document.XPathSelectElements("/Tree/*/OperationKind/Entry"))
936+
{
937+
builder.Add(ParsePrefixHexValue(explicitKind.Attribute("Value").Value));
938+
}
939+
940+
return builder.ToImmutable();
941+
}
942+
943+
private static int ParsePrefixHexValue(string value)
944+
{
945+
if (!value.StartsWith("0x"))
946+
{
947+
throw new InvalidOperationException($"Unexpected number format: '{value}'");
948+
}
949+
950+
return int.Parse(value.Substring("0x".Length), NumberStyles.AllowHexSpecifier);
951+
}
794952
}
795953

796954
private sealed class InterfaceData
797955
{
798956
private readonly DocumentData documentData;
799957

800-
public InterfaceData(DocumentData documentData, XElement node)
958+
public InterfaceData(DocumentData documentData, XElement node, ImmutableArray<(string name, int value, string extraDescription)> operationKinds)
801959
{
802960
this.documentData = documentData;
803961

962+
this.OperationKinds = operationKinds;
804963
this.InterfaceName = node.Attribute("Name").Value;
964+
this.Name = this.InterfaceName.Substring("I".Length, this.InterfaceName.Length - "I".Length - "Operation".Length);
805965
this.WrapperName = this.InterfaceName + "Wrapper";
806966
this.BaseInterfaceName = node.Attribute("Base").Value;
807967
this.IsAbstract = node.Name == "AbstractNode";
808968
this.Properties = node.XPathSelectElements("Property").Select(property => new PropertyData(property)).ToImmutableArray();
809969
}
810970

971+
public ImmutableArray<(string name, int value, string extraDescription)> OperationKinds { get; }
972+
811973
public string InterfaceName { get; }
812974

975+
public string Name { get; }
976+
813977
public string WrapperName { get; }
814978

815979
public string BaseInterfaceName { get; }
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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.Test.CSharp7.Lightup
5+
{
6+
using System.Collections.Generic;
7+
using System.Reflection;
8+
using Microsoft.CodeAnalysis;
9+
using StyleCop.Analyzers.Lightup;
10+
using Xunit;
11+
12+
public class OperationKindExTests
13+
{
14+
private static readonly Dictionary<OperationKind, string> OperationKindToName;
15+
private static readonly Dictionary<string, OperationKind> NameToOperationKind;
16+
17+
static OperationKindExTests()
18+
{
19+
var renamedOperations =
20+
new Dictionary<string, string>()
21+
{
22+
{ "BinaryOperator", "Binary" },
23+
{ "ConstructorBodyOperation", "ConstructorBody" },
24+
{ "MethodBodyOperation", "MethodBody" },
25+
{ "TupleBinaryOperator", "TupleBinary" },
26+
{ "UnaryOperator", "Unary" },
27+
};
28+
29+
OperationKindToName = new Dictionary<OperationKind, string>();
30+
NameToOperationKind = new Dictionary<string, OperationKind>();
31+
32+
foreach (var field in typeof(OperationKind).GetTypeInfo().DeclaredFields)
33+
{
34+
if (!field.IsStatic)
35+
{
36+
continue;
37+
}
38+
39+
var value = (OperationKind)field.GetRawConstantValue();
40+
var name = field.Name;
41+
if (renamedOperations.TryGetValue(name, out var newName))
42+
{
43+
name = newName;
44+
}
45+
46+
if (!OperationKindToName.ContainsKey(value))
47+
{
48+
OperationKindToName[value] = name;
49+
}
50+
51+
if (!NameToOperationKind.ContainsKey(name))
52+
{
53+
NameToOperationKind.Add(name, value);
54+
}
55+
}
56+
}
57+
58+
public static IEnumerable<object[]> OperationKinds
59+
{
60+
get
61+
{
62+
foreach (var field in typeof(OperationKindEx).GetTypeInfo().DeclaredFields)
63+
{
64+
yield return new object[] { field.Name, (OperationKind)field.GetRawConstantValue() };
65+
}
66+
}
67+
}
68+
69+
[Theory]
70+
[MemberData(nameof(OperationKinds))]
71+
public void TestOperationKind(string name, OperationKind operationKind)
72+
{
73+
if (OperationKindToName.TryGetValue(operationKind, out var expectedName))
74+
{
75+
Assert.Equal(expectedName, name);
76+
}
77+
else
78+
{
79+
Assert.False(NameToOperationKind.TryGetValue(name, out _));
80+
}
81+
}
82+
}
83+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.Test.CSharp8.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.CSharp7.Lightup;
7+
8+
public class OperationKindExTestsCSharp8 : OperationKindExTests
9+
{
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.Test.CSharp9.Lightup
5+
{
6+
using StyleCop.Analyzers.Test.CSharp8.Lightup;
7+
8+
public class OperationKindExTestsCSharp9 : OperationKindExTestsCSharp8
9+
{
10+
}
11+
}

0 commit comments

Comments
 (0)