From cf2fb763d4acea0392160f0d8f0dd665dc2d4ba9 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Wed, 26 Aug 2020 08:54:06 -0400 Subject: [PATCH 01/50] Add Ace text editor for formatting/highlighting This makes editing the C# text much easier, especially when it's complicated. --- src/Quoter.Web/wwwroot/index.html | 5 ++--- src/Quoter.Web/wwwroot/scripts.js | 12 ++++++++++-- src/Quoter.Web/wwwroot/styles.css | 2 -- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Quoter.Web/wwwroot/index.html b/src/Quoter.Web/wwwroot/index.html index 863208c..1b7d8af 100644 --- a/src/Quoter.Web/wwwroot/index.html +++ b/src/Quoter.Web/wwwroot/index.html @@ -3,6 +3,7 @@ Roslyn Quoter + @@ -17,9 +18,7 @@

Open-source at https://github.com/KirillOsenkov/RoslynQuoter

-
- -
+
-
-CompilationUnit() +
CompilationUnit() .WithMembers( SingletonList<MemberDeclarationSyntax>( ClassDeclaration("C"))) .NormalizeWhitespace() -
+
Fork me on GitHub diff --git a/src/Quoter.Web/wwwroot/scripts.js b/src/Quoter.Web/wwwroot/scripts.js index 33a7b6e..fe84e49 100644 --- a/src/Quoter.Web/wwwroot/scripts.js +++ b/src/Quoter.Web/wwwroot/scripts.js @@ -1,4 +1,5 @@ var editor; +var resultDisplay; function onPageLoad() { ace.config.set('basePath', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/'); @@ -7,6 +8,16 @@ function onPageLoad() { editor.setTheme("ace/theme/textmate"); editor.setKeyboardHandler("ace/keyboard/vscode"); editor.session.setMode("ace/mode/csharp"); + + resultDisplay = ace.edit("outputBox", + { + minLines: 5, + maxLines: 500 + }); + resultDisplay.setReadOnly(true); + resultDisplay.setTheme("ace/theme/textmate"); + resultDisplay.setKeyboardHandler("ace/keyboard/vscode"); + resultDisplay.session.setMode("ace/mode/csharp"); } function generateQuery() { @@ -94,12 +105,12 @@ function enableSubmit(enabled) { } function setResult(data) { - var container = document.getElementById("outputDiv"); - if (container) { - container.innerHTML = data; - } + resultDisplay.setValue(data); } function loadResults(data) { - setResult(data); + // The return value is XML encoded, decode special chars + var doc = new DOMParser().parseFromString(data, "text/html"); + + setResult(doc.documentElement.textContent); } \ No newline at end of file diff --git a/src/Quoter.Web/wwwroot/styles.css b/src/Quoter.Web/wwwroot/styles.css index ae30810..0c1a92f 100644 --- a/src/Quoter.Web/wwwroot/styles.css +++ b/src/Quoter.Web/wwwroot/styles.css @@ -32,9 +32,5 @@ } #outputDiv { - height: 70%; - padding: 30px; - padding-top: 0px; - white-space: pre-wrap; - font-family: Consolas, 'Courier New', monospace; + padding: 30px 0; } \ No newline at end of file From d29e8f01ecafa80866161c93c08b7e2e00232098 Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 27 Sep 2020 19:22:29 -0700 Subject: [PATCH 03/50] Update to Roslyn 3.8.0-3.final. --- src/Quoter.Tests/Quoter.Tests.csproj | 3 +-- src/Quoter.Web/Quoter.Web.csproj | 10 +--------- src/Quoter/Quoter.csproj | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Quoter.Tests/Quoter.Tests.csproj b/src/Quoter.Tests/Quoter.Tests.csproj index a84d69e..152f9ec 100644 --- a/src/Quoter.Tests/Quoter.Tests.csproj +++ b/src/Quoter.Tests/Quoter.Tests.csproj @@ -2,11 +2,10 @@ Quoter.Tests - net461 + net472 - all diff --git a/src/Quoter.Web/Quoter.Web.csproj b/src/Quoter.Web/Quoter.Web.csproj index c3e3896..f373ff6 100644 --- a/src/Quoter.Web/Quoter.Web.csproj +++ b/src/Quoter.Web/Quoter.Web.csproj @@ -1,18 +1,10 @@ - netcoreapp2.2 + netcoreapp3.1 113425b6-d4c0-42c1-bd99-694335fdfa16 - - - - - - - - diff --git a/src/Quoter/Quoter.csproj b/src/Quoter/Quoter.csproj index 1c2bbf0..484e42e 100644 --- a/src/Quoter/Quoter.csproj +++ b/src/Quoter/Quoter.csproj @@ -24,7 +24,7 @@ - + \ No newline at end of file From bbbf687bfbb77c8a36bffa699be395ac44b0fa0f Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 27 Sep 2020 19:27:05 -0700 Subject: [PATCH 04/50] Use the Preview language version. Ignore IsNint, IsNotNull, IsNuint properties on nodes. --- src/Quoter/Quoter.cs | 63 +++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 23f4a23..1a173d7 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -115,7 +115,7 @@ namespace RoslynQuoter switch (nodeKind) { case NodeKind.CompilationUnit: - return SyntaxFactory.ParseCompilationUnit(sourceText); + return SyntaxFactory.ParseCompilationUnit(sourceText, options: new CSharpParseOptions(LanguageVersion.Preview)); case NodeKind.MemberDeclaration: return SyntaxFactory.ParseMemberDeclaration(sourceText); case NodeKind.Statement: @@ -1774,35 +1774,38 @@ namespace RoslynQuoter /// private static readonly string[] nonStructuralProperties = { - "AllowsAnyExpression", - "Arity", - "ContainsAnnotations", - "ContainsDiagnostics", - "ContainsDirectives", - "ContainsSkippedText", - "DirectiveNameToken", - "FullSpan", - "HasLeadingTrivia", - "HasTrailingTrivia", - "HasStructuredTrivia", - "HasStructure", - "IsConst", - "IsDirective", - "IsElastic", - "IsFixed", - "IsMissing", - "IsStructuredTrivia", - "IsUnboundGenericName", - "IsUnmanaged", - "IsVar", - "Kind", - "Language", - "Parent", - "ParentTrivia", - "PlainName", - "Span", - "SyntaxTree", - }; + "AllowsAnyExpression", + "Arity", + "ContainsAnnotations", + "ContainsDiagnostics", + "ContainsDirectives", + "ContainsSkippedText", + "DirectiveNameToken", + "FullSpan", + "HasLeadingTrivia", + "HasStructure", + "HasStructuredTrivia", + "HasTrailingTrivia", + "IsConst", + "IsDirective", + "IsElastic", + "IsFixed", + "IsMissing", + "IsNint", + "IsNotNull", + "IsNuint", + "IsStructuredTrivia", + "IsUnboundGenericName", + "IsUnmanaged", + "IsVar", + "Kind", + "Language", + "Parent", + "ParentTrivia", + "PlainName", + "Span", + "SyntaxTree", + }; /// /// "Stringly typed" representation of a C# property or method invocation expression, with a From ae781ba8473411ba26243ddcf7ee36cb6973931b Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 27 Sep 2020 19:27:12 -0700 Subject: [PATCH 05/50] Remove unneeded app.config. --- src/Quoter/app.config | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 src/Quoter/app.config diff --git a/src/Quoter/app.config b/src/Quoter/app.config deleted file mode 100644 index 0759eac..0000000 --- a/src/Quoter/app.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - From 05f6fedc9b81491def72b578972b4a5442bd2556 Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 27 Sep 2020 19:30:52 -0700 Subject: [PATCH 06/50] Fix website warnings. --- src/Quoter.Web/Startup.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Quoter.Web/Startup.cs b/src/Quoter.Web/Startup.cs index bcc0bf1..d98204a 100644 --- a/src/Quoter.Web/Startup.cs +++ b/src/Quoter.Web/Startup.cs @@ -23,19 +23,17 @@ namespace QuoterWeb // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc(o => + { + o.EnableEndpointRouting = false; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - app.UseMvc(); - app.UseFileServer(); + app.UseFileServer(); } } } From 6cdcb39b2a46b4b937a733532c88f7d028d4f28a Mon Sep 17 00:00:00 2001 From: "Alexey I.m2strng4dtwrld" Date: Sat, 10 Oct 2020 13:04:18 +0300 Subject: [PATCH 07/50] feat: add JsonStringEnumConverter --- src/Quoter.Web/Quoter.Web.csproj | 4 ++++ src/Quoter.Web/Startup.cs | 17 ++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Quoter.Web/Quoter.Web.csproj b/src/Quoter.Web/Quoter.Web.csproj index f373ff6..d7aa6f2 100644 --- a/src/Quoter.Web/Quoter.Web.csproj +++ b/src/Quoter.Web/Quoter.Web.csproj @@ -5,6 +5,10 @@ 113425b6-d4c0-42c1-bd99-694335fdfa16 + + + + diff --git a/src/Quoter.Web/Startup.cs b/src/Quoter.Web/Startup.cs index d98204a..c9f9099 100644 --- a/src/Quoter.Web/Startup.cs +++ b/src/Quoter.Web/Startup.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace QuoterWeb { @@ -24,9 +19,13 @@ namespace QuoterWeb public void ConfigureServices(IServiceCollection services) { services.AddMvc(o => - { - o.EnableEndpointRouting = false; - }); + { + o.EnableEndpointRouting = false; + }) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 93993ef090f7a0daaef2172fc069efb8a5f7ae48 Mon Sep 17 00:00:00 2001 From: "Alexey I.m2strng4dtwrld" Date: Sat, 10 Oct 2020 13:04:46 +0300 Subject: [PATCH 08/50] feat: rework api method to POST --- .../Controllers/QuoterController.cs | 38 ++++++++---------- src/Quoter.Web/Dtos/QuoterRequestArgument.cs | 16 ++++++++ src/Quoter.Web/wwwroot/scripts.js | 39 ++++++++++--------- 3 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 src/Quoter.Web/Dtos/QuoterRequestArgument.cs diff --git a/src/Quoter.Web/Controllers/QuoterController.cs b/src/Quoter.Web/Controllers/QuoterController.cs index 84d5544..99ac0bf 100644 --- a/src/Quoter.Web/Controllers/QuoterController.cs +++ b/src/Quoter.Web/Controllers/QuoterController.cs @@ -1,9 +1,8 @@ using System; -using System.Net; -using System.Net.Http; using System.Text; using System.Web; using Microsoft.AspNetCore.Mvc; +using Quoter.Web.Dtos; using RoslynQuoter; namespace QuoterService.Controllers @@ -11,27 +10,22 @@ namespace QuoterService.Controllers [Route("api/[controller]")] public class QuoterController : Controller { - [HttpGet] - public IActionResult Get( - string sourceText, - NodeKind nodeKind = NodeKind.CompilationUnit, - bool openCurlyOnNewLine = false, - bool closeCurlyOnNewLine = false, - bool preserveOriginalWhitespace = false, - bool keepRedundantApiCalls = false, - bool avoidUsingStatic = false, - bool generateLINQPad = false) + [HttpPost] + public IActionResult Post([FromBody] QuoterRequestArgument arguments) { string prefix = null; string responseText = "Quoter is currently down for maintenance. Please check back later."; - if (string.IsNullOrEmpty(sourceText)) + if (arguments is null) + return BadRequest(responseText); + + if (string.IsNullOrEmpty(arguments.SourceText)) { responseText = "Please specify the source text."; } - else if (sourceText.Length > 2000) + else if (arguments.SourceText.Length > 2000) { - responseText = "Only strings shorter than 2000 characters are supported; your input string is " + sourceText.Length + " characters long."; + responseText = "Only strings shorter than 2000 characters are supported; your input string is " + arguments.SourceText.Length + " characters long."; } else { @@ -39,14 +33,14 @@ namespace QuoterService.Controllers { var quoter = new Quoter { - OpenParenthesisOnNewLine = openCurlyOnNewLine, - ClosingParenthesisOnNewLine = closeCurlyOnNewLine, - UseDefaultFormatting = !preserveOriginalWhitespace, - RemoveRedundantModifyingCalls = !keepRedundantApiCalls, - ShortenCodeWithUsingStatic = !avoidUsingStatic + OpenParenthesisOnNewLine = arguments.OpenCurlyOnNewLine, + ClosingParenthesisOnNewLine = arguments.CloseCurlyOnNewLine, + UseDefaultFormatting = !arguments.PreserveOriginalWhitespace, + RemoveRedundantModifyingCalls = !arguments.KeepRedundantApiCalls, + ShortenCodeWithUsingStatic = !arguments.AvoidUsingStatic }; - responseText = quoter.QuoteText(sourceText, nodeKind); + responseText = quoter.QuoteText(arguments.SourceText, arguments.NodeKind); } catch (Exception ex) { @@ -56,7 +50,7 @@ namespace QuoterService.Controllers } } - if (generateLINQPad) + if (arguments.GenerateLinqPad) { var linqpadFile = $@" Microsoft.CodeAnalysis.Compilers diff --git a/src/Quoter.Web/Dtos/QuoterRequestArgument.cs b/src/Quoter.Web/Dtos/QuoterRequestArgument.cs new file mode 100644 index 0000000..4825a26 --- /dev/null +++ b/src/Quoter.Web/Dtos/QuoterRequestArgument.cs @@ -0,0 +1,16 @@ +using RoslynQuoter; + +namespace Quoter.Web.Dtos +{ + public class QuoterRequestArgument + { + public string SourceText { get; set; } + public NodeKind NodeKind { get; set; } + public bool OpenCurlyOnNewLine { get; set; } + public bool CloseCurlyOnNewLine { get; set; } + public bool PreserveOriginalWhitespace { get; set; } + public bool KeepRedundantApiCalls { get; set; } + public bool AvoidUsingStatic { get; set; } + public bool GenerateLinqPad { get; set; } + } +} \ No newline at end of file diff --git a/src/Quoter.Web/wwwroot/scripts.js b/src/Quoter.Web/wwwroot/scripts.js index fe84e49..ef8434d 100644 --- a/src/Quoter.Web/wwwroot/scripts.js +++ b/src/Quoter.Web/wwwroot/scripts.js @@ -27,56 +27,57 @@ function generateQuery() { var preserveOriginalWhitespace = getCheckboxValue("preserveOriginalWhitespace"); var keepRedundantApiCalls = getCheckboxValue("keepRedundantApiCalls"); var avoidUsingStatic = getCheckboxValue("avoidUsingStatic"); - var query = "api/quoter/?sourceText=" + encodeURIComponent(editor.getValue()); - - query = query + "&nodeKind=" + nodeKind; + var arguments = new Object(); + arguments.sourceText = editor.getValue(); + arguments.nodeKind = nodeKind; if (openCurlyOnNewLine) { - query = query + "&openCurlyOnNewLine=true"; + arguments.openCurlyOnNewLine = true; } if (closeCurlyOnNewLine) { - query = query + "&closeCurlyOnNewLine=true"; + arguments.closeCurlyOnNewLine = true; } if (preserveOriginalWhitespace) { - query = query + "&preserveOriginalWhitespace=true"; + arguments.preserveOriginalWhitespace = true; } if (keepRedundantApiCalls) { - query = query + "&keepRedundantApiCalls=true"; + arguments.keepRedundantApiCalls = true; } if (avoidUsingStatic) { - query = query + "&avoidUsingStatic=true"; + arguments.avoidUsingStatic = true; } - return query; + return arguments; } function onSubmitClick() { - var query = generateQuery(); + var arguments = generateQuery(); - getUrl(query, loadResults); + getUrl(arguments, loadResults); } function onSubmitLINQPad() { - var query = generateQuery(); + var arguments = generateQuery(); - query = query + "&generateLINQPad=true"; + arguments.generateLINQPad = true; - window.location = query; -} + window.location = arguments; +} function getCheckboxValue(id) { return document.getElementById(id).checked; } -function getUrl(url, callback) { +function getUrl(requestArgument, callback) { enableSubmit(false); var xhr = new XMLHttpRequest(); - xhr.open("GET", url, true); - xhr.setRequestHeader("Accept", "text/html"); + xhr.open("POST", "api/quoter", true); + xhr.setRequestHeader("Accept", "application/json"); + xhr.setRequestHeader("Content-type", "application/json"); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { var data = xhr.responseText; @@ -87,7 +88,7 @@ function getUrl(url, callback) { enableSubmit(true); }; - xhr.send(); + xhr.send(JSON.stringify(requestArgument)); return xhr; } From f3b595a683bda6323c13f407fdde275246fcf9b9 Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 11 Oct 2020 15:04:40 -0700 Subject: [PATCH 09/50] Fix build. --- src/Quoter.Web/Controllers/QuoterController.cs | 4 +++- src/Quoter.Web/Dtos/QuoterRequestArgument.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Quoter.Web/Controllers/QuoterController.cs b/src/Quoter.Web/Controllers/QuoterController.cs index 99ac0bf..e1ff434 100644 --- a/src/Quoter.Web/Controllers/QuoterController.cs +++ b/src/Quoter.Web/Controllers/QuoterController.cs @@ -2,7 +2,7 @@ using System.Text; using System.Web; using Microsoft.AspNetCore.Mvc; -using Quoter.Web.Dtos; +using QuoterWeb; using RoslynQuoter; namespace QuoterService.Controllers @@ -17,7 +17,9 @@ namespace QuoterService.Controllers string responseText = "Quoter is currently down for maintenance. Please check back later."; if (arguments is null) + { return BadRequest(responseText); + } if (string.IsNullOrEmpty(arguments.SourceText)) { diff --git a/src/Quoter.Web/Dtos/QuoterRequestArgument.cs b/src/Quoter.Web/Dtos/QuoterRequestArgument.cs index 4825a26..481cae5 100644 --- a/src/Quoter.Web/Dtos/QuoterRequestArgument.cs +++ b/src/Quoter.Web/Dtos/QuoterRequestArgument.cs @@ -1,6 +1,6 @@ using RoslynQuoter; -namespace Quoter.Web.Dtos +namespace QuoterWeb { public class QuoterRequestArgument { From 4d31e8f0a2d19e07fbecc077962551e1b940bd7b Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 11 Oct 2020 15:41:17 -0700 Subject: [PATCH 10/50] Call JSON.parse on the response. Since the content type is now application/json, the line breaks in the response were encoded as \r\n, so need to call JSON.parse() to unwrap them back to real line endings. The symptom was that the response displayed on a single line with actual \r\n in the source instead of line breaks. --- src/Quoter.Web/wwwroot/scripts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Quoter.Web/wwwroot/scripts.js b/src/Quoter.Web/wwwroot/scripts.js index ef8434d..c5b7e4e 100644 --- a/src/Quoter.Web/wwwroot/scripts.js +++ b/src/Quoter.Web/wwwroot/scripts.js @@ -82,6 +82,7 @@ function getUrl(requestArgument, callback) { if (xhr.readyState == 4) { var data = xhr.responseText; if (typeof data === "string" && data.length > 0) { + data = JSON.parse(data); callback(data); } } From 0f750078464566fe01f2ef677a17b390c64aa249 Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 17 Jan 2021 14:57:43 -0800 Subject: [PATCH 11/50] Update to Roslyn 3.9.0-2.final. Add support for BinaryPatternSyntax. Fixes #61. --- src/Quoter.Tests/Tests.cs | 6 ++++++ src/Quoter/Quoter.cs | 1 + src/Quoter/Quoter.csproj | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Quoter.Tests/Tests.cs b/src/Quoter.Tests/Tests.cs index 5f4f756..cc9c63e 100644 --- a/src/Quoter.Tests/Tests.cs +++ b/src/Quoter.Tests/Tests.cs @@ -574,6 +574,12 @@ class C { }"); Test(@"if () {}", "Parse error. Have you selected the right Parse As context?", nodeKind: NodeKind.MemberDeclaration); } + [Fact] + public void TestIssue61() + { + Test(@"void M() { var a = M() is char and > 'H' }"); + } + private void Test( string sourceText, string expected, diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 1a173d7..22dbdbf 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -228,6 +228,7 @@ namespace RoslynQuoter if (node is AccessorDeclarationSyntax || node is AssignmentExpressionSyntax || node is BinaryExpressionSyntax || + node is BinaryPatternSyntax || node is ClassOrStructConstraintSyntax || node is CheckedExpressionSyntax || node is CheckedStatementSyntax || diff --git a/src/Quoter/Quoter.csproj b/src/Quoter/Quoter.csproj index 484e42e..1d46c6a 100644 --- a/src/Quoter/Quoter.csproj +++ b/src/Quoter/Quoter.csproj @@ -24,7 +24,7 @@ - + \ No newline at end of file From 1b0b3beb7641bfd5d4dc922eb8a284f97b21a2e6 Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Sun, 17 Jan 2021 15:01:41 -0800 Subject: [PATCH 12/50] Add a better hint for this exception for next time --- src/Quoter/Quoter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 22dbdbf..8409777 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -1284,7 +1284,9 @@ namespace RoslynQuoter var (candidate, arguments) = PickCandidateMethod(name, methodCall.Arguments, candidates, genericArgumentType); if (candidate == null) { - throw new Exception("Can't pick a method to call for " + methodCall.Name); + throw new Exception( + $@"Can't pick a method to call for {methodCall.Name}. +If the first parameter is of type SyntaxKind, please add an exception for this node type in QuotePropertyValues()."); } var node = candidate.Invoke(instance, arguments); From 90117a07c180f814b3da4d50a1cb882dc644b24c Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Thu, 4 Mar 2021 12:02:16 -0800 Subject: [PATCH 13/50] Fix Linqpad. --- .../Controllers/LinqpadController.cs | 69 +++++++++++++++++++ src/Quoter.Web/wwwroot/scripts.js | 39 +++++++++-- 2 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 src/Quoter.Web/Controllers/LinqpadController.cs diff --git a/src/Quoter.Web/Controllers/LinqpadController.cs b/src/Quoter.Web/Controllers/LinqpadController.cs new file mode 100644 index 0000000..86a2a99 --- /dev/null +++ b/src/Quoter.Web/Controllers/LinqpadController.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; +using Microsoft.AspNetCore.Mvc; +using RoslynQuoter; + +namespace QuoterService.Controllers +{ + [Route("api/[controller]")] + public class LinqpadController : Controller + { + [HttpGet] + public IActionResult Get( + string sourceText, + NodeKind nodeKind = NodeKind.CompilationUnit, + bool openCurlyOnNewLine = false, + bool closeCurlyOnNewLine = false, + bool preserveOriginalWhitespace = false, + bool keepRedundantApiCalls = false, + bool avoidUsingStatic = false) + { + string responseText; + + if (string.IsNullOrEmpty(sourceText)) + { + responseText = "Please specify the source text."; + } + else if (sourceText.Length > 2000) + { + responseText = "Only strings shorter than 2000 characters are supported; your input string is " + sourceText.Length + " characters long."; + } + else + { + try + { + var quoter = new Quoter + { + OpenParenthesisOnNewLine = openCurlyOnNewLine, + ClosingParenthesisOnNewLine = closeCurlyOnNewLine, + UseDefaultFormatting = !preserveOriginalWhitespace, + RemoveRedundantModifyingCalls = !keepRedundantApiCalls, + ShortenCodeWithUsingStatic = !avoidUsingStatic + }; + + responseText = quoter.QuoteText(sourceText, nodeKind); + } + catch (Exception ex) + { + responseText = ex.ToString(); + } + } + + var linqpadFile = $@" + Microsoft.CodeAnalysis.Compilers + Microsoft.CodeAnalysis.CSharp + static Microsoft.CodeAnalysis.CSharp.SyntaxFactory + Microsoft.CodeAnalysis.CSharp.Syntax + Microsoft.CodeAnalysis.CSharp + Microsoft.CodeAnalysis + + +{responseText} +"; + + var responseBytes = Encoding.UTF8.GetBytes(linqpadFile); + + return File(responseBytes, "application/octet-stream", "Quoter.linq"); + } + } +} \ No newline at end of file diff --git a/src/Quoter.Web/wwwroot/scripts.js b/src/Quoter.Web/wwwroot/scripts.js index c5b7e4e..ad137ba 100644 --- a/src/Quoter.Web/wwwroot/scripts.js +++ b/src/Quoter.Web/wwwroot/scripts.js @@ -20,7 +20,7 @@ function onPageLoad() { resultDisplay.session.setMode("ace/mode/csharp"); } -function generateQuery() { +function generateArguments() { var nodeKind = document.getElementById("nodeKind").value; var openCurlyOnNewLine = getCheckboxValue("openCurlyOnNewLine"); var closeCurlyOnNewLine = getCheckboxValue("closeCurlyOnNewLine"); @@ -54,18 +54,45 @@ function generateQuery() { return arguments; } +function generateLinqpadQuery(arguments) { + var query = "api/linqpad/?sourceText=" + encodeURIComponent(arguments.sourceText); + query = query + "&nodeKind=" + arguments.nodeKind; + + if (openCurlyOnNewLine) { + query = query + "&openCurlyOnNewLine=true"; + } + + if (closeCurlyOnNewLine) { + query = query + "&closeCurlyOnNewLine=true"; + } + + if (preserveOriginalWhitespace) { + query = query + "&preserveOriginalWhitespace=true"; + } + + if (keepRedundantApiCalls) { + query = query + "&keepRedundantApiCalls=true"; + } + + if (avoidUsingStatic) { + query = query + "&avoidUsingStatic=true"; + } + + return query; +} + + function onSubmitClick() { - var arguments = generateQuery(); + var arguments = generateArguments(); getUrl(arguments, loadResults); } function onSubmitLINQPad() { - var arguments = generateQuery(); + var arguments = generateArguments(); + var query = generateLinqpadQuery(arguments); - arguments.generateLINQPad = true; - - window.location = arguments; + window.location = query; } function getCheckboxValue(id) { From 2ac5ddd562b50cd4b2528709833b0cb1a3e4f0b4 Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Thu, 4 Mar 2021 12:35:05 -0800 Subject: [PATCH 14/50] Remove duplicated (unused) code. --- .../Controllers/QuoterController.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/Quoter.Web/Controllers/QuoterController.cs b/src/Quoter.Web/Controllers/QuoterController.cs index e1ff434..d6547ec 100644 --- a/src/Quoter.Web/Controllers/QuoterController.cs +++ b/src/Quoter.Web/Controllers/QuoterController.cs @@ -1,5 +1,4 @@ using System; -using System.Text; using System.Web; using Microsoft.AspNetCore.Mvc; using QuoterWeb; @@ -52,25 +51,6 @@ namespace QuoterService.Controllers } } - if (arguments.GenerateLinqPad) - { - var linqpadFile = $@" - Microsoft.CodeAnalysis.Compilers - Microsoft.CodeAnalysis.CSharp - static Microsoft.CodeAnalysis.CSharp.SyntaxFactory - Microsoft.CodeAnalysis.CSharp.Syntax - Microsoft.CodeAnalysis.CSharp - Microsoft.CodeAnalysis - - -{responseText} -"; - - var responseBytes = Encoding.UTF8.GetBytes(linqpadFile); - - return File(responseBytes, "application/octet-stream", "Quoter.linq"); - } - responseText = HttpUtility.HtmlEncode(responseText); if (prefix != null) From 76f9da5733b2ae3ca965edce53455f655e560762 Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Sun, 11 Apr 2021 22:24:23 +0200 Subject: [PATCH 15/50] Bugfix for verbatim-identifiers and contextual keywords, always-false case "value.IsMissing" removed --- src/Quoter/Quoter.cs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 8409777..afebdab 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -385,6 +385,15 @@ namespace RoslynQuoter return codeBlock; } + private static SyntaxKind GetContextualKind(SyntaxToken value) + { + var property = typeof(SyntaxToken).GetProperty("RawContextualKind", + BindingFlags.NonPublic | BindingFlags.Instance); + var result = property.GetValue(value); + + return (SyntaxKind)(int)result; + } + private ApiCall QuoteToken(SyntaxToken value, string name) { if (value == default(SyntaxToken) || value.Kind() == SyntaxKind.None) @@ -412,23 +421,29 @@ namespace RoslynQuoter if (value.Kind() == SyntaxKind.IdentifierToken && !value.IsMissing) { methodName = SyntaxFactoryMethod("Identifier"); - if (value.IsMissing) - { - methodName = SyntaxFactoryMethod("MissingToken"); - } - if (value.IsMissing) + var contextualKind = GetContextualKind(value); + var kind = value.Kind(); + actualValue = escapedTokenValueText; + + //is it required for escaped identifiers (like "st\u0061tic")? + if (contextualKind != kind || verbatim) { - actualValue = value.Kind(); + leading = leading ?? GetEmptyTrivia("LeadingTrivia"); + trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); + + arguments.Add(leading); + arguments.Add(contextualKind); + arguments.Add(actualValue); + arguments.Add(EscapeAndQuote(value.ValueText)); + arguments.Add(trailing); } else { - actualValue = escapedTokenValueText; + AddIfNotNull(arguments, leading); + arguments.Add(actualValue); + AddIfNotNull(arguments, trailing); } - - AddIfNotNull(arguments, leading); - arguments.Add(actualValue); - AddIfNotNull(arguments, trailing); } else if (value.Kind() == SyntaxKind.InterpolatedStringTextToken && !value.IsMissing) { From 36913d0c9e890bcdaa9f916dc3a1bcbe419d6413 Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Sun, 11 Apr 2021 23:17:41 +0200 Subject: [PATCH 16/50] "ReadyToRun"- template added (similar to LinqPad-extension) --- .../Controllers/QuoterController.cs | 7 ++- src/Quoter.Web/Dtos/QuoterRequestArgument.cs | 1 + src/Quoter.Web/ReadyToRunHelper.cs | 43 +++++++++++++++++++ src/Quoter.Web/wwwroot/index.html | 5 +++ src/Quoter.Web/wwwroot/scripts.js | 5 +++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/Quoter.Web/ReadyToRunHelper.cs diff --git a/src/Quoter.Web/Controllers/QuoterController.cs b/src/Quoter.Web/Controllers/QuoterController.cs index d6547ec..f853ec2 100644 --- a/src/Quoter.Web/Controllers/QuoterController.cs +++ b/src/Quoter.Web/Controllers/QuoterController.cs @@ -42,6 +42,11 @@ namespace QuoterService.Controllers }; responseText = quoter.QuoteText(arguments.SourceText, arguments.NodeKind); + + if (arguments.ReadyToRun) + { + responseText = ReadyToRunHelper.CreateReadyToRunCode(arguments, responseText); + } } catch (Exception ex) { @@ -50,7 +55,7 @@ namespace QuoterService.Controllers prefix = "Congratulations! You've found a bug in Quoter! Please open an issue at https://github.com/KirillOsenkov/RoslynQuoter/issues/new and paste the code you've typed above and this stack:"; } } - + responseText = HttpUtility.HtmlEncode(responseText); if (prefix != null) diff --git a/src/Quoter.Web/Dtos/QuoterRequestArgument.cs b/src/Quoter.Web/Dtos/QuoterRequestArgument.cs index 481cae5..082394c 100644 --- a/src/Quoter.Web/Dtos/QuoterRequestArgument.cs +++ b/src/Quoter.Web/Dtos/QuoterRequestArgument.cs @@ -12,5 +12,6 @@ namespace QuoterWeb public bool KeepRedundantApiCalls { get; set; } public bool AvoidUsingStatic { get; set; } public bool GenerateLinqPad { get; set; } + public bool ReadyToRun { get; set; } } } \ No newline at end of file diff --git a/src/Quoter.Web/ReadyToRunHelper.cs b/src/Quoter.Web/ReadyToRunHelper.cs new file mode 100644 index 0000000..4ee3115 --- /dev/null +++ b/src/Quoter.Web/ReadyToRunHelper.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace QuoterWeb +{ + public static class ReadyToRunHelper + { + public static string CreateReadyToRunCode(QuoterRequestArgument arguments, string roslynCode) + { + return @$"using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax;{(arguments.AvoidUsingStatic ? string.Empty : @" + +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;")} + +/* +{arguments.SourceText} +*/ + +var tree = SyntaxTree( +//CODE FROM ROSLYN QUOTER: +{roslynCode} +//END +); + +var refApis = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic) + .Select(a => MetadataReference.CreateFromFile(a.Location)); + +var compilation = CSharpCompilation.Create(""something"", new[] {{ tree }}, refApis); +var diag = compilation.GetDiagnostics().Where(e => e.Severity == DiagnosticSeverity.Error).ToList(); + +foreach(var d in diag) +{{ + Console.WriteLine(d); +}}"; + } + } +} diff --git a/src/Quoter.Web/wwwroot/index.html b/src/Quoter.Web/wwwroot/index.html index 2dcd5f7..6bb208d 100644 --- a/src/Quoter.Web/wwwroot/index.html +++ b/src/Quoter.Web/wwwroot/index.html @@ -55,6 +55,11 @@  Do not require 'using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;' +
+ +
 
diff --git a/src/Quoter.Web/wwwroot/scripts.js b/src/Quoter.Web/wwwroot/scripts.js index ad137ba..3883fe8 100644 --- a/src/Quoter.Web/wwwroot/scripts.js +++ b/src/Quoter.Web/wwwroot/scripts.js @@ -27,6 +27,7 @@ function generateArguments() { var preserveOriginalWhitespace = getCheckboxValue("preserveOriginalWhitespace"); var keepRedundantApiCalls = getCheckboxValue("keepRedundantApiCalls"); var avoidUsingStatic = getCheckboxValue("avoidUsingStatic"); + var readyToRun = getCheckboxValue("readyToRun"); var arguments = new Object(); arguments.sourceText = editor.getValue(); arguments.nodeKind = nodeKind; @@ -51,6 +52,10 @@ function generateArguments() { arguments.avoidUsingStatic = true; } + if (readyToRun) { + arguments.readyToRun = true; + } + return arguments; } From d8bfd40292c3844fa912674973756b76c7d1b96b Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Sun, 11 Apr 2021 23:54:19 +0200 Subject: [PATCH 17/50] simple test --- src/Quoter.Tests/Tests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Quoter.Tests/Tests.cs b/src/Quoter.Tests/Tests.cs index cc9c63e..73577cd 100644 --- a/src/Quoter.Tests/Tests.cs +++ b/src/Quoter.Tests/Tests.cs @@ -580,6 +580,14 @@ class C { }"); Test(@"void M() { var a = M() is char and > 'H' }"); } + [Fact] + public void TestIdentifiers() + { + Test(@"using System; +var @class = 12; +Console.WriteLine(nameof(@class));"); + } + private void Test( string sourceText, string expected, From d2a3ffa9fa65bc1874e4ae08489e85643924926e Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Mon, 12 Apr 2021 08:54:25 +0200 Subject: [PATCH 18/50] Usage of SyntaxFacts.GetContextualKeywordKind, rename of misleading escapedTokenValueText => tokenText --- src/Quoter/Quoter.cs | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index afebdab..239ffe2 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -385,15 +385,6 @@ namespace RoslynQuoter return codeBlock; } - private static SyntaxKind GetContextualKind(SyntaxToken value) - { - var property = typeof(SyntaxToken).GetProperty("RawContextualKind", - BindingFlags.NonPublic | BindingFlags.Instance); - var result = property.GetValue(value); - - return (SyntaxKind)(int)result; - } - private ApiCall QuoteToken(SyntaxToken value, string name) { if (value == default(SyntaxToken) || value.Kind() == SyntaxKind.None) @@ -403,13 +394,13 @@ namespace RoslynQuoter var arguments = new List(); string methodName = SyntaxFactoryMethod("Token"); - bool verbatim = - value.Text.StartsWith("@") || - value.Text.Contains("\r") || - value.Text.Contains("\n"); - string escapedTokenValueText = EscapeAndQuote(value.ToString(), verbatim); + var tokenText = value.Text; + bool verbatim = + tokenText.StartsWith("@") || + tokenText.Contains("\r") || + tokenText.Contains("\n"); + string escapedTokenText = EscapeAndQuote(tokenText, verbatim); object leading = GetLeadingTrivia(value); - object actualValue; object trailing = GetTrailingTrivia(value); if (leading != null || trailing != null) @@ -422,26 +413,35 @@ namespace RoslynQuoter { methodName = SyntaxFactoryMethod("Identifier"); - var contextualKind = GetContextualKind(value); var kind = value.Kind(); - actualValue = escapedTokenValueText; - - //is it required for escaped identifiers (like "st\u0061tic")? - if (contextualKind != kind || verbatim) + + if (verbatim) { leading = leading ?? GetEmptyTrivia("LeadingTrivia"); trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); arguments.Add(leading); - arguments.Add(contextualKind); - arguments.Add(actualValue); + arguments.Add(kind); + arguments.Add(escapedTokenText); arguments.Add(EscapeAndQuote(value.ValueText)); arguments.Add(trailing); + } else + if (SyntaxFacts.GetContextualKeywordKind(value.ValueText) is var contextualKeyWord + && contextualKeyWord != SyntaxKind.None) + { + leading = leading ?? GetEmptyTrivia("LeadingTrivia"); + trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); + + arguments.Add(leading); + arguments.Add(contextualKeyWord); + arguments.Add(escapedTokenText); + arguments.Add(escapedTokenText); + arguments.Add(trailing); } else { AddIfNotNull(arguments, leading); - arguments.Add(actualValue); + arguments.Add(escapedTokenText); AddIfNotNull(arguments, trailing); } } @@ -451,8 +451,8 @@ namespace RoslynQuoter trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); AddIfNotNull(arguments, leading); arguments.Add(value.Kind()); - arguments.Add(escapedTokenValueText); - arguments.Add(escapedTokenValueText); + arguments.Add(escapedTokenText); + arguments.Add(escapedTokenText); AddIfNotNull(arguments, trailing); } else if ((value.Kind() == SyntaxKind.XmlTextLiteralToken || @@ -470,8 +470,8 @@ namespace RoslynQuoter } arguments.Add(leading ?? GetEmptyTrivia("LeadingTrivia")); - arguments.Add(escapedTokenValueText); - arguments.Add(escapedTokenValueText); + arguments.Add(escapedTokenText); + arguments.Add(escapedTokenText); arguments.Add(trailing ?? GetEmptyTrivia("TrailingTrivia")); } else if ((value.Parent is LiteralExpressionSyntax || @@ -535,7 +535,7 @@ namespace RoslynQuoter if (value.Kind() == SyntaxKind.BadToken) { - tokenValue = escapedTokenValueText; + tokenValue = escapedTokenText; } AddIfNotNull(arguments, leading); From d869d6806e3d54fcf848848736f266c5e2e6b34c Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Mon, 12 Apr 2021 09:03:35 +0200 Subject: [PATCH 19/50] apply formatting --- src/Quoter/Quoter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 239ffe2..70bbc01 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -395,7 +395,7 @@ namespace RoslynQuoter var arguments = new List(); string methodName = SyntaxFactoryMethod("Token"); var tokenText = value.Text; - bool verbatim = + bool verbatim = tokenText.StartsWith("@") || tokenText.Contains("\r") || tokenText.Contains("\n"); @@ -414,7 +414,7 @@ namespace RoslynQuoter methodName = SyntaxFactoryMethod("Identifier"); var kind = value.Kind(); - + if (verbatim) { leading = leading ?? GetEmptyTrivia("LeadingTrivia"); @@ -425,7 +425,8 @@ namespace RoslynQuoter arguments.Add(escapedTokenText); arguments.Add(EscapeAndQuote(value.ValueText)); arguments.Add(trailing); - } else + } + else if (SyntaxFacts.GetContextualKeywordKind(value.ValueText) is var contextualKeyWord && contextualKeyWord != SyntaxKind.None) { From 9813e254f556acaf4dea7e2ca1babf530753c35c Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Tue, 13 Apr 2021 20:18:48 -0700 Subject: [PATCH 20/50] Fix whitespace. --- src/Quoter.Web/ReadyToRunHelper.cs | 2 +- src/Quoter/Quoter.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Quoter.Web/ReadyToRunHelper.cs b/src/Quoter.Web/ReadyToRunHelper.cs index 4ee3115..a502e19 100644 --- a/src/Quoter.Web/ReadyToRunHelper.cs +++ b/src/Quoter.Web/ReadyToRunHelper.cs @@ -36,7 +36,7 @@ var diag = compilation.GetDiagnostics().Where(e => e.Severity == DiagnosticSever foreach(var d in diag) {{ - Console.WriteLine(d); + Console.WriteLine(d); }}"; } } diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 70bbc01..066954b 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -387,7 +387,7 @@ namespace RoslynQuoter private ApiCall QuoteToken(SyntaxToken value, string name) { - if (value == default(SyntaxToken) || value.Kind() == SyntaxKind.None) + if (value == default || value.Kind() == SyntaxKind.None) { return null; } @@ -426,8 +426,7 @@ namespace RoslynQuoter arguments.Add(EscapeAndQuote(value.ValueText)); arguments.Add(trailing); } - else - if (SyntaxFacts.GetContextualKeywordKind(value.ValueText) is var contextualKeyWord + else if (SyntaxFacts.GetContextualKeywordKind(value.ValueText) is var contextualKeyWord && contextualKeyWord != SyntaxKind.None) { leading = leading ?? GetEmptyTrivia("LeadingTrivia"); @@ -506,7 +505,7 @@ namespace RoslynQuoter if (shouldAddTrivia || (value.Kind() == SyntaxKind.StringLiteralToken && - value.ToString() != Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Literal(value.ValueText).ToString())) + value.ToString() != SyntaxFactory.Literal(value.ValueText).ToString())) { arguments.Add(escapedText); } From 9d8c7c28fa4292b199aba3138f1df58e75c6780c Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Tue, 20 Apr 2021 15:31:00 -0700 Subject: [PATCH 21/50] Extract variables. --- src/Quoter/Quoter.cs | 80 +++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 066954b..786a7da 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -385,23 +385,27 @@ namespace RoslynQuoter return codeBlock; } - private ApiCall QuoteToken(SyntaxToken value, string name) + private ApiCall QuoteToken(SyntaxToken token, string name) { - if (value == default || value.Kind() == SyntaxKind.None) + var tokenKind = token.Kind(); + var tokenText = token.Text; + var tokenValueText = token.ValueText; + bool tokenIsMissing = token.IsMissing; + + if (token == default || tokenKind == SyntaxKind.None) { return null; } var arguments = new List(); string methodName = SyntaxFactoryMethod("Token"); - var tokenText = value.Text; bool verbatim = tokenText.StartsWith("@") || tokenText.Contains("\r") || tokenText.Contains("\n"); string escapedTokenText = EscapeAndQuote(tokenText, verbatim); - object leading = GetLeadingTrivia(value); - object trailing = GetTrailingTrivia(value); + object leading = GetLeadingTrivia(token); + object trailing = GetTrailingTrivia(token); if (leading != null || trailing != null) { @@ -409,24 +413,22 @@ namespace RoslynQuoter trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); } - if (value.Kind() == SyntaxKind.IdentifierToken && !value.IsMissing) + if (tokenKind == SyntaxKind.IdentifierToken && !tokenIsMissing) { methodName = SyntaxFactoryMethod("Identifier"); - var kind = value.Kind(); - if (verbatim) { leading = leading ?? GetEmptyTrivia("LeadingTrivia"); trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); arguments.Add(leading); - arguments.Add(kind); + arguments.Add(tokenKind); arguments.Add(escapedTokenText); - arguments.Add(EscapeAndQuote(value.ValueText)); + arguments.Add(EscapeAndQuote(tokenValueText)); arguments.Add(trailing); } - else if (SyntaxFacts.GetContextualKeywordKind(value.ValueText) is var contextualKeyWord + else if (SyntaxFacts.GetContextualKeywordKind(tokenValueText) is var contextualKeyWord && contextualKeyWord != SyntaxKind.None) { leading = leading ?? GetEmptyTrivia("LeadingTrivia"); @@ -445,26 +447,26 @@ namespace RoslynQuoter AddIfNotNull(arguments, trailing); } } - else if (value.Kind() == SyntaxKind.InterpolatedStringTextToken && !value.IsMissing) + else if (tokenKind == SyntaxKind.InterpolatedStringTextToken && !tokenIsMissing) { leading = leading ?? GetEmptyTrivia("LeadingTrivia"); trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); AddIfNotNull(arguments, leading); - arguments.Add(value.Kind()); + arguments.Add(tokenKind); arguments.Add(escapedTokenText); arguments.Add(escapedTokenText); AddIfNotNull(arguments, trailing); } - else if ((value.Kind() == SyntaxKind.XmlTextLiteralToken || - value.Kind() == SyntaxKind.XmlTextLiteralNewLineToken || - value.Kind() == SyntaxKind.XmlEntityLiteralToken) && !value.IsMissing) + else if ((tokenKind == SyntaxKind.XmlTextLiteralToken || + tokenKind == SyntaxKind.XmlTextLiteralNewLineToken || + tokenKind == SyntaxKind.XmlEntityLiteralToken) && !tokenIsMissing) { methodName = SyntaxFactoryMethod("XmlTextLiteral"); - if (value.Kind() == SyntaxKind.XmlTextLiteralNewLineToken) + if (tokenKind == SyntaxKind.XmlTextLiteralNewLineToken) { methodName = SyntaxFactoryMethod("XmlTextNewLine"); } - else if (value.Kind() == SyntaxKind.XmlEntityLiteralToken) + else if (tokenKind == SyntaxKind.XmlEntityLiteralToken) { methodName = SyntaxFactoryMethod("XmlEntity"); } @@ -474,15 +476,15 @@ namespace RoslynQuoter arguments.Add(escapedTokenText); arguments.Add(trailing ?? GetEmptyTrivia("TrailingTrivia")); } - else if ((value.Parent is LiteralExpressionSyntax || - value.Kind() == SyntaxKind.StringLiteralToken || - value.Kind() == SyntaxKind.NumericLiteralToken) && - value.Kind() != SyntaxKind.TrueKeyword && - value.Kind() != SyntaxKind.FalseKeyword && - value.Kind() != SyntaxKind.NullKeyword && - value.Kind() != SyntaxKind.ArgListKeyword && - value.Kind() != SyntaxKind.DefaultKeyword && - !value.IsMissing) + else if ((token.Parent is LiteralExpressionSyntax || + tokenKind == SyntaxKind.StringLiteralToken || + tokenKind == SyntaxKind.NumericLiteralToken) && + tokenKind != SyntaxKind.TrueKeyword && + tokenKind != SyntaxKind.FalseKeyword && + tokenKind != SyntaxKind.NullKeyword && + tokenKind != SyntaxKind.ArgListKeyword && + tokenKind != SyntaxKind.DefaultKeyword && + !tokenIsMissing) { methodName = SyntaxFactoryMethod("Literal"); bool shouldAddTrivia = leading != null || trailing != null; @@ -491,21 +493,21 @@ namespace RoslynQuoter arguments.Add(leading ?? GetEmptyTrivia("LeadingTrivia")); } - string escapedText = EscapeAndQuote(value.Text); - string escapedValue = EscapeAndQuote(value.ValueText); + string escapedText = EscapeAndQuote(tokenText); + string escapedValue = EscapeAndQuote(tokenValueText); - if (value.Kind() == SyntaxKind.CharacterLiteralToken) + if (tokenKind == SyntaxKind.CharacterLiteralToken) { - escapedValue = EscapeAndQuote(value.ValueText, "'"); + escapedValue = EscapeAndQuote(tokenValueText, "'"); } - else if (value.Kind() != SyntaxKind.StringLiteralToken) + else if (tokenKind != SyntaxKind.StringLiteralToken) { - escapedValue = value.ValueText; + escapedValue = tokenValueText; } if (shouldAddTrivia || - (value.Kind() == SyntaxKind.StringLiteralToken && - value.ToString() != SyntaxFactory.Literal(value.ValueText).ToString())) + (tokenKind == SyntaxKind.StringLiteralToken && + token.ToString() != SyntaxFactory.Literal(tokenValueText).ToString())) { arguments.Add(escapedText); } @@ -519,21 +521,21 @@ namespace RoslynQuoter } else { - if (value.IsMissing) + if (tokenIsMissing) { methodName = SyntaxFactoryMethod("MissingToken"); } - if (value.Kind() == SyntaxKind.BadToken) + if (tokenKind == SyntaxKind.BadToken) { methodName = SyntaxFactoryMethod("BadToken"); leading = leading ?? GetEmptyTrivia("LeadingTrivia"); trailing = trailing ?? GetEmptyTrivia("TrailingTrivia"); } - object tokenValue = value.Kind(); + object tokenValue = tokenKind; - if (value.Kind() == SyntaxKind.BadToken) + if (tokenKind == SyntaxKind.BadToken) { tokenValue = escapedTokenText; } From dc9c6c7772642273e7059587bd9d3a296fc60bce Mon Sep 17 00:00:00 2001 From: Kirill Osenkov Date: Tue, 20 Apr 2021 19:41:35 -0700 Subject: [PATCH 22/50] Support hex, uint, ulong, float, double and decimal literals Fixes #66 --- src/Quoter.Tests/Tests.cs | 126 +++++++++++++++++++++++++++++++++++--- src/Quoter/Quoter.cs | 93 +++++++++++++++++++++++++--- 2 files changed, 202 insertions(+), 17 deletions(-) diff --git a/src/Quoter.Tests/Tests.cs b/src/Quoter.Tests/Tests.cs index 73577cd..0da01eb 100644 --- a/src/Quoter.Tests/Tests.cs +++ b/src/Quoter.Tests/Tests.cs @@ -324,7 +324,7 @@ class Program } [Fact] - public void Roundtrip6() + public void RoundtripBoolLiteral() { Test(@"class C { bool b = true; }"); } @@ -571,7 +571,11 @@ class C { }"); [Fact] public void TestIssue49() { - Test(@"if () {}", "Parse error. Have you selected the right Parse As context?", nodeKind: NodeKind.MemberDeclaration); + Test( + @"if () {}", + "Parse error. Have you selected the right Parse As context?", + nodeKind: NodeKind.MemberDeclaration, + testRoundtrip: false); } [Fact] @@ -588,13 +592,107 @@ var @class = 12; Console.WriteLine(nameof(@class));"); } + [Fact] + public void TestFloatLiteral() + { + Test("1F", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(1F)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression); + } + + [Fact] + public void TestDoubleLiteral() + { + Test("1D", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(1D)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false // 1D gets erased to just 1 when we interpret the syntax factory calls + ); + } + + [Fact] + public void TestDecimalLiteral() + { + Test("1m", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(1m)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false // 1m gets converted to 1M during roundtrip + ); + } + + [Fact] + public void TestUnsignedLiteral() + { + Test("1u", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(1u)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false // 1u gets converted to 1L during roundtrip + ); + + Test("0x1u", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(0x1u)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false + ); + } + + [Fact] + public void TestLongLiteral() + { + Test("1l", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(1l)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false // 1l gets converted to 1L during roundtrip + ); + + Test("0x1L", @"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(0x1L)) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false + ); + } + + [Theory] + [InlineData("ul")] + [InlineData("uL")] + [InlineData("Ul")] + [InlineData("UL")] + [InlineData("lu")] + [InlineData("lU")] + [InlineData("Lu")] + [InlineData("LU")] + public void TestUnsignedLongLiteral(string suffix) + { + Test("1" + suffix, $@"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(1{suffix})) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false + ); + + Test("0x2" + suffix, $@"SyntaxFactory.LiteralExpression( + SyntaxKind.NumericLiteralExpression, + SyntaxFactory.Literal(0x2{suffix})) +.NormalizeWhitespace()", nodeKind: NodeKind.Expression, + testRoundtrip: false + ); + } + private void Test( string sourceText, string expected, bool useDefaultFormatting = true, bool removeRedundantModifyingCalls = true, bool shortenCodeWithUsingStatic = false, - NodeKind nodeKind = NodeKind.CompilationUnit) + NodeKind nodeKind = NodeKind.CompilationUnit, + bool testRoundtrip = true) { var quoter = new Quoter { @@ -605,16 +703,24 @@ Console.WriteLine(nameof(@class));"); var actual = quoter.QuoteText(sourceText, nodeKind); Assert.Equal(expected, actual); - Test(sourceText); + if (testRoundtrip) + { + Test(sourceText, nodeKind); + } } - private void Test(string sourceText) + private void Test(string sourceText, NodeKind nodeKind = NodeKind.CompilationUnit) { - Test(sourceText, useDefaultFormatting: true, removeRedundantCalls: true, shortenCodeWithUsingStatic: false); - Test(sourceText, useDefaultFormatting: false, removeRedundantCalls: true, shortenCodeWithUsingStatic: true); + Test(sourceText, useDefaultFormatting: true, removeRedundantCalls: true, shortenCodeWithUsingStatic: false, nodeKind); + Test(sourceText, useDefaultFormatting: false, removeRedundantCalls: true, shortenCodeWithUsingStatic: true, nodeKind); } - private static void Test(string sourceText, bool useDefaultFormatting, bool removeRedundantCalls, bool shortenCodeWithUsingStatic) + private static void Test( + string sourceText, + bool useDefaultFormatting, + bool removeRedundantCalls, + bool shortenCodeWithUsingStatic, + NodeKind nodeKind = NodeKind.CompilationUnit) { if (useDefaultFormatting) { @@ -628,9 +734,9 @@ Console.WriteLine(nameof(@class));"); var quoter = new Quoter { UseDefaultFormatting = useDefaultFormatting, - RemoveRedundantModifyingCalls = removeRedundantCalls + RemoveRedundantModifyingCalls = removeRedundantCalls, }; - var generatedCode = quoter.Quote(sourceText); + var generatedCode = quoter.Quote(sourceText, nodeKind); var resultText = quoter.Evaluate(generatedCode); diff --git a/src/Quoter/Quoter.cs b/src/Quoter/Quoter.cs index 786a7da..db19e4f 100644 --- a/src/Quoter/Quoter.cs +++ b/src/Quoter/Quoter.cs @@ -502,7 +502,7 @@ namespace RoslynQuoter } else if (tokenKind != SyntaxKind.StringLiteralToken) { - escapedValue = tokenValueText; + escapedValue = tokenText; } if (shouldAddTrivia || @@ -1282,9 +1282,9 @@ namespace RoslynQuoter return methodCall.Arguments.Select(a => InterpretApiCall((ApiCall)a)).ToArray(); } - if (instance is CompilationUnitSyntax compilationUnit && name == "NormalizeWhitespace") + if (instance is SyntaxNode n && name == "NormalizeWhitespace") { - return compilationUnit.NormalizeWhitespace(); + return n.NormalizeWhitespace(); } string genericArgument; @@ -1555,13 +1555,53 @@ If the first parameter is of type SyntaxKind, please add an exception for this n { return (ParseStringLiteral(str), true); } - else if (parameterType == typeof(int) && int.TryParse(str, out int int32)) + else if (parameterType == typeof(int)) { - return (int32, true); + if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + str = str.Remove(0, 2); + } + + if (int.TryParse(str, out var int32)) + { + return (int32, true); + } } - else if (parameterType == typeof(double) && double.TryParse(str, out double dbl)) + else if (parameterType == typeof(double)) { - return (dbl, true); + if (str.EndsWith("d", StringComparison.OrdinalIgnoreCase)) + { + str = str.Substring(0, str.Length - 1); + } + + if (double.TryParse(str, out var dbl)) + { + return (dbl, true); + } + } + else if (parameterType == typeof(float)) + { + if (str.EndsWith("f", StringComparison.OrdinalIgnoreCase)) + { + str = str.Substring(0, str.Length - 1); + } + + if (float.TryParse(str, out var fl)) + { + return (fl, true); + } + } + else if (parameterType == typeof(decimal)) + { + if (str.EndsWith("m", StringComparison.OrdinalIgnoreCase)) + { + str = str.Substring(0, str.Length - 1); + } + + if (decimal.TryParse(str, out var d)) + { + return (d, true); + } } else if (parameterType == typeof(char) && str.StartsWith("'") && str.EndsWith("'") && char.TryParse(str.Trim('\''), out char ch)) { @@ -1578,6 +1618,45 @@ If the first parameter is of type SyntaxKind, please add an exception for this n return (false, true); } } + else if ( + parameterType == typeof(uint) || + parameterType == typeof(ulong) || + parameterType == typeof(long)) + { + if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + str = str.Remove(0, 2); + } + + if (str.EndsWith("lu", StringComparison.OrdinalIgnoreCase) || + str.EndsWith("ul", StringComparison.OrdinalIgnoreCase)) + { + str = str.Substring(0, str.Length - 2); + } + else if (str.EndsWith("u", StringComparison.OrdinalIgnoreCase)) + { + str = str.Substring(0, str.Length - 1); + } + else if (str.EndsWith("l", StringComparison.OrdinalIgnoreCase)) + { + str = str.Substring(0, str.Length - 1); + } + + if (uint.TryParse(str, out var ui)) + { + return (ui, true); + } + + if (ulong.TryParse(str, out var ul)) + { + return (ul, true); + } + + if (long.TryParse(str, out var l)) + { + return (l, true); + } + } } else if (argument != null && parameterType.IsAssignableFrom(argument.GetType())) { From 6493098e33aabce8baa21270ad895783adc402ac Mon Sep 17 00:00:00 2001 From: Bernd Baumanns Date: Thu, 6 May 2021 13:36:42 +0200 Subject: [PATCH 23/50] PR to fix #36 --- src/Quoter.Web/wwwroot/index.html | 3 ++- src/Quoter/Quoter.cs | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Quoter.Web/wwwroot/index.html b/src/Quoter.Web/wwwroot/index.html index 6bb208d..a13e122 100644 --- a/src/Quoter.Web/wwwroot/index.html +++ b/src/Quoter.Web/wwwroot/index.html @@ -23,7 +23,8 @@