From 7ee065b8b2e7fb290af45e88d7698223ee54a610 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 16:07:55 -0700 Subject: [PATCH 01/26] Rename GenerateVisitNullCheckedMethod ... to GenerateVisitNullCheckedOneArgumentMethod, in preparation for introducing the two-argument overload. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 99b73d2e..c4c9a2fc 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -75,7 +75,7 @@ internal string GenerateRewritingVisitor() .AddMembers( GenerateVisitMethod(), GenerateVisitActualMethod(), - GenerateVisitNullCheckedMethod()) + GenerateVisitNullCheckedOneArgumentMethod()) .AddMembers( GenerateVisitClassMethods()); @@ -216,7 +216,7 @@ private SwitchSectionSyntax[] GenerateVisitActualSwitchSections() return switchSections; } - private MethodDeclarationSyntax GenerateVisitNullCheckedMethod() + private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() { TypeSyntax typeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); From db326c7232d3c94d51c601b328cc09eb223aeb88 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 16:15:17 -0700 Subject: [PATCH 02/26] Introduce GenerateVisitNullCheckedTwoArgumentMethod For now, it's identical to the one-argument method. --- .../RewritingVisitorGenerator.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index c4c9a2fc..e0d93666 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -75,7 +75,8 @@ internal string GenerateRewritingVisitor() .AddMembers( GenerateVisitMethod(), GenerateVisitActualMethod(), - GenerateVisitNullCheckedOneArgumentMethod()) + GenerateVisitNullCheckedOneArgumentMethod(), + GenerateVisitNullCheckedTwoArgumentMethod()) .AddMembers( GenerateVisitClassMethods()); @@ -257,6 +258,46 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() SyntaxFactory.IdentifierName(NodeParameterName)))))))); } + private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() + { + TypeSyntax typeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); + + return SyntaxFactory.MethodDeclaration( + typeParameterType, + VisitNullCheckedMethodName) + .AddModifiers( + SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) + .AddTypeParameterListParameters( + SyntaxFactory.TypeParameter(TypeParameterName)) + .AddConstraintClauses( + SyntaxFactory.TypeParameterConstraintClause( + SyntaxFactory.IdentifierName(TypeParameterName), + SyntaxFactory.SeparatedList( + new TypeParameterConstraintSyntax[] + { + SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint), + SyntaxFactory.TypeConstraint( + SyntaxFactory.ParseTypeName(_nodeInterfaceName)) + }))) + .AddParameterListParameters( + SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) + .WithType(typeParameterType)) + .AddBodyStatements( + SyntaxFactory.IfStatement( + SyntaxHelper.IsNull(NodeParameterName), + SyntaxFactory.Block( + SyntaxFactory.ReturnStatement( + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), + SyntaxFactory.ReturnStatement( + SyntaxFactory.CastExpression( + typeParameterType, + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(VisitMethodName), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Argument( + SyntaxFactory.IdentifierName(NodeParameterName)))))))); + } private MemberDeclarationSyntax[] GenerateVisitClassMethods() { // There is one VisitXxx method for each generated class. From 84be68bcdcd7a38148ddfd9a1f5b55c19febf949 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 16:20:44 -0700 Subject: [PATCH 03/26] Add second parameter to two-parameter overload of VisitNullChecked. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index e0d93666..7d506a87 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -13,6 +13,7 @@ namespace Microsoft.Json.Schema.ToDotNet internal class RewritingVisitorGenerator { private const string NodeParameterName = "node"; + private const string KeyParameterName = "key"; private const string VisitMethodName = "Visit"; private const string VisitActualMethodName = "VisitActual"; private const string VisitNullCheckedMethodName = "VisitNullChecked"; @@ -261,6 +262,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() { TypeSyntax typeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); + TypeSyntax stringParameterType = SyntaxFactory.ParseTypeName("string"); return SyntaxFactory.MethodDeclaration( typeParameterType, @@ -281,7 +283,9 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() }))) .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) - .WithType(typeParameterType)) + .WithType(typeParameterType), + SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) + .WithType(stringParameterType)) .AddBodyStatements( SyntaxFactory.IfStatement( SyntaxHelper.IsNull(NodeParameterName), From 09b80a746a22acd79266f893d270f56b77c8acf9 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 16:23:25 -0700 Subject: [PATCH 04/26] Rename parameter type variables and hoist into class. --- .../RewritingVisitorGenerator.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 7d506a87..8c5c7bca 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -22,6 +22,9 @@ internal class RewritingVisitorGenerator private const string AddMethodName = "Add"; private const string ToArrayMethodName = "ToArray"; + private readonly TypeSyntax TypeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); + private readonly TypeSyntax StringParameterType = SyntaxFactory.ParseTypeName("string"); + private readonly Dictionary _classInfoDictionary; private readonly string _copyrightNotice; private readonly string _namespaceName; @@ -220,10 +223,8 @@ private SwitchSectionSyntax[] GenerateVisitActualSwitchSections() private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() { - TypeSyntax typeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); - return SyntaxFactory.MethodDeclaration( - typeParameterType, + TypeParameterType, VisitNullCheckedMethodName) .AddModifiers( SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) @@ -241,7 +242,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() }))) .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) - .WithType(typeParameterType)) + .WithType(TypeParameterType)) .AddBodyStatements( SyntaxFactory.IfStatement( SyntaxHelper.IsNull(NodeParameterName), @@ -250,7 +251,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), SyntaxFactory.ReturnStatement( SyntaxFactory.CastExpression( - typeParameterType, + TypeParameterType, SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName(VisitMethodName), SyntaxFactory.ArgumentList( @@ -261,11 +262,8 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() { - TypeSyntax typeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); - TypeSyntax stringParameterType = SyntaxFactory.ParseTypeName("string"); - return SyntaxFactory.MethodDeclaration( - typeParameterType, + TypeParameterType, VisitNullCheckedMethodName) .AddModifiers( SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) @@ -283,9 +281,9 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() }))) .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) - .WithType(typeParameterType), + .WithType(TypeParameterType), SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) - .WithType(stringParameterType)) + .WithType(StringParameterType)) .AddBodyStatements( SyntaxFactory.IfStatement( SyntaxHelper.IsNull(NodeParameterName), @@ -294,7 +292,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), SyntaxFactory.ReturnStatement( SyntaxFactory.CastExpression( - typeParameterType, + TypeParameterType, SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName(VisitMethodName), SyntaxFactory.ArgumentList( From d8c369fb40fcc60d905187d073af1e677ad3849c Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 16:27:45 -0700 Subject: [PATCH 05/26] Add ref keyword to second parameter. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 8c5c7bca..ee55920b 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -283,7 +283,8 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) .WithType(TypeParameterType), SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) - .WithType(StringParameterType)) + .WithType(StringParameterType) + .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.RefKeyword)))) .AddBodyStatements( SyntaxFactory.IfStatement( SyntaxHelper.IsNull(NodeParameterName), From 578cb87ff3e23df327cf37a1b0eaafee1df04c3b Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 16:29:16 -0700 Subject: [PATCH 06/26] Remove body from one-argument overload. --- .../RewritingVisitorGenerator.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index ee55920b..f8f59b06 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -243,21 +243,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) .WithType(TypeParameterType)) - .AddBodyStatements( - SyntaxFactory.IfStatement( - SyntaxHelper.IsNull(NodeParameterName), - SyntaxFactory.Block( - SyntaxFactory.ReturnStatement( - SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), - SyntaxFactory.ReturnStatement( - SyntaxFactory.CastExpression( - TypeParameterType, - SyntaxFactory.InvocationExpression( - SyntaxFactory.IdentifierName(VisitMethodName), - SyntaxFactory.ArgumentList( - SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.Argument( - SyntaxFactory.IdentifierName(NodeParameterName)))))))); + .AddBodyStatements(); } private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() From 6cc1333447492271a7eeca914731bf1acf51e12a Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:15:11 -0700 Subject: [PATCH 07/26] Declare emptyKey. --- .../RewritingVisitorGenerator.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index f8f59b06..29ea4a25 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -223,6 +223,8 @@ private SwitchSectionSyntax[] GenerateVisitActualSwitchSections() private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() { + const string EmptyKeyVariableName = "emptyKey"; + return SyntaxFactory.MethodDeclaration( TypeParameterType, VisitNullCheckedMethodName) @@ -243,7 +245,16 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) .WithType(TypeParameterType)) - .AddBodyStatements(); + .AddBodyStatements( + SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration( + StringParameterType, + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator( + SyntaxFactory.Identifier(EmptyKeyVariableName), + default(BracketedArgumentListSyntax), + SyntaxFactory.EqualsValueClause( + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))))))); } private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() From aa6e0aa3b0916c5996d601a3fdeff536c0f0992f Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:20:44 -0700 Subject: [PATCH 08/26] Add call to two-argument form. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 29ea4a25..1f76cdd3 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -246,6 +246,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) .WithType(TypeParameterType)) .AddBodyStatements( + // string emptyKey = null; SyntaxFactory.LocalDeclarationStatement( SyntaxFactory.VariableDeclaration( StringParameterType, @@ -254,7 +255,11 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() SyntaxFactory.Identifier(EmptyKeyVariableName), default(BracketedArgumentListSyntax), SyntaxFactory.EqualsValueClause( - SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))))))); + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))))), + // return VisitNullChecked(node, ref emptyKey) + SyntaxFactory.ReturnStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(VisitNullCheckedMethodName)))); } private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() From ee15cd2e5591e28c33b2db948164ffb74838ca25 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:32:30 -0700 Subject: [PATCH 09/26] Add generic type to call (not really needed) and arguments. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 1f76cdd3..199df958 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -259,7 +259,14 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() // return VisitNullChecked(node, ref emptyKey) SyntaxFactory.ReturnStatement( SyntaxFactory.InvocationExpression( - SyntaxFactory.IdentifierName(VisitNullCheckedMethodName)))); + SyntaxFactory.GenericName( + SyntaxFactory.Identifier(VisitNullCheckedMethodName), + SyntaxFactory.TypeArgumentList( + SyntaxFactory.SingletonSeparatedList(TypeParameterType))), + SyntaxHelper.ArgumentList( + SyntaxFactory.IdentifierName(NodeParameterName), + SyntaxFactory.RefExpression( + SyntaxFactory.IdentifierName(EmptyKeyVariableName)))))); } private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() From 2ea7a288e15ac7ab6ff89f17da398456554b5a64 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:35:28 -0700 Subject: [PATCH 10/26] Add second if statement --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 199df958..ae9d194f 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -300,6 +300,11 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() SyntaxFactory.Block( SyntaxFactory.ReturnStatement( SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), + SyntaxFactory.IfStatement( + SyntaxHelper.IsNull(KeyParameterName), + SyntaxFactory.Block( + SyntaxFactory.ReturnStatement( + SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), SyntaxFactory.ReturnStatement( SyntaxFactory.CastExpression( TypeParameterType, From 4fccf9ba8bf7c1b19d138110618cb28ae48c2b6a Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:42:40 -0700 Subject: [PATCH 11/26] Finish body of two-argument overload. --- .../RewritingVisitorGenerator.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index ae9d194f..46585a03 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -17,6 +17,7 @@ internal class RewritingVisitorGenerator private const string VisitMethodName = "Visit"; private const string VisitActualMethodName = "VisitActual"; private const string VisitNullCheckedMethodName = "VisitNullChecked"; + private const string VisitDictionaryEntryMethodName = "VisitDictionaryEntry"; private const string TypeParameterName = "T"; private const string CountPropertyName = "Count"; private const string AddMethodName = "Add"; @@ -304,17 +305,25 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() SyntaxHelper.IsNull(KeyParameterName), SyntaxFactory.Block( SyntaxFactory.ReturnStatement( - SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))), + SyntaxFactory.CastExpression( + TypeParameterType, + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(VisitMethodName), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Argument( + SyntaxFactory.IdentifierName(NodeParameterName))))))))), SyntaxFactory.ReturnStatement( SyntaxFactory.CastExpression( TypeParameterType, SyntaxFactory.InvocationExpression( - SyntaxFactory.IdentifierName(VisitMethodName), - SyntaxFactory.ArgumentList( - SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.Argument( - SyntaxFactory.IdentifierName(NodeParameterName)))))))); + SyntaxFactory.IdentifierName(VisitDictionaryEntryMethodName), + SyntaxHelper.ArgumentList( + SyntaxFactory.IdentifierName(NodeParameterName), + SyntaxFactory.RefExpression( + SyntaxFactory.IdentifierName(KeyParameterName))))))); } + private MemberDeclarationSyntax[] GenerateVisitClassMethods() { // There is one VisitXxx method for each generated class. From 8d303c9bbf072600d6941e2543799f71a52e06c3 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:50:34 -0700 Subject: [PATCH 12/26] Add empty VisitDictionaryEntryMethod --- .../RewritingVisitorGenerator.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 46585a03..17729adc 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -25,6 +25,7 @@ internal class RewritingVisitorGenerator private readonly TypeSyntax TypeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); private readonly TypeSyntax StringParameterType = SyntaxFactory.ParseTypeName("string"); + private readonly TypeSyntax NodeInterfaceType; private readonly Dictionary _classInfoDictionary; private readonly string _copyrightNotice; @@ -32,7 +33,6 @@ internal class RewritingVisitorGenerator private readonly string _className; private readonly string _schemaName; private readonly string _kindEnumName; - private readonly string _nodeInterfaceName; private readonly List _generatedClassNames; private readonly LocalVariableNameGenerator _localVariableNameGenerator; @@ -64,7 +64,7 @@ internal RewritingVisitorGenerator( _className = className; _schemaName = schemaName; _kindEnumName = kindEnumName; - _nodeInterfaceName = nodeInterfaceName; + NodeInterfaceType = SyntaxFactory.ParseTypeName(nodeInterfaceName); _generatedClassNames = generatedClassNames.OrderBy(gn => gn).ToList(); _localVariableNameGenerator = new LocalVariableNameGenerator(); @@ -81,7 +81,8 @@ internal string GenerateRewritingVisitor() GenerateVisitMethod(), GenerateVisitActualMethod(), GenerateVisitNullCheckedOneArgumentMethod(), - GenerateVisitNullCheckedTwoArgumentMethod()) + GenerateVisitNullCheckedTwoArgumentMethod(), + GenerateVisitDictionaryEntryMethod()) .AddMembers( GenerateVisitClassMethods()); @@ -111,8 +112,7 @@ private MemberDeclarationSyntax GenerateVisitMethod() .AddParameterListParameters( SyntaxFactory.Parameter( SyntaxFactory.Identifier(NodeParameterName)) - .WithType( - SyntaxFactory.ParseTypeName(_nodeInterfaceName))) + .WithType(NodeInterfaceType)) .AddBodyStatements( SyntaxFactory.ReturnStatement( SyntaxFactory.InvocationExpression( @@ -147,8 +147,7 @@ private MemberDeclarationSyntax GenerateVisitActualMethod() .AddParameterListParameters( SyntaxFactory.Parameter( SyntaxFactory.Identifier(NodeParameterName)) - .WithType( - SyntaxFactory.ParseTypeName(_nodeInterfaceName))) + .WithType(NodeInterfaceType)) .AddBodyStatements( SyntaxFactory.IfStatement( SyntaxHelper.IsNull(NodeParameterName), @@ -240,8 +239,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedOneArgumentMethod() new TypeParameterConstraintSyntax[] { SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint), - SyntaxFactory.TypeConstraint( - SyntaxFactory.ParseTypeName(_nodeInterfaceName)) + SyntaxFactory.TypeConstraint(NodeInterfaceType) }))) .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) @@ -286,8 +284,7 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() new TypeParameterConstraintSyntax[] { SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint), - SyntaxFactory.TypeConstraint( - SyntaxFactory.ParseTypeName(_nodeInterfaceName)) + SyntaxFactory.TypeConstraint(NodeInterfaceType) }))) .AddParameterListParameters( SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) @@ -324,6 +321,16 @@ private MethodDeclarationSyntax GenerateVisitNullCheckedTwoArgumentMethod() SyntaxFactory.IdentifierName(KeyParameterName))))))); } + private MethodDeclarationSyntax GenerateVisitDictionaryEntryMethod() + { + return SyntaxFactory.MethodDeclaration( + NodeInterfaceType, + VisitDictionaryEntryMethodName) + .AddModifiers( + SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) + .AddBodyStatements(); + } + private MemberDeclarationSyntax[] GenerateVisitClassMethods() { // There is one VisitXxx method for each generated class. From c9720ddb2cec4e4d8dd63fcbfc9608ea47b75388 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Fri, 21 Sep 2018 17:52:25 -0700 Subject: [PATCH 13/26] Add arguments to VisitDictionaryEntry. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 17729adc..87d52ca2 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -328,6 +328,12 @@ private MethodDeclarationSyntax GenerateVisitDictionaryEntryMethod() VisitDictionaryEntryMethodName) .AddModifiers( SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) + .AddParameterListParameters( + SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) + .WithType(NodeInterfaceType), + SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) + .WithType(StringParameterType) + .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.RefKeyword)))) .AddBodyStatements(); } From a0300ac4e325db79889eadc02e503bde63bc9ea5 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 11:46:38 -0700 Subject: [PATCH 14/26] Add null parameter checks to GenerateVisitDictionaryEntry. --- .../RewritingVisitorGenerator.cs | 4 ++- src/Json.Schema.ToDotNet/SyntaxHelper.cs | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 87d52ca2..089c6afa 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -334,7 +334,9 @@ private MethodDeclarationSyntax GenerateVisitDictionaryEntryMethod() SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) .WithType(StringParameterType) .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.RefKeyword)))) - .AddBodyStatements(); + .AddBodyStatements( + SyntaxHelper.NullParameterCheck(NodeParameterName), + SyntaxHelper.NullParameterCheck(KeyParameterName)); } private MemberDeclarationSyntax[] GenerateVisitClassMethods() diff --git a/src/Json.Schema.ToDotNet/SyntaxHelper.cs b/src/Json.Schema.ToDotNet/SyntaxHelper.cs index cd5f58ff..25475ac2 100644 --- a/src/Json.Schema.ToDotNet/SyntaxHelper.cs +++ b/src/Json.Schema.ToDotNet/SyntaxHelper.cs @@ -188,5 +188,37 @@ internal static TypeSyntax Var() { return SyntaxFactory.ParseTypeName("var"); } + + // Roslyn doesn't directly expose a "nameof expression". This article shows how + // to make one: + // https://stackoverflow.com/questions/46259039/constructing-nameof-expression-via-syntaxfactory-roslyn + internal static ExpressionSyntax NameofExpression(string symbol) + { + return SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName( + SyntaxFactory.Identifier( + leading: SyntaxFactory.TriviaList(), + contextualKind: SyntaxKind.NameOfKeyword, + text: "nameof", + valueText: "nameof", + trailing: SyntaxFactory.TriviaList())), + ArgumentList( + SyntaxFactory.IdentifierName(symbol))); + } + + private static readonly TypeSyntax ArgumentNullExceptionType = SyntaxFactory.ParseTypeName("ArgumentNullException"); + + internal static IfStatementSyntax NullParameterCheck(string parameterName) + { + return SyntaxFactory.IfStatement( + IsNull(parameterName), + SyntaxFactory.Block( + SyntaxFactory.ThrowStatement( + SyntaxFactory.ObjectCreationExpression( + ArgumentNullExceptionType, + ArgumentList( + NameofExpression(parameterName)), + default(InitializerExpressionSyntax))))); + } } } From 88dccd004cbf898d7ad8a96de0a3c5ae7912d43f Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 11:50:02 -0700 Subject: [PATCH 15/26] Unify null parameter checks. --- .../RewritingVisitorGenerator.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 089c6afa..6a43045c 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -149,19 +149,7 @@ private MemberDeclarationSyntax GenerateVisitActualMethod() SyntaxFactory.Identifier(NodeParameterName)) .WithType(NodeInterfaceType)) .AddBodyStatements( - SyntaxFactory.IfStatement( - SyntaxHelper.IsNull(NodeParameterName), - SyntaxFactory.Block( - SyntaxFactory.ThrowStatement( - SyntaxFactory.ObjectCreationExpression( - SyntaxFactory.ParseTypeName("ArgumentNullException"), - SyntaxFactory.ArgumentList( - SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.Argument( - SyntaxFactory.LiteralExpression( - SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(NodeParameterName))))), - default(InitializerExpressionSyntax))))), + SyntaxHelper.NullParameterCheck(NodeParameterName), SyntaxFactory.SwitchStatement( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, From 94adbe2db52f0bb71d81e4f4be020b0b0d839585 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 12:03:03 -0700 Subject: [PATCH 16/26] Introduce GenerateDictionaryEntrySwitchSections. --- .../RewritingVisitorGenerator.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 6a43045c..61ef8b39 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -324,7 +324,18 @@ private MethodDeclarationSyntax GenerateVisitDictionaryEntryMethod() .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.RefKeyword)))) .AddBodyStatements( SyntaxHelper.NullParameterCheck(NodeParameterName), - SyntaxHelper.NullParameterCheck(KeyParameterName)); + SyntaxHelper.NullParameterCheck(KeyParameterName), + SyntaxFactory.SwitchStatement( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NodeParameterName), + SyntaxFactory.IdentifierName(_kindEnumName))) + .AddSections(GenerateVisitDictionaryEntrySwitchSections())); + } + + private SwitchSectionSyntax[] GenerateVisitDictionaryEntrySwitchSections() + { + return new SwitchSectionSyntax[0]; } private MemberDeclarationSyntax[] GenerateVisitClassMethods() From 61aef84ff9125c2e74e3430bf717523c9bd039af Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 12:44:03 -0700 Subject: [PATCH 17/26] Generate switch sections in GenerateVisitDictionaryEntrySwitchSections. --- .../RewritingVisitorGenerator.cs | 39 ++++++++++++++++++- src/Json.Schema.ToDotNet/SyntaxHelper.cs | 28 ++++++++++--- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 61ef8b39..ffe71ead 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -35,6 +36,9 @@ internal class RewritingVisitorGenerator private readonly string _kindEnumName; private readonly List _generatedClassNames; + // The names of classes that occur in dictionary entries. + private readonly List _dictionaryEntryClassNames; + private readonly LocalVariableNameGenerator _localVariableNameGenerator; /// @@ -66,6 +70,7 @@ internal RewritingVisitorGenerator( _kindEnumName = kindEnumName; NodeInterfaceType = SyntaxFactory.ParseTypeName(nodeInterfaceName); _generatedClassNames = generatedClassNames.OrderBy(gn => gn).ToList(); + _dictionaryEntryClassNames = new List { "FileData", "Graph", "LogicalLocation", "Rule" }; _localVariableNameGenerator = new LocalVariableNameGenerator(); } @@ -335,7 +340,39 @@ private MethodDeclarationSyntax GenerateVisitDictionaryEntryMethod() private SwitchSectionSyntax[] GenerateVisitDictionaryEntrySwitchSections() { - return new SwitchSectionSyntax[0]; + // There is one switch section for each class that can occur in a dictionary, plus one for the default. + var switchSections = new SwitchSectionSyntax[_dictionaryEntryClassNames.Count + 1]; + + int index = 0; + foreach (string className in _dictionaryEntryClassNames) + { + string methodName = MakeVisitDictionaryEntryMethodName(className); + switchSections[index++] = SyntaxFactory.SwitchSection( + SyntaxFactory.SingletonList( + SyntaxFactory.CaseSwitchLabel( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(_kindEnumName), + SyntaxFactory.IdentifierName(className)))), + SyntaxFactory.SingletonList( + SyntaxFactory.ReturnStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(methodName))))); + } + + switchSections[index] = SyntaxFactory.SwitchSection( + SyntaxFactory.SingletonList( + SyntaxFactory.DefaultSwitchLabel()), + SyntaxFactory.SingletonList( + SyntaxFactory.ThrowStatement( + SyntaxHelper.NewInvalidOperationException()))); + + return switchSections; + } + + private string MakeVisitDictionaryEntryMethodName(string className) + { + return "Visit" + className + "DictionaryEntry"; } private MemberDeclarationSyntax[] GenerateVisitClassMethods() diff --git a/src/Json.Schema.ToDotNet/SyntaxHelper.cs b/src/Json.Schema.ToDotNet/SyntaxHelper.cs index 25475ac2..fa56d553 100644 --- a/src/Json.Schema.ToDotNet/SyntaxHelper.cs +++ b/src/Json.Schema.ToDotNet/SyntaxHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -206,7 +207,26 @@ internal static ExpressionSyntax NameofExpression(string symbol) SyntaxFactory.IdentifierName(symbol))); } - private static readonly TypeSyntax ArgumentNullExceptionType = SyntaxFactory.ParseTypeName("ArgumentNullException"); + private static readonly TypeSyntax ArgumentNullExceptionType = SyntaxFactory.ParseTypeName(nameof(ArgumentNullException)); + + internal static ObjectCreationExpressionSyntax NewArgumentNullException(string parameterName) + { + return SyntaxFactory.ObjectCreationExpression( + ArgumentNullExceptionType, + ArgumentList( + NameofExpression(parameterName)), + default(InitializerExpressionSyntax)); + } + + private static readonly TypeSyntax InvalidOperationExceptionType = SyntaxFactory.ParseTypeName(nameof(InvalidOperationException)); + + internal static ObjectCreationExpressionSyntax NewInvalidOperationException() + { + return SyntaxFactory.ObjectCreationExpression( + InvalidOperationExceptionType, + ArgumentList(), + default(InitializerExpressionSyntax)); + } internal static IfStatementSyntax NullParameterCheck(string parameterName) { @@ -214,11 +234,7 @@ internal static IfStatementSyntax NullParameterCheck(string parameterName) IsNull(parameterName), SyntaxFactory.Block( SyntaxFactory.ThrowStatement( - SyntaxFactory.ObjectCreationExpression( - ArgumentNullExceptionType, - ArgumentList( - NameofExpression(parameterName)), - default(InitializerExpressionSyntax))))); + NewArgumentNullException(parameterName)))); } } } From 30d6b5a69e134385ed18677562dc150ecc43856b Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 12:48:27 -0700 Subject: [PATCH 18/26] Add parameters to calls in GenerateVisitDictionaryEntrySwitchSections. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index ffe71ead..02fd75d4 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -357,7 +357,13 @@ private SwitchSectionSyntax[] GenerateVisitDictionaryEntrySwitchSections() SyntaxFactory.SingletonList( SyntaxFactory.ReturnStatement( SyntaxFactory.InvocationExpression( - SyntaxFactory.IdentifierName(methodName))))); + SyntaxFactory.IdentifierName(methodName), + SyntaxHelper.ArgumentList( + SyntaxFactory.CastExpression( + SyntaxFactory.ParseTypeName(className), + SyntaxFactory.IdentifierName(NodeParameterName)), + SyntaxFactory.RefExpression( + SyntaxFactory.IdentifierName(KeyParameterName))))))); } switchSections[index] = SyntaxFactory.SwitchSection( From 297ec435161309ead9d2d054ea1ddd704cae2ff1 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 13:02:41 -0700 Subject: [PATCH 19/26] Generate empty type dictionary entry visitor methods. --- .../RewritingVisitorGenerator.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 02fd75d4..5549d600 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -88,6 +88,8 @@ internal string GenerateRewritingVisitor() GenerateVisitNullCheckedOneArgumentMethod(), GenerateVisitNullCheckedTwoArgumentMethod(), GenerateVisitDictionaryEntryMethod()) + .AddMembers( + GenerateVisitTypedDictionaryEntryMethods()) .AddMembers( GenerateVisitClassMethods()); @@ -380,6 +382,37 @@ private string MakeVisitDictionaryEntryMethodName(string className) { return "Visit" + className + "DictionaryEntry"; } + private MemberDeclarationSyntax[] GenerateVisitTypedDictionaryEntryMethods() + { + // There is one method for each class that can occur in a dictionary. + var methodDeclarations = new MemberDeclarationSyntax[_dictionaryEntryClassNames.Count]; + + int index = 0; + foreach (string className in _dictionaryEntryClassNames) + { + methodDeclarations[index++] = GenerateVisitTypedDictionaryEntryMethods(className); + } + + return methodDeclarations; + } + + private MemberDeclarationSyntax GenerateVisitTypedDictionaryEntryMethods(string className) + { + string methodName = MakeVisitDictionaryEntryMethodName(className); + TypeSyntax generatedClassType = SyntaxFactory.ParseTypeName(className); + + return SyntaxFactory.MethodDeclaration(generatedClassType, methodName) + .AddModifiers( + SyntaxFactory.Token(SyntaxKind.PublicKeyword), + SyntaxFactory.Token(SyntaxKind.VirtualKeyword)) + .AddParameterListParameters( + SyntaxFactory.Parameter(SyntaxFactory.Identifier(NodeParameterName)) + .WithType(generatedClassType), + SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) + .WithType(StringParameterType) + .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.RefKeyword)))) + .AddBodyStatements(); + } private MemberDeclarationSyntax[] GenerateVisitClassMethods() { From 72ce442ce4b4b433f3075abe516a59bc9fbc7074 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sat, 22 Sep 2018 13:08:18 -0700 Subject: [PATCH 20/26] Generate typed dictionary entry visitor method bodies. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 5549d600..3583a6ff 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -411,7 +411,14 @@ private MemberDeclarationSyntax GenerateVisitTypedDictionaryEntryMethods(string SyntaxFactory.Parameter(SyntaxFactory.Identifier(KeyParameterName)) .WithType(StringParameterType) .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.RefKeyword)))) - .AddBodyStatements(); + .AddBodyStatements( + SyntaxFactory.ReturnStatement( + SyntaxFactory.CastExpression( + generatedClassType, + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(VisitMethodName), + SyntaxHelper.ArgumentList( + SyntaxFactory.IdentifierName(NodeParameterName)))))); } private MemberDeclarationSyntax[] GenerateVisitClassMethods() From 3b7cd93e6835b19c594a2141b0cdb4eb34830397 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sun, 23 Sep 2018 16:02:39 -0700 Subject: [PATCH 21/26] Add dictionary entry checking logic to class visitors. --- .../RewritingVisitorGenerator.cs | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 3583a6ff..2db93c54 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -15,6 +15,10 @@ internal class RewritingVisitorGenerator { private const string NodeParameterName = "node"; private const string KeyParameterName = "key"; + private const string KeyVariableName = "key"; + private const string KeysVariableName = "keys"; + private const string KeysPropertyName = "Keys"; + private const string ValueVariableName = "value"; private const string VisitMethodName = "Visit"; private const string VisitActualMethodName = "VisitActual"; private const string VisitNullCheckedMethodName = "VisitNullChecked"; @@ -22,6 +26,7 @@ internal class RewritingVisitorGenerator private const string TypeParameterName = "T"; private const string CountPropertyName = "Count"; private const string AddMethodName = "Add"; + private const string RemoveMethodName = "Remove"; private const string ToArrayMethodName = "ToArray"; private readonly TypeSyntax TypeParameterType = SyntaxFactory.ParseTypeName(TypeParameterName); @@ -590,11 +595,6 @@ private StatementSyntax GenerateScalarVisit( private StatementSyntax[] GenerateDictionaryVisit(int arrayRank, string propertyName) { - const string KeyVariableName = "key"; - const string KeysVariableName = "keys"; - const string KeysPropertyName = "Keys"; - const string ValueVariableName = "value"; - ExpressionSyntax dictionaryValue = SyntaxFactory.ElementAccessExpression( SyntaxFactory.MemberAccessExpression( @@ -612,10 +612,7 @@ private StatementSyntax[] GenerateDictionaryVisit(int arrayRank, string property if (arrayRank == 0) { - dictionaryElementVisitStatements = new StatementSyntax[] - { - GenerateScalarVisit(dictionaryValue, SyntaxFactory.IdentifierName(ValueVariableName)) - }; + dictionaryElementVisitStatements = GenerateDictionaryElementVisit(propertyName); } else { @@ -680,6 +677,67 @@ private StatementSyntax[] GenerateDictionaryVisit(int arrayRank, string property }; } + private StatementSyntax[] GenerateDictionaryElementVisit(string propertyName) + { + const string NewKeyVariableName = "newKey"; + + return new StatementSyntax[] + { + // string newKey = key; + SyntaxFactory.LocalDeclarationStatement( + SyntaxFactory.VariableDeclaration( + StringParameterType, + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator( + SyntaxFactory.Identifier(NewKeyVariableName), + default(BracketedArgumentListSyntax), + SyntaxFactory.EqualsValueClause( + SyntaxFactory.IdentifierName(KeyVariableName)))))), + // node.Property.Remove(key); + SyntaxFactory.ExpressionStatement( + SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NodeParameterName), + SyntaxFactory.IdentifierName(propertyName)), + SyntaxFactory.IdentifierName(RemoveMethodName)), + SyntaxHelper.ArgumentList( + SyntaxFactory.IdentifierName(KeyVariableName)))), + // value = VisitNullChecked(value, ref newKey); + SyntaxFactory.ExpressionStatement( + SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.IdentifierName(ValueVariableName), + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(VisitNullCheckedMethodName), + SyntaxHelper.ArgumentList( + SyntaxFactory.IdentifierName(ValueVariableName), + SyntaxFactory.RefExpression( + SyntaxFactory.IdentifierName(NewKeyVariableName)))))), + // if (newKey != null) + SyntaxFactory.IfStatement( + SyntaxHelper.IsNotNull(NewKeyVariableName), + SyntaxFactory.Block( + SyntaxFactory.ExpressionStatement( + // node.Property[newKey] = VisitNullChecked(value); + SyntaxFactory.AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + SyntaxFactory.ElementAccessExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(NodeParameterName), + SyntaxFactory.IdentifierName(propertyName)), + SyntaxHelper.BracketedArgumentList( + SyntaxFactory.IdentifierName(NewKeyVariableName))), + SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(VisitNullCheckedMethodName), + SyntaxHelper.ArgumentList( + SyntaxFactory.IdentifierName(ValueVariableName))))))) + }; + } + private StatementSyntax[] GenerateArrayVisit( int arrayRank, int nestingLevel, From 3a325fef826ef794d2d0262576a8aaa20ed5e9f1 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sun, 23 Sep 2018 16:50:58 -0700 Subject: [PATCH 22/26] Infer list of classes that occur as dictionary entries. --- .../RewritingVisitorGenerator.cs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 2db93c54..6bd6f29b 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -42,7 +42,7 @@ internal class RewritingVisitorGenerator private readonly List _generatedClassNames; // The names of classes that occur in dictionary entries. - private readonly List _dictionaryEntryClassNames; + private readonly IList _dictionaryEntryClassNames; private readonly LocalVariableNameGenerator _localVariableNameGenerator; @@ -75,11 +75,60 @@ internal RewritingVisitorGenerator( _kindEnumName = kindEnumName; NodeInterfaceType = SyntaxFactory.ParseTypeName(nodeInterfaceName); _generatedClassNames = generatedClassNames.OrderBy(gn => gn).ToList(); - _dictionaryEntryClassNames = new List { "FileData", "Graph", "LogicalLocation", "Rule" }; + _dictionaryEntryClassNames = GetDictionaryEntryClassNames(classInfoDictionary); _localVariableNameGenerator = new LocalVariableNameGenerator(); } + /// + /// Gets the list of schema-defined classes that appear as dictionary entries. + /// + /// + /// The property dictionary for a class contains one entry describing each property. + /// For those properties that are dictionaries, the property dictionary also + /// contains an entry that describes the dictionary _entry_. For example, suppose + /// a class named Run contains a property named Graphs, defined as a dictionary + /// from string to Graph. Then the class dictionary for the Run class contains + /// one entry with the key "Graphs", which describes the property Run.Graphs. + /// It contains another entry with the key "Graphs{}", which describes the + /// entries in the Run.Graphs dictionary, and the PropertyInfo indexed by + /// "Graphs{}" tells us that the dictionary entry is of the schema-defined + /// type Graph. The string "{}" is called the "dictionary marker". + /// + /// Our goal is to locate dictionary-valued properties whose dictionary + /// entries are of schema-defined types. + /// + IList GetDictionaryEntryClassNames(Dictionary classInfoDictionary) + { + var dictionaryEntryClassNames = new HashSet(); + + // Loop over all properties of all classes defined in the schema. + foreach (string className in classInfoDictionary.Keys) + { + PropertyInfoDictionary propertyInfoDictionary = classInfoDictionary[className]; + foreach (string key in propertyInfoDictionary.Keys) + { + int dictionaryMarkerIndex = key.IndexOf(PropertyInfoDictionary.DictionaryMarker); + if (dictionaryMarkerIndex != -1) + { + // We've found a property info dictionary entry that describes + // an entry in a dictionary-valued property defined by the schema. + PropertyInfo dictionaryEntryInfo = propertyInfoDictionary[key]; + { + if (dictionaryEntryInfo.IsOfSchemaDefinedType) + { + // The dictionary entries for this property are of schema-defined type! + // Remember this type so we can generate code to descend into the dictionary entries. + dictionaryEntryClassNames.Add(dictionaryEntryInfo.TypeName); + } + } + } + } + } + + return dictionaryEntryClassNames.OrderBy(name => name).ToList(); + } + internal string GenerateRewritingVisitor() { ClassDeclarationSyntax visitorClassDeclaration = From ca3a5db791fcbd6041f1a028ccc93a2c587a248c Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sun, 23 Sep 2018 17:06:38 -0700 Subject: [PATCH 23/26] Update unit tests. --- .../DataModelGeneratorTests.cs | 91 +++++++++++++++++-- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs b/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs index 9a702e0b..a26265a4 100644 --- a/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs +++ b/src/Json.Schema.ToDotNet.UnitTests/DataModelGeneratorTests.cs @@ -1419,7 +1419,7 @@ public virtual object VisitActual(ISNode node) { if (node == null) { - throw new ArgumentNullException(""node""); + throw new ArgumentNullException(nameof(node)); } switch (node.SNodeKind) @@ -1434,13 +1434,50 @@ public virtual object VisitActual(ISNode node) } private T VisitNullChecked(T node) where T : class, ISNode + { + string emptyKey = null; + return VisitNullChecked(node, ref emptyKey); + } + + private T VisitNullChecked(T node, ref string key) where T : class, ISNode { if (node == null) { return null; } - return (T)Visit(node); + if (key == null) + { + return (T)Visit(node); + } + + return (T)VisitDictionaryEntry(node, ref key); + } + + private ISNode VisitDictionaryEntry(ISNode node, ref string key) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + switch (node.SNodeKind) + { + case SNodeKind.D: + return VisitDDictionaryEntry((D)node, ref key); + default: + throw new InvalidOperationException(); + } + } + + public virtual D VisitDDictionaryEntry(D node, ref string key) + { + return (D)Visit(node); } public virtual C VisitC(C node) @@ -1479,7 +1516,13 @@ public virtual C VisitC(C node) var value = node.DictionaryWithObjectSchemaProp[key]; if (value != null) { - node.DictionaryWithObjectSchemaProp[key] = VisitNullChecked(value); + string newKey = key; + node.DictionaryWithObjectSchemaProp.Remove(key); + value = VisitNullChecked(value, ref newKey); + if (newKey != null) + { + node.DictionaryWithObjectSchemaProp[newKey] = VisitNullChecked(value); + } } } } @@ -1508,7 +1551,13 @@ public virtual C VisitC(C node) var value = node.DictionaryWithUriKeyProp[key]; if (value != null) { - node.DictionaryWithUriKeyProp[key] = VisitNullChecked(value); + string newKey = key; + node.DictionaryWithUriKeyProp.Remove(key); + value = VisitNullChecked(value, ref newKey); + if (newKey != null) + { + node.DictionaryWithUriKeyProp[newKey] = VisitNullChecked(value); + } } } } @@ -2675,7 +2724,7 @@ public virtual object VisitActual(ISNode node) { if (node == null) { - throw new ArgumentNullException(""node""); + throw new ArgumentNullException(nameof(node)); } switch (node.SNodeKind) @@ -2688,13 +2737,43 @@ public virtual object VisitActual(ISNode node) } private T VisitNullChecked(T node) where T : class, ISNode + { + string emptyKey = null; + return VisitNullChecked(node, ref emptyKey); + } + + private T VisitNullChecked(T node, ref string key) where T : class, ISNode { if (node == null) { return null; } - return (T)Visit(node); + if (key == null) + { + return (T)Visit(node); + } + + return (T)VisitDictionaryEntry(node, ref key); + } + + private ISNode VisitDictionaryEntry(ISNode node, ref string key) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + switch (node.SNodeKind) + { + default: + throw new InvalidOperationException(); + } } public virtual C VisitC(C node) From c7c213653ca1b52faaabae95c9c02b6322655034 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sun, 23 Sep 2018 17:19:32 -0700 Subject: [PATCH 24/26] Add a comment to SyntaxHelper. --- src/Json.Schema.ToDotNet/SyntaxHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Json.Schema.ToDotNet/SyntaxHelper.cs b/src/Json.Schema.ToDotNet/SyntaxHelper.cs index fa56d553..f5765803 100644 --- a/src/Json.Schema.ToDotNet/SyntaxHelper.cs +++ b/src/Json.Schema.ToDotNet/SyntaxHelper.cs @@ -12,6 +12,10 @@ namespace Microsoft.Json.Schema.ToDotNet { + /// + /// The methods in this class encapsulate or simplify the generation of certain + /// code patterns that The ToDotNet code generator emits. + /// internal static class SyntaxHelper { private const string DocCommentSummaryFormat = From 79fddb8c94ad54cefbba390a26f0eb8d782186c2 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sun, 23 Sep 2018 17:26:08 -0700 Subject: [PATCH 25/26] Add comments to the new methods in SyntaxHelper. --- src/Json.Schema.ToDotNet/SyntaxHelper.cs | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Json.Schema.ToDotNet/SyntaxHelper.cs b/src/Json.Schema.ToDotNet/SyntaxHelper.cs index f5765803..32a3ed0a 100644 --- a/src/Json.Schema.ToDotNet/SyntaxHelper.cs +++ b/src/Json.Schema.ToDotNet/SyntaxHelper.cs @@ -194,9 +194,17 @@ internal static TypeSyntax Var() return SyntaxFactory.ParseTypeName("var"); } - // Roslyn doesn't directly expose a "nameof expression". This article shows how - // to make one: + /// + /// Generates the code nameof(symbol). + /// + /// + /// The argument to the nameof operator. + /// + /// + /// Roslyn doesn't directly expose a "nameof expression". This article shows how + /// to make one: // https://stackoverflow.com/questions/46259039/constructing-nameof-expression-via-syntaxfactory-roslyn + /// internal static ExpressionSyntax NameofExpression(string symbol) { return SyntaxFactory.InvocationExpression( @@ -213,6 +221,15 @@ internal static ExpressionSyntax NameofExpression(string symbol) private static readonly TypeSyntax ArgumentNullExceptionType = SyntaxFactory.ParseTypeName(nameof(ArgumentNullException)); + /// + /// Generates the code new ArgumentNullException(nameof(parameter)). + /// + /// + /// The name of the parameter that was null. + /// + /// + /// The generated code. + /// internal static ObjectCreationExpressionSyntax NewArgumentNullException(string parameterName) { return SyntaxFactory.ObjectCreationExpression( @@ -224,6 +241,9 @@ internal static ObjectCreationExpressionSyntax NewArgumentNullException(string p private static readonly TypeSyntax InvalidOperationExceptionType = SyntaxFactory.ParseTypeName(nameof(InvalidOperationException)); + /// + /// Generates the code new InvalidOperationException(). + /// internal static ObjectCreationExpressionSyntax NewInvalidOperationException() { return SyntaxFactory.ObjectCreationExpression( @@ -232,6 +252,15 @@ internal static ObjectCreationExpressionSyntax NewInvalidOperationException() default(InitializerExpressionSyntax)); } + /// + /// Generates the code if (parameter == null) { throw new ArgumentNullException(nameof(parameter)); }. + /// + /// + /// The name of the parameter begin null-checked. + /// + /// + /// The generated code. + /// internal static IfStatementSyntax NullParameterCheck(string parameterName) { return SyntaxFactory.IfStatement( From 21e1e76e2bb196f0524c6e71aa9092cb4d6e1f56 Mon Sep 17 00:00:00 2001 From: Larry Golding Date: Sun, 23 Sep 2018 17:34:13 -0700 Subject: [PATCH 26/26] Fix a mistake in a comment. --- src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs index 6bd6f29b..c119224d 100644 --- a/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs +++ b/src/Json.Schema.ToDotNet/RewritingVisitorGenerator.cs @@ -88,7 +88,7 @@ internal RewritingVisitorGenerator( /// For those properties that are dictionaries, the property dictionary also /// contains an entry that describes the dictionary _entry_. For example, suppose /// a class named Run contains a property named Graphs, defined as a dictionary - /// from string to Graph. Then the class dictionary for the Run class contains + /// from string to Graph. Then the property dictionary for the Run class contains /// one entry with the key "Graphs", which describes the property Run.Graphs. /// It contains another entry with the key "Graphs{}", which describes the /// entries in the Run.Graphs dictionary, and the PropertyInfo indexed by