From b837cd167094f36aff7735cb12a131eb4844971d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20Pi=C4=85tek?= Date: Mon, 8 May 2023 17:32:34 +0200 Subject: [PATCH 1/4] Handle new line escaping in C# mapping code generator --- .../Serialization/MappingConverter.cs | 20 +++++++++++++------ ...eMockAdminApi_GetMappingsCode.verified.txt | 7 ++++++- .../WireMockAdminApiTests.cs | 6 +++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 21858eb9f..2aa663e78 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -150,12 +150,12 @@ public string ToCSharpCode(IMapping mapping, MappingConverterSettings? settings { case BodyType.String: case BodyType.FormUrlEncoded: - sb.AppendLine($" .WithBody(\"{EscapeCSharpString(bodyData.BodyAsString)}\")"); + sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyData.BodyAsString)})"); break; case BodyType.Json: if (bodyData.BodyAsJson is string bodyStringValue) { - sb.AppendLine($" .WithBody(\"{EscapeCSharpString(bodyStringValue)}\")"); + sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyStringValue)})"); } else { @@ -406,7 +406,7 @@ public MappingModel ToMappingModel(IMapping mapping) private static string GetString(IStringMatcher stringMatcher) { - return stringMatcher.GetPatterns().Select(p => $"\"{p.GetPattern()}\"").First(); + return stringMatcher.GetPatterns().Select(p => ToCSharpStringLiteral(p.GetPattern())).First(); } private static string[] GetStringArray(IReadOnlyList stringMatchers) @@ -459,10 +459,18 @@ private static string To1Or2Arguments(MatchOperator? matchOperator, string[]? va private static string ToValueArguments(string[]? values, string defaultValue = "") { - return values is { } ? string.Join(", ", values.Select(v => $"\"{EscapeCSharpString(v)}\"")) : $"\"{EscapeCSharpString(defaultValue)}\""; + return values is { } ? string.Join(", ", values.Select(ToCSharpStringLiteral)) : ToCSharpStringLiteral(defaultValue); } - private static string? EscapeCSharpString(string? value) => value?.Replace("\"", "\\\""); + private static string ToCSharpStringLiteral(string? value) + { + var escapedValue = value?.Replace("\"", "\\\"") ?? string.Empty; + if (escapedValue.Contains("\n")) + { + return $"@\"{escapedValue}\""; + } + return $"\"{escapedValue}\""; + } private static WebProxyModel? MapWebProxy(WebProxySettings? settings) { @@ -505,7 +513,7 @@ private static string ConvertJsonToAnonymousObjectDefinition(JToken token, int i JTokenType.None => "null", JTokenType.Integer => jValue.Value?.ToString() ?? "null", JTokenType.Float => jValue.Value?.ToString() ?? "null", - JTokenType.String => $"\"{EscapeCSharpString(jValue.Value?.ToString())}\"", + JTokenType.String => ToCSharpStringLiteral(jValue.Value?.ToString()), JTokenType.Boolean => jValue.Value?.ToString()?.ToLower() ?? "null", JTokenType.Null => "null", JTokenType.Undefined => "null", diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt index 50764a68a..6f7490c5a 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt @@ -59,7 +59,12 @@ server b = 3 } } - } + }, + multiline_text = @"This +is +multiline +text +" }) ); diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs index d8be86e40..8cbafd399 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs @@ -762,7 +762,11 @@ public async Task IWireMockAdminApi_GetMappingsCode() .RespondWith( Response.Create() .WithStatusCode(HttpStatusCode.AlreadyReported) - .WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}} }) + .WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}}, multiline_text= @"This +is +multiline +text +" }) ); // Act From ff58e421ea7ca93fe14abc3d60d3991594e453a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20Pi=C4=85tek?= Date: Mon, 8 May 2023 18:03:05 +0200 Subject: [PATCH 2/4] Prevent date conversion when value persisted as string --- src/WireMock.Net/Serialization/MappingConverter.cs | 11 ++++++++--- ...sts.IWireMockAdminApi_GetMappingsCode.verified.txt | 2 ++ test/WireMock.Net.Tests/WireMockAdminApiTests.cs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 2aa663e78..0592f5988 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Text; @@ -157,10 +158,13 @@ public string ToCSharpCode(IMapping mapping, MappingConverterSettings? settings { sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyStringValue)})"); } - else + else if(bodyData.BodyAsJson is {} jsonBody) { - var serializedBody = JsonConvert.SerializeObject(bodyData.BodyAsJson); - var deserializedBody = JToken.Parse(serializedBody); + var serializedBody = JsonConvert.SerializeObject(jsonBody); + using var jsonReader = new JsonTextReader(new StringReader(serializedBody)); + jsonReader.DateParseHandling = DateParseHandling.None; + var deserializedBody = JObject.Load(jsonReader); + sb.AppendLine($" .WithBodyAsJson({ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2)})"); } @@ -517,6 +521,7 @@ private static string ConvertJsonToAnonymousObjectDefinition(JToken token, int i JTokenType.Boolean => jValue.Value?.ToString()?.ToLower() ?? "null", JTokenType.Null => "null", JTokenType.Undefined => "null", + JTokenType.Date when jValue.Value is DateTime dateValue => $"DateTime.Parse({ToCSharpStringLiteral(dateValue.ToString("s"))})", _ => $"UNHANDLED_CASE: {jValue.Type}" }, _ => $"UNHANDLED_CASE: {token}" diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt index 6f7490c5a..30db12190 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt @@ -60,6 +60,8 @@ server } } }, + date_field = "2023-05-08T11:20:19", + string_field_with_date = "2021-03-13T21:04:00Z", multiline_text = @"This is multiline diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs index 8cbafd399..e437e9518 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs @@ -762,7 +762,7 @@ public async Task IWireMockAdminApi_GetMappingsCode() .RespondWith( Response.Create() .WithStatusCode(HttpStatusCode.AlreadyReported) - .WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}}, multiline_text= @"This + .WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}}, date_field = new DateTime(2023,05,08,11,20,19), string_field_with_date="2021-03-13T21:04:00Z", multiline_text= @"This is multiline text From 2593431b1fed884a4af88665d1b65d0f88d9b24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20Pi=C4=85tek?= Date: Thu, 11 May 2023 21:38:05 +0200 Subject: [PATCH 3/4] Handle object properties named as csharp keywords --- .../Serialization/MappingConverter.cs | 88 ++++++++++++++++++- ...eMockAdminApi_GetMappingsCode.verified.txt | 2 +- .../WireMockAdminApiTests.cs | 2 +- 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 0592f5988..d601c00a1 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -511,7 +511,7 @@ private static string ConvertJsonToAnonymousObjectDefinition(JToken token, int i { JArray jArray => FormatArray(jArray, ind), JObject jObject => FormatObject(jObject, ind), - JProperty jProperty => $"{jProperty.Name} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}", + JProperty jProperty => $"{FormatPropertyName(jProperty.Name)} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}", JValue jValue => jValue.Type switch { JTokenType.None => "null", @@ -528,6 +528,11 @@ private static string ConvertJsonToAnonymousObjectDefinition(JToken token, int i }; } + private static string FormatPropertyName(string propertyName) + { + return csharp_keywords.Contains(propertyName) ? "@" + propertyName : propertyName; + } + private static string FormatObject(JObject jObject, int ind) { var indStr = new string(' ', 4 * ind); @@ -550,4 +555,85 @@ private static string FormatArray(JArray jArray, int ind) return $"new [] {{ {string.Join(", ", items)} }}"; } + + private static readonly HashSet csharp_keywords = new HashSet(new[] + { + "abstract", + "as", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "foreach", + "goto", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "virtual", + "void", + "volatile", + "while" + }); } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt index 30db12190..777f41cd3 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.IWireMockAdminApi_GetMappingsCode.verified.txt @@ -36,7 +36,7 @@ server .WithStatusCode(208) .WithBodyAsJson(new { - a = 1, + @as = 1, b = 1.2, d = true, e = false, diff --git a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs index e437e9518..8aeeb7cc0 100644 --- a/test/WireMock.Net.Tests/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/WireMockAdminApiTests.cs @@ -762,7 +762,7 @@ public async Task IWireMockAdminApi_GetMappingsCode() .RespondWith( Response.Create() .WithStatusCode(HttpStatusCode.AlreadyReported) - .WithBodyAsJson(new { a = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}}, date_field = new DateTime(2023,05,08,11,20,19), string_field_with_date="2021-03-13T21:04:00Z", multiline_text= @"This + .WithBodyAsJson(new { @as = 1, b=1.2, d=true, e=false, f=new[]{1,2,3,4}, g= new{z1=1, z2=2, z3=new []{"a","b","c"}, z4=new[]{new {a=1, b=2},new {a=2, b=3}}}, date_field = new DateTime(2023,05,08,11,20,19), string_field_with_date="2021-03-13T21:04:00Z", multiline_text= @"This is multiline text From 3da300042d36639b99a7319d41cce51a8e3e5b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20Pi=C4=85tek?= Date: Fri, 12 May 2023 19:48:59 +0200 Subject: [PATCH 4/4] Refactor: Extract logic responsible for generating anonymous object definition to a separate class --- .../Serialization/MappingConverter.cs | 151 +--------------- src/WireMock.Net/Util/CSharpFormatter.cs | 167 ++++++++++++++++++ 2 files changed, 170 insertions(+), 148 deletions(-) create mode 100644 src/WireMock.Net/Util/CSharpFormatter.cs diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index d601c00a1..aa45caf5b 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Constants; @@ -20,6 +18,7 @@ using WireMock.Types; using WireMock.Util; +using static WireMock.Util.CSharpFormatter; namespace WireMock.Serialization; internal class MappingConverter @@ -160,12 +159,8 @@ public string ToCSharpCode(IMapping mapping, MappingConverterSettings? settings } else if(bodyData.BodyAsJson is {} jsonBody) { - var serializedBody = JsonConvert.SerializeObject(jsonBody); - using var jsonReader = new JsonTextReader(new StringReader(serializedBody)); - jsonReader.DateParseHandling = DateParseHandling.None; - var deserializedBody = JObject.Load(jsonReader); - - sb.AppendLine($" .WithBodyAsJson({ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2)})"); + var anonymousObjectDefinition = ConvertToAnonymousObjectDefinition(jsonBody); + sb.AppendLine($" .WithBodyAsJson({anonymousObjectDefinition})"); } break; @@ -466,16 +461,6 @@ private static string ToValueArguments(string[]? values, string defaultValue = " return values is { } ? string.Join(", ", values.Select(ToCSharpStringLiteral)) : ToCSharpStringLiteral(defaultValue); } - private static string ToCSharpStringLiteral(string? value) - { - var escapedValue = value?.Replace("\"", "\\\"") ?? string.Empty; - if (escapedValue.Contains("\n")) - { - return $"@\"{escapedValue}\""; - } - return $"\"{escapedValue}\""; - } - private static WebProxyModel? MapWebProxy(WebProxySettings? settings) { return settings != null ? new WebProxyModel @@ -505,135 +490,5 @@ private static IDictionary MapHeaders(IDictionary FormatArray(jArray, ind), - JObject jObject => FormatObject(jObject, ind), - JProperty jProperty => $"{FormatPropertyName(jProperty.Name)} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}", - JValue jValue => jValue.Type switch - { - JTokenType.None => "null", - JTokenType.Integer => jValue.Value?.ToString() ?? "null", - JTokenType.Float => jValue.Value?.ToString() ?? "null", - JTokenType.String => ToCSharpStringLiteral(jValue.Value?.ToString()), - JTokenType.Boolean => jValue.Value?.ToString()?.ToLower() ?? "null", - JTokenType.Null => "null", - JTokenType.Undefined => "null", - JTokenType.Date when jValue.Value is DateTime dateValue => $"DateTime.Parse({ToCSharpStringLiteral(dateValue.ToString("s"))})", - _ => $"UNHANDLED_CASE: {jValue.Type}" - }, - _ => $"UNHANDLED_CASE: {token}" - }; - } - - private static string FormatPropertyName(string propertyName) - { - return csharp_keywords.Contains(propertyName) ? "@" + propertyName : propertyName; - } - - private static string FormatObject(JObject jObject, int ind) - { - var indStr = new string(' ', 4 * ind); - var indStrSub = new string(' ', 4 * (ind + 1)); - var items = jObject.Properties().Select(x => ConvertJsonToAnonymousObjectDefinition(x, ind + 1)); - return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}"; - } - - private static string FormatArray(JArray jArray, int ind) - { - var hasComplexItems = jArray.FirstOrDefault() is JObject or JArray; - var items = jArray.Select(x => ConvertJsonToAnonymousObjectDefinition(x, hasComplexItems ? ind + 1 : ind)); - if (hasComplexItems) - { - var indStr = new string(' ', 4 * ind); - var indStrSub = new string(' ', 4 * (ind + 1)); - return $"new []\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}"; - } - - return $"new [] {{ {string.Join(", ", items)} }}"; - } - - private static readonly HashSet csharp_keywords = new HashSet(new[] - { - "abstract", - "as", - "base", - "bool", - "break", - "byte", - "case", - "catch", - "char", - "checked", - "class", - "const", - "continue", - "decimal", - "default", - "delegate", - "do", - "double", - "else", - "enum", - "event", - "explicit", - "extern", - "false", - "finally", - "fixed", - "float", - "for", - "foreach", - "goto", - "if", - "implicit", - "in", - "int", - "interface", - "internal", - "is", - "lock", - "long", - "namespace", - "new", - "null", - "object", - "operator", - "out", - "override", - "params", - "private", - "protected", - "public", - "readonly", - "ref", - "return", - "sbyte", - "sealed", - "short", - "sizeof", - "stackalloc", - "static", - "string", - "struct", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "uint", - "ulong", - "unchecked", - "unsafe", - "ushort", - "using", - "virtual", - "void", - "volatile", - "while" - }); } \ No newline at end of file diff --git a/src/WireMock.Net/Util/CSharpFormatter.cs b/src/WireMock.Net/Util/CSharpFormatter.cs new file mode 100644 index 000000000..483bc4095 --- /dev/null +++ b/src/WireMock.Net/Util/CSharpFormatter.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace WireMock.Util; + +internal static class CSharpFormatter +{ + public static object ConvertToAnonymousObjectDefinition(object jsonBody) + { + var serializedBody = JsonConvert.SerializeObject(jsonBody); + using var jsonReader = new JsonTextReader(new StringReader(serializedBody)); + jsonReader.DateParseHandling = DateParseHandling.None; + var deserializedBody = JObject.Load(jsonReader); + + var anonymousObjectDefinition = ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2); + return anonymousObjectDefinition; + } + + private static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0) + { + return token switch + { + JArray jArray => FormatArray(jArray, ind), + JObject jObject => FormatObject(jObject, ind), + JProperty jProperty => + $"{FormatPropertyName(jProperty.Name)} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}", + JValue jValue => jValue.Type switch + { + JTokenType.None => "null", + JTokenType.Integer => jValue.Value?.ToString() ?? "null", + JTokenType.Float => jValue.Value?.ToString() ?? "null", + JTokenType.String => ToCSharpStringLiteral(jValue.Value?.ToString()), + JTokenType.Boolean => jValue.Value?.ToString()?.ToLower() ?? "null", + JTokenType.Null => "null", + JTokenType.Undefined => "null", + JTokenType.Date when jValue.Value is DateTime dateValue => + $"DateTime.Parse({ToCSharpStringLiteral(dateValue.ToString("s"))})", + _ => $"UNHANDLED_CASE: {jValue.Type}" + }, + _ => $"UNHANDLED_CASE: {token}" + }; + } + + public static string ToCSharpStringLiteral(string? value) + { + var escapedValue = value?.Replace("\"", "\\\"") ?? string.Empty; + if (escapedValue.Contains("\n")) + { + return $"@\"{escapedValue}\""; + } + + return $"\"{escapedValue}\""; + } + + private static string FormatPropertyName(string propertyName) + { + return CsharpKeywords.Contains(propertyName) ? "@" + propertyName : propertyName; + } + + private static string FormatObject(JObject jObject, int ind) + { + var indStr = new string(' ', 4 * ind); + var indStrSub = new string(' ', 4 * (ind + 1)); + var items = jObject.Properties().Select(x => ConvertJsonToAnonymousObjectDefinition(x, ind + 1)); + + return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}"; + } + + private static string FormatArray(JArray jArray, int ind) + { + var hasComplexItems = jArray.FirstOrDefault() is JObject or JArray; + var items = jArray.Select(x => ConvertJsonToAnonymousObjectDefinition(x, hasComplexItems ? ind + 1 : ind)); + if (hasComplexItems) + { + var indStr = new string(' ', 4 * ind); + var indStrSub = new string(' ', 4 * (ind + 1)); + return $"new []\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}"; + } + + return $"new [] {{ {string.Join(", ", items)} }}"; + } + + private static readonly HashSet CsharpKeywords = new HashSet(new[] + { + "abstract", + "as", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "foreach", + "goto", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "virtual", + "void", + "volatile", + "while" + }); +} \ No newline at end of file