From be8454384d63cc1306bc44c6ee43ad41b6793cec Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Fri, 26 Jan 2024 11:11:30 -0600 Subject: [PATCH] Actually format csx files closes #1141 --- Src/CSharpier.Benchmarks/Program.cs | 3 ++ Src/CSharpier.Cli/CommandLineFormatter.cs | 9 ++++- .../ClientApp/src/AppContext.ts | 6 +++ .../ClientApp/src/Controls.tsx | 7 ++++ .../ClientApp/src/FormatCode.ts | 4 +- .../Controllers/FormatController.cs | 18 +++++++-- .../CommandLineFormatterTests.cs | 38 +++++++++++++++++-- .../FormattingTests/BaseTest.cs | 6 ++- .../FormattingTests/TestFiles/csx/Basics.test | 5 +++ Src/CSharpier.Tests/Samples/Samples.cs | 3 ++ .../SyntaxNodeComparerTests.cs | 3 ++ Src/CSharpier/CSharpFormatter.cs | 19 ++++++++-- Src/CSharpier/CodeFormatter.cs | 1 + Src/CSharpier/SyntaxNodeComparer.cs | 6 ++- Src/CSharpier/SyntaxPrinter/Token.cs | 17 +-------- 15 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/csx/Basics.test diff --git a/Src/CSharpier.Benchmarks/Program.cs b/Src/CSharpier.Benchmarks/Program.cs index 124989fde..08bab192a 100644 --- a/Src/CSharpier.Benchmarks/Program.cs +++ b/Src/CSharpier.Benchmarks/Program.cs @@ -5,6 +5,8 @@ namespace CSharpier.Benchmarks; +using Microsoft.CodeAnalysis; + [MemoryDiagnoser] public class Benchmarks { @@ -22,6 +24,7 @@ public void Default_SyntaxNodeComparer() this.code, false, false, + SourceCodeKind.Regular, CancellationToken.None ); syntaxNodeComparer.CompareSource(); diff --git a/Src/CSharpier.Cli/CommandLineFormatter.cs b/Src/CSharpier.Cli/CommandLineFormatter.cs index 83dcb10b7..2cdddca55 100644 --- a/Src/CSharpier.Cli/CommandLineFormatter.cs +++ b/Src/CSharpier.Cli/CommandLineFormatter.cs @@ -7,6 +7,8 @@ namespace CSharpier.Cli; +using Microsoft.CodeAnalysis; + internal static class CommandLineFormatter { public static async Task Format( @@ -375,12 +377,16 @@ CancellationToken cancellationToken CodeFormatterResult codeFormattingResult; + var sourceCodeKind = Path.GetExtension(fileToFormatInfo.Path).EqualsIgnoreCase(".csx") + ? SourceCodeKind.Script + : SourceCodeKind.Regular; + try { - // TODO xml find correct formatter codeFormattingResult = await CSharpFormatter.FormatAsync( fileToFormatInfo.FileContents, printerOptions, + sourceCodeKind, cancellationToken ); } @@ -430,6 +436,7 @@ CancellationToken cancellationToken codeFormattingResult.Code, codeFormattingResult.ReorderedModifiers, codeFormattingResult.ReorderedUsingsWithDisabledText, + sourceCodeKind, cancellationToken ); diff --git a/Src/CSharpier.Playground/ClientApp/src/AppContext.ts b/Src/CSharpier.Playground/ClientApp/src/AppContext.ts index e8e8ce2fb..eb654a3d1 100644 --- a/Src/CSharpier.Playground/ClientApp/src/AppContext.ts +++ b/Src/CSharpier.Playground/ClientApp/src/AppContext.ts @@ -8,6 +8,8 @@ export const AppContext = React.createContext({ setIndentSize: (value: number) => {}, useTabs: false, setUseTabs: (value: boolean) => {}, + parser: "CSharp", + setParser: (value: string) => {}, showAst: false, setShowAst: (value: boolean) => {}, showDoc: false, @@ -41,6 +43,7 @@ export const useSetupAppContext = () => { const [printWidth, setPrintWidth] = useState(100); const [indentSize, setIndentSize] = useState(4); const [useTabs, setUseTabs] = useState(false); + const [parser, setParser] = useState("CSharp"); const [showAst, setShowAst] = useState(getInitialShowAst()); const [showDoc, setShowDoc] = useState(getInitialShowDoc()); const [hideNull, setHideNull] = useState(getInitialHideNull()); @@ -58,6 +61,8 @@ export const useSetupAppContext = () => { setIndentSize, useTabs, setUseTabs, + parser, + setParser, showAst, setShowAst: (value: boolean) => { window.sessionStorage.setItem("showAst", value.toString()); @@ -95,6 +100,7 @@ export const useSetupAppContext = () => { printWidth, indentSize, useTabs, + parser, ); if (syntaxValidation) { diff --git a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx index fbadb7c77..fef3d50ce 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx @@ -19,6 +19,8 @@ export const Controls = () => { setIndentSize, useTabs, setUseTabs, + parser, + setParser, } = useAppContext(); return (
@@ -38,6 +40,11 @@ export const Controls = () => { /> Use Tabs + +

Debug

diff --git a/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts b/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts index 99ba8a8e7..9ec4f25af 100644 --- a/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts +++ b/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts @@ -2,11 +2,11 @@ let gutters: any[] = []; let marks: any[] = []; let editor: any = undefined; -export const formatCode = async (code: string, printWidth: number, indentSize: number, useTabs: boolean) => { +export const formatCode = async (code: string, printWidth: number, indentSize: number, useTabs: boolean, parser: string) => { const makeRequest = async () => { const response = await fetch("/Format", { method: "POST", - body: JSON.stringify({ code, printWidth, indentSize, useTabs }), + body: JSON.stringify({ code, printWidth, indentSize, useTabs, parser }), headers: { "Content-Type": "application/json", "cache-control": "no-cache", diff --git a/Src/CSharpier.Playground/Controllers/FormatController.cs b/Src/CSharpier.Playground/Controllers/FormatController.cs index 0fe6985b6..5920d34da 100644 --- a/Src/CSharpier.Playground/Controllers/FormatController.cs +++ b/Src/CSharpier.Playground/Controllers/FormatController.cs @@ -9,6 +9,8 @@ namespace CSharpier.Playground.Controllers; +using CSharpier.Utilities; + public class FormatResult { public required string Code { get; set; } @@ -47,11 +49,18 @@ public class PostModel public int PrintWidth { get; set; } public int IndentSize { get; set; } public bool UseTabs { get; set; } + public string Parser { get; set; } = string.Empty; } [HttpPost] - public async Task Post([FromBody] PostModel model) + public async Task Post( + [FromBody] PostModel model, + CancellationToken cancellationToken + ) { + var sourceCodeKind = model.Parser.EqualsIgnoreCase("CSharp") + ? SourceCodeKind.Regular + : SourceCodeKind.Script; var result = await CSharpFormatter.FormatAsync( model.Code, new PrinterOptions @@ -61,7 +70,9 @@ public async Task Post([FromBody] PostModel model) Width = model.PrintWidth, TabWidth = model.IndentSize, UseTabs = model.UseTabs - } + }, + sourceCodeKind, + cancellationToken ); var comparer = new SyntaxNodeComparer( @@ -69,7 +80,8 @@ public async Task Post([FromBody] PostModel model) result.Code, result.ReorderedModifiers, result.ReorderedUsingsWithDisabledText, - CancellationToken.None + sourceCodeKind, + cancellationToken ); return new FormatResult diff --git a/Src/CSharpier.Tests/CommandLineFormatterTests.cs b/Src/CSharpier.Tests/CommandLineFormatterTests.cs index 2f7477555..9b39201e3 100644 --- a/Src/CSharpier.Tests/CommandLineFormatterTests.cs +++ b/Src/CSharpier.Tests/CommandLineFormatterTests.cs @@ -91,12 +91,10 @@ public void Format_Writes_Unsupported() .Be(@"Warning ./Unsupported.js - Is an unsupported file type."); } - [TestCase(".cs")] - [TestCase(".csx")] - public void Format_Writes_File_With_Directory_Path(string extension) + public void Format_Writes_File_With_Directory_Path() { var context = new TestContext(); - var unformattedFilePath = $"Unformatted.{extension}"; + var unformattedFilePath = $"Unformatted.cs"; context.WhenAFileExists(unformattedFilePath, UnformattedClassContent); this.Format(context); @@ -104,6 +102,38 @@ public void Format_Writes_File_With_Directory_Path(string extension) context.GetFileContent(unformattedFilePath).Should().Be(FormattedClassContent); } + [Test] + public void Formats_CSX_File() + { + var context = new TestContext(); + var unformattedFilePath = "Unformatted.csx"; + context.WhenAFileExists( + unformattedFilePath, + """ + #r "Microsoft.WindowsAzure.Storage" + + public static void Run() + { + } + """ + ); + + var result = this.Format(context); + result.OutputLines.First().Should().StartWith("Formatted 1 files"); + + context + .GetFileContent(unformattedFilePath) + .Should() + .Be( + """ + #r "Microsoft.WindowsAzure.Storage" + + public static void Run() { } + + """ + ); + } + [TestCase("0.9.0", false)] [TestCase("9999.0.0", false)] [TestCase("current", true)] diff --git a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs index 678206f21..5c6ad7da6 100644 --- a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs +++ b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs @@ -7,6 +7,9 @@ namespace CSharpier.Tests.FormattingTests; +using CSharpier.Utilities; +using Microsoft.CodeAnalysis; + public class BaseTest { private readonly DirectoryInfo rootDirectory = DirectoryFinder.FindParent("CSharpier.Tests"); @@ -26,10 +29,10 @@ protected async Task RunTest(string fileName, string fileExtension, bool useTabs CancellationToken.None ); - // TODO xml use proper formatter var result = await CSharpFormatter.FormatAsync( fileReaderResult.FileContents, new PrinterOptions { Width = PrinterOptions.WidthUsedByTests, UseTabs = useTabs }, + fileExtension.EqualsIgnoreCase("csx") ? SourceCodeKind.Script : SourceCodeKind.Regular, CancellationToken.None ); @@ -60,6 +63,7 @@ protected async Task RunTest(string fileName, string fileExtension, bool useTabs normalizedCode, false, false, + SourceCodeKind.Regular, CancellationToken.None ); diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/csx/Basics.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/csx/Basics.test new file mode 100644 index 000000000..6d3c3f1e8 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/csx/Basics.test @@ -0,0 +1,5 @@ +#!/usr/bin/env dotnet-script +#r "Microsoft.WindowsAzure.Storage" +#load "mylogger.csx" + +public static void Run() { } diff --git a/Src/CSharpier.Tests/Samples/Samples.cs b/Src/CSharpier.Tests/Samples/Samples.cs index b1086835d..8cc6e5f76 100644 --- a/Src/CSharpier.Tests/Samples/Samples.cs +++ b/Src/CSharpier.Tests/Samples/Samples.cs @@ -4,6 +4,8 @@ namespace CSharpier.Tests.Samples; +using Microsoft.CodeAnalysis; + [TestFixture] [Parallelizable(ParallelScope.All)] public class Samples @@ -41,6 +43,7 @@ public static async Task RunTest(string fileName) result.Code, false, false, + SourceCodeKind.Regular, CancellationToken.None ); diff --git a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs index 47b9cda1b..a56537389 100644 --- a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs +++ b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs @@ -3,6 +3,8 @@ namespace CSharpier.Tests; +using Microsoft.CodeAnalysis; + [TestFixture] [Parallelizable(ParallelScope.All)] public class SyntaxNodeComparerTests @@ -623,6 +625,7 @@ private static string CompareSource( right, false, reorderedUsingsWithDisabledText, + SourceCodeKind.Regular, CancellationToken.None ).CompareSource(); diff --git a/Src/CSharpier/CSharpFormatter.cs b/Src/CSharpier/CSharpFormatter.cs index 5cc1436bd..59e3942ff 100644 --- a/Src/CSharpier/CSharpFormatter.cs +++ b/Src/CSharpier/CSharpFormatter.cs @@ -5,6 +5,8 @@ namespace CSharpier; using CSharpier.Formatters.CSharp; using CSharpier.SyntaxPrinter; +internal class CSharpScriptFormatter : CSharpFormatter { } + internal class CSharpFormatter : IFormatter { internal static readonly LanguageVersion LanguageVersion = LanguageVersion.Preview; @@ -27,13 +29,21 @@ internal static Task FormatAsync( string code, PrinterOptions printerOptions, CancellationToken cancellationToken + ) => FormatAsync(code, printerOptions, SourceCodeKind.Regular, cancellationToken); + + internal static Task FormatAsync( + string code, + PrinterOptions printerOptions, + SourceCodeKind sourceCodeKind, + CancellationToken cancellationToken ) { var initialSymbolSet = Array.Empty(); return FormatAsync( - ParseText(code, initialSymbolSet, cancellationToken), + ParseText(code, initialSymbolSet, sourceCodeKind, cancellationToken), printerOptions, + sourceCodeKind, cancellationToken ); } @@ -41,6 +51,7 @@ CancellationToken cancellationToken private static SyntaxTree ParseText( string codeToFormat, IEnumerable preprocessorSymbols, + SourceCodeKind sourceCodeKind, CancellationToken cancellationToken ) { @@ -49,7 +60,8 @@ CancellationToken cancellationToken new CSharpParseOptions( LanguageVersion, DocumentationMode.Diagnose, - preprocessorSymbols: preprocessorSymbols + preprocessorSymbols: preprocessorSymbols, + kind: sourceCodeKind ), cancellationToken: cancellationToken ); @@ -58,6 +70,7 @@ CancellationToken cancellationToken internal static async Task FormatAsync( SyntaxTree syntaxTree, PrinterOptions printerOptions, + SourceCodeKind sourceCodeKind, CancellationToken cancellationToken ) { @@ -120,7 +133,7 @@ bool TryGetCompilationFailure(out CodeFormatterResult compilationResult) foreach (var symbolSet in PreprocessorSymbols.GetSets(syntaxTree)) { - syntaxTree = ParseText(formattedCode, symbolSet, cancellationToken); + syntaxTree = ParseText(formattedCode, symbolSet, sourceCodeKind, cancellationToken); if (TryGetCompilationFailure(out result)) { diff --git a/Src/CSharpier/CodeFormatter.cs b/Src/CSharpier/CodeFormatter.cs index cb135a866..949f1452a 100644 --- a/Src/CSharpier/CodeFormatter.cs +++ b/Src/CSharpier/CodeFormatter.cs @@ -45,6 +45,7 @@ public static Task FormatAsync( return CSharpFormatter.FormatAsync( syntaxTree, new PrinterOptions { Width = options.Width }, + SourceCodeKind.Regular, cancellationToken ); } diff --git a/Src/CSharpier/SyntaxNodeComparer.cs b/Src/CSharpier/SyntaxNodeComparer.cs index af3eeaf29..7b1188d44 100644 --- a/Src/CSharpier/SyntaxNodeComparer.cs +++ b/Src/CSharpier/SyntaxNodeComparer.cs @@ -18,6 +18,7 @@ public SyntaxNodeComparer( string newSourceCode, bool reorderedModifiers, bool reorderedUsingsWithDisabledText, + SourceCodeKind sourceCodeKind, CancellationToken cancellationToken ) { @@ -26,7 +27,10 @@ CancellationToken cancellationToken this.ReorderedModifiers = reorderedModifiers; this.ReorderedUsingsWithDisabledText = reorderedUsingsWithDisabledText; - var cSharpParseOptions = new CSharpParseOptions(CSharpFormatter.LanguageVersion); + var cSharpParseOptions = new CSharpParseOptions( + CSharpFormatter.LanguageVersion, + kind: sourceCodeKind + ); this.OriginalSyntaxTree = CSharpSyntaxTree.ParseText( this.OriginalSourceCode, cSharpParseOptions, diff --git a/Src/CSharpier/SyntaxPrinter/Token.cs b/Src/CSharpier/SyntaxPrinter/Token.cs index 6999b82b9..a5c05a70e 100644 --- a/Src/CSharpier/SyntaxPrinter/Token.cs +++ b/Src/CSharpier/SyntaxPrinter/Token.cs @@ -267,7 +267,7 @@ void AddLeadingComment(CommentType commentType) ); docs.Add(Doc.HardLine); } - else if (IsDirective(kind)) + else if (trivia.IsDirective) { var triviaText = trivia.ToString(); @@ -341,21 +341,6 @@ is SyntaxKind.SingleLineDocumentationCommentTrivia private static bool IsMultiLineComment(SyntaxKind kind) => kind is SyntaxKind.MultiLineCommentTrivia or SyntaxKind.MultiLineDocumentationCommentTrivia; - private static bool IsDirective(SyntaxKind kind) => - kind - is SyntaxKind.IfDirectiveTrivia - or SyntaxKind.ElseDirectiveTrivia - or SyntaxKind.ElifDirectiveTrivia - or SyntaxKind.EndIfDirectiveTrivia - or SyntaxKind.LineDirectiveTrivia - or SyntaxKind.ErrorDirectiveTrivia - or SyntaxKind.WarningDirectiveTrivia - or SyntaxKind.PragmaWarningDirectiveTrivia - or SyntaxKind.PragmaChecksumDirectiveTrivia - or SyntaxKind.DefineDirectiveTrivia - or SyntaxKind.UndefDirectiveTrivia - or SyntaxKind.NullableDirectiveTrivia; - private static bool IsRegion(SyntaxKind kind) => kind is SyntaxKind.RegionDirectiveTrivia or SyntaxKind.EndRegionDirectiveTrivia;