diff --git a/.editorconfig b/.editorconfig index bd26ec482..c06f455ce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,3 +4,5 @@ indent_style = space [*.{cs,vb}] dotnet_diagnostic.RS0041.severity = warning +dotnet_diagnostic.IDE0065.severity = warning +csharp_using_directive_placement = outside_namespace diff --git a/.gitignore b/.gitignore index d2e133726..53553bfce 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,6 @@ build publish *.doctree.txt -*.actual.cst *.actual.test *.vsix /.husky/pre-commit diff --git a/.idea/.idea.CSharpier/.idea/prettier.xml b/.idea/.idea.CSharpier/.idea/prettier.xml new file mode 100644 index 000000000..0c83ac4e8 --- /dev/null +++ b/.idea/.idea.CSharpier/.idea/prettier.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/CSharpier.sln.DotSettings b/CSharpier.sln.DotSettings index 708b98810..176f4143d 100644 --- a/CSharpier.sln.DotSettings +++ b/CSharpier.sln.DotSettings @@ -1,4 +1,5 @@  + False True True True diff --git a/Directory.Build.props b/Directory.Build.props index 091ac51e5..cd8226273 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ enable enable - CS8032 + CS8032;CS8033; true diff --git a/Scripts/UpdateCSharpierRepos.ps1 b/Scripts/UpdateCSharpierRepos.ps1 new file mode 100644 index 000000000..91a7713d9 --- /dev/null +++ b/Scripts/UpdateCSharpierRepos.ps1 @@ -0,0 +1,68 @@ +$repositories = @() +$repositories += "https://github.com/dotnet/aspnetcore.git" +$repositories += "https://github.com/aspnet/AspNetWebStack.git" +$repositories += "https://github.com/AutoMapper/AutoMapper.git" +$repositories += "https://github.com/castleproject/Core.git" +$repositories += "https://github.com/dotnet/command-line-api.git" +$repositories += "https://github.com/dotnet/format.git" +$repositories += "https://github.com/dotnet/efcore.git" +$repositories += "https://github.com/moq/moq4.git" +$repositories += "https://github.com/JamesNK/Newtonsoft.Json.git" +$repositories += "https://github.com/dotnet/roslyn.git" +$repositories += "https://github.com/dotnet/runtime.git" +$repositories += "https://github.com/mono/mono.git" +$repositories += "https://github.com/increase-POS/Res-Server.git" + +$tempLocation = "c:\temp\UpdateRepos" + +if (-not (Test-Path $tempLocation)) { + New-Item $tempLocation -Force -ItemType Directory +} + +Set-Location $tempLocation + +$ErrorActionPreference = "Continue" + +foreach ($repository in $repositories) { + $repoFolder = $tempLocation + "/" + (Split-Path $repositories -Leaf).Replace(".git", "") + if (Test-Path $repoFolder) { + Set-Location $repoFolder + & git pull origin + Set-Location $tempLocation + } + else { + & git clone $repository + } +} + +$destination = "C:\projects\csharpier-repos\" +Set-Location $destination +& git checkout main + +Get-ChildItem $tempLocation | Copy-Item -Destination $destination -Recurse -Force + +$extensions = (".cs", ".csproj", ".props", ".targets") + +$items = Get-ChildItem -Recurse C:\projects\csharpier-repos -File +foreach ($item in $items) { + if ($item.Name -eq ".git") { + Remove-Item -Force -Recurse $item.FullName + } + + if (-not ($extensions.Contains($item.Extension)) -and $item.Name -ne ".csharpierignore") { + Remove-Item $item.FullName + } +} + +$items = Get-ChildItem C:\projects\csharpier-repos -Directory -Recurse +foreach ($item in $items) { + if ($item.Name -eq ".git") { + Remove-Item -Force -Recurse $item.FullName + } +} + +Set-Location $destination + +& git add . +& git commit -m "Updating repos" +& git push origin \ No newline at end of file diff --git a/Src/CSharpier.Benchmarks/Program.cs b/Src/CSharpier.Benchmarks/Program.cs index 08bab192a..33c594fe6 100644 --- a/Src/CSharpier.Benchmarks/Program.cs +++ b/Src/CSharpier.Benchmarks/Program.cs @@ -2,11 +2,10 @@ using System.Threading; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; +using Microsoft.CodeAnalysis; namespace CSharpier.Benchmarks; -using Microsoft.CodeAnalysis; - [MemoryDiagnoser] public class Benchmarks { diff --git a/Src/CSharpier.Cli.Tests/CliTests.cs b/Src/CSharpier.Cli.Tests/CliTests.cs index 74d1d85e7..659e0ee03 100644 --- a/Src/CSharpier.Cli.Tests/CliTests.cs +++ b/Src/CSharpier.Cli.Tests/CliTests.cs @@ -1,13 +1,11 @@ -namespace CSharpier.Cli.Tests; - -using System.Collections.Generic; -using System.Linq; using System.Text; using CliWrap; using CliWrap.Buffered; using FluentAssertions; using NUnit.Framework; +namespace CSharpier.Cli.Tests; + // these tests are kind of nice as c# because they run in the same place. // except the one test that has issues with console input redirection // they used to be powershell, but doing the multiple file thing didn't work diff --git a/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs b/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs index 03b028f1b..5cf8729d1 100644 --- a/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs +++ b/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs @@ -1,10 +1,10 @@ -namespace CSharpier.Cli.Tests.EditorConfig; - using CSharpier.Cli.EditorConfig; using FluentAssertions; using IniParser.Model; using NUnit.Framework; +namespace CSharpier.Cli.Tests.EditorConfig; + [TestFixture] public class SectionTests { diff --git a/Src/CSharpier.Cli.Tests/ServerTests.cs b/Src/CSharpier.Cli.Tests/ServerTests.cs index 91ba2b642..56739fc62 100644 --- a/Src/CSharpier.Cli.Tests/ServerTests.cs +++ b/Src/CSharpier.Cli.Tests/ServerTests.cs @@ -1,14 +1,13 @@ -namespace CSharpier.Cli.Tests; - using System.Diagnostics; using System.Net.Http.Json; -using System.Text; using CliWrap; using CliWrap.EventStream; using CSharpier.Cli.Server; using FluentAssertions; using NUnit.Framework; +namespace CSharpier.Cli.Tests; + [TestFixture] public class ServerTests { @@ -79,7 +78,8 @@ public void RunTwo() async Task NewFunction() { - var command = Cli.Wrap("dotnet") + var command = CliWrap + .Cli.Wrap("dotnet") .WithArguments(path + " --server") .WithValidation(CommandResultValidation.None); diff --git a/Src/CSharpier.Cli/CommandLineFormatter.cs b/Src/CSharpier.Cli/CommandLineFormatter.cs index 1b80ef17c..3a0046757 100644 --- a/Src/CSharpier.Cli/CommandLineFormatter.cs +++ b/Src/CSharpier.Cli/CommandLineFormatter.cs @@ -3,12 +3,11 @@ using System.Text; using CSharpier.Cli.Options; using CSharpier.Utilities; +using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; namespace CSharpier.Cli; -using Microsoft.CodeAnalysis; - internal static class CommandLineFormatter { public static async Task Format( @@ -177,9 +176,9 @@ CancellationToken cancellationToken cancellationToken ); - var originalDirectoryOrFile = commandLineOptions - .OriginalDirectoryOrFilePaths[x] - .Replace("\\", "/"); + var originalDirectoryOrFile = commandLineOptions.OriginalDirectoryOrFilePaths[ + x + ].Replace("\\", "/"); var formattingCache = await FormattingCacheFactory.InitializeAsync( commandLineOptions, @@ -255,12 +254,8 @@ await FormatPhysicalFile( return 1; } - var tasks = fileSystem - .Directory.EnumerateFiles( - directoryOrFilePath, - "*.*", - SearchOption.AllDirectories - ) + var tasks = fileSystem.Directory + .EnumerateFiles(directoryOrFilePath, "*.*", SearchOption.AllDirectories) .Select(o => { var normalizedPath = o.Replace("\\", "/"); @@ -380,18 +375,13 @@ CancellationToken cancellationToken cancellationToken.ThrowIfCancellationRequested(); - CodeFormatterResult codeFormattingResult; - - var sourceCodeKind = Path.GetExtension(fileToFormatInfo.Path).EqualsIgnoreCase(".csx") - ? SourceCodeKind.Script - : SourceCodeKind.Regular; + CodeFormatterResult? codeFormattingResult; try { - codeFormattingResult = await CSharpFormatter.FormatAsync( + codeFormattingResult = await CodeFormatter.FormatAsync( fileToFormatInfo.FileContents, printerOptions, - sourceCodeKind, cancellationToken ); } @@ -434,8 +424,17 @@ CancellationToken cancellationToken return; } - if (!commandLineOptions.Fast) + // TODO xml implement this stuff - maybe new PR? + // https://github.com/belav/csharpier/pull/858#issuecomment-1487385384 + // TODO xml review this https://github.com/belav/csharpier-repos/pull/67 + // TODO xml what about allowing lines between elements? + if (!commandLineOptions.Fast && fileToFormatInfo.Path.EndsWithIgnoreCase(".cs")) { + // TODO xml the thing above should do this if we are using the csharp formatter + var sourceCodeKind = Path.GetExtension(fileToFormatInfo.Path).EqualsIgnoreCase(".csx") + ? SourceCodeKind.Script + : SourceCodeKind.Regular; + var syntaxNodeComparer = new SyntaxNodeComparer( fileToFormatInfo.FileContents, codeFormattingResult.Code, diff --git a/Src/CSharpier.Cli/CommandLineOptions.cs b/Src/CSharpier.Cli/CommandLineOptions.cs index d12dfc1fd..3f72b9850 100644 --- a/Src/CSharpier.Cli/CommandLineOptions.cs +++ b/Src/CSharpier.Cli/CommandLineOptions.cs @@ -1,9 +1,8 @@ using System.CommandLine; +using Microsoft.Extensions.Logging; namespace CSharpier.Cli; -using Microsoft.Extensions.Logging; - internal class CommandLineOptions { public string[] DirectoryOrFilePaths { get; init; } = Array.Empty(); diff --git a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs index ddb82d51d..88573fad7 100644 --- a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs +++ b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs @@ -16,14 +16,18 @@ internal class EditorConfigSections var formatter = resolvedConfiguration.Formatter - ?? (filePath.EndsWith(".cs") || filePath.EndsWith(".csx") ? "csharp" : null); + ?? ( + filePath.EndsWith(".cs") ? "CSharp" + : filePath.EndsWith(".csx") ? "CSharpScript" + : null + ); - if (formatter == null) + if (!Enum.TryParse(formatter, ignoreCase: true, out var parsedFormatter)) { return null; } - var printerOptions = new PrinterOptions { Formatter = formatter }; + var printerOptions = new PrinterOptions { Formatter = parsedFormatter }; if (resolvedConfiguration.MaxLineLength is { } maxLineLength) { diff --git a/Src/CSharpier.Cli/EditorConfig/GlobMatcher.cs b/Src/CSharpier.Cli/EditorConfig/GlobMatcher.cs index 3332886e1..0b9b80112 100644 --- a/Src/CSharpier.Cli/EditorConfig/GlobMatcher.cs +++ b/Src/CSharpier.Cli/EditorConfig/GlobMatcher.cs @@ -5,9 +5,6 @@ * From https://github.com/SLaks/Minimatch */ -#nullable disable - -namespace CSharpier.Cli.EditorConfig; using System; using System.Collections.Generic; @@ -16,6 +13,10 @@ namespace CSharpier.Cli.EditorConfig; using System.Text; using System.Text.RegularExpressions; +#nullable disable + +namespace CSharpier.Cli.EditorConfig; + // ReSharper disable UnusedAutoPropertyAccessor.Global ///Contains options that control how Minimatch matches strings. diff --git a/Src/CSharpier.Cli/EditorConfig/Section.cs b/Src/CSharpier.Cli/EditorConfig/Section.cs index f50f312d2..51189760d 100644 --- a/Src/CSharpier.Cli/EditorConfig/Section.cs +++ b/Src/CSharpier.Cli/EditorConfig/Section.cs @@ -1,7 +1,7 @@ -namespace CSharpier.Cli.EditorConfig; - using IniParser.Model; +namespace CSharpier.Cli.EditorConfig; + internal class Section { private readonly GlobMatcher matcher; diff --git a/Src/CSharpier.Cli/FormattingCache.cs b/Src/CSharpier.Cli/FormattingCache.cs index ee86cd315..b6ca28ba9 100644 --- a/Src/CSharpier.Cli/FormattingCache.cs +++ b/Src/CSharpier.Cli/FormattingCache.cs @@ -3,12 +3,11 @@ using System.IO.Hashing; using System.Text; using System.Text.Json; +using CSharpier.Cli.Options; using CSharpier.Utilities; namespace CSharpier.Cli; -using CSharpier.Cli.Options; - internal interface IFormattingCache { Task ResolveAsync(CancellationToken cancellationToken); diff --git a/Src/CSharpier.Cli/Options/CaseInsensitiveEnumConverter.cs b/Src/CSharpier.Cli/Options/CaseInsensitiveEnumConverter.cs index 2968d3ab4..c5820c85d 100644 --- a/Src/CSharpier.Cli/Options/CaseInsensitiveEnumConverter.cs +++ b/Src/CSharpier.Cli/Options/CaseInsensitiveEnumConverter.cs @@ -1,8 +1,8 @@ -namespace CSharpier.Cli.Options; - using System.Text.Json; using System.Text.Json.Serialization; +namespace CSharpier.Cli.Options; + internal class CaseInsensitiveEnumConverter : JsonConverter where TEnum : struct { diff --git a/Src/CSharpier.Cli/Options/ConfigFileParser.cs b/Src/CSharpier.Cli/Options/ConfigFileParser.cs index 5591d2f4a..954fdc31e 100644 --- a/Src/CSharpier.Cli/Options/ConfigFileParser.cs +++ b/Src/CSharpier.Cli/Options/ConfigFileParser.cs @@ -1,11 +1,11 @@ -namespace CSharpier.Cli.Options; - using System.IO.Abstractions; using System.Text.Json; using Microsoft.Extensions.Logging; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; +namespace CSharpier.Cli.Options; + internal static class ConfigFileParser { private static readonly string[] validExtensions = { ".csharpierrc", ".json", ".yml", ".yaml" }; diff --git a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs index eeaaa72ac..6fe6d27ba 100644 --- a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs +++ b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs @@ -1,8 +1,8 @@ -namespace CSharpier.Cli.Options; - using System.Text.Json.Serialization; using CSharpier.Cli.EditorConfig; +namespace CSharpier.Cli.Options; + internal class ConfigurationFileOptions { public int PrintWidth { get; init; } = 100; @@ -20,13 +20,24 @@ internal class ConfigurationFileOptions var matchingOverride = this.Overrides.LastOrDefault(o => o.IsMatch(filePath)); if (matchingOverride is not null) { + if ( + !Enum.TryParse( + matchingOverride.Formatter, + ignoreCase: true, + out var parsedFormatter + ) + ) + { + return null; + } + return new PrinterOptions { IndentSize = matchingOverride.TabWidth, UseTabs = matchingOverride.UseTabs, Width = matchingOverride.PrintWidth, EndOfLine = matchingOverride.EndOfLine, - Formatter = matchingOverride.Formatter, + Formatter = parsedFormatter, }; } @@ -38,7 +49,7 @@ internal class ConfigurationFileOptions UseTabs = this.UseTabs, Width = this.PrintWidth, EndOfLine = this.EndOfLine, - Formatter = "csharp", + Formatter = filePath.EndsWith(".cs") ? Formatter.CSharp : Formatter.CSharpScript, }; } diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index 2497b0be2..8b26a8f17 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -1,10 +1,10 @@ -namespace CSharpier.Cli.Options; - using System.IO.Abstractions; using System.Text.Json; using CSharpier.Cli.EditorConfig; using Microsoft.Extensions.Logging; +namespace CSharpier.Cli.Options; + internal class OptionsProvider { private readonly IList editorConfigs; @@ -110,9 +110,24 @@ public static async Task Create( return resolvedEditorConfig.ConvertToPrinterOptions(filePath); } - if (filePath.EndsWith(".cs") || filePath.EndsWith(".csx")) + if (filePath.EndsWith(".cs")) + { + return new PrinterOptions { Formatter = Formatter.CSharp }; + } + + if (filePath.EndsWith(".csx")) + { + return new PrinterOptions { Formatter = Formatter.CSharpScript }; + } + + if ( + filePath.EndsWith(".csproj") + || filePath.EndsWith(".props") + || filePath.EndsWith(".targets") + || filePath.EndsWith(".xml") + ) { - return new PrinterOptions { Formatter = "csharp" }; + return new PrinterOptions { Formatter = Formatter.XML }; } return null; diff --git a/Src/CSharpier.Cli/PipeMultipleFilesFormatter.cs b/Src/CSharpier.Cli/PipeMultipleFilesFormatter.cs index 861455f5d..f20df6b91 100644 --- a/Src/CSharpier.Cli/PipeMultipleFilesFormatter.cs +++ b/Src/CSharpier.Cli/PipeMultipleFilesFormatter.cs @@ -1,9 +1,9 @@ -namespace CSharpier.Cli; - using System.IO.Abstractions; using System.Text; using Microsoft.Extensions.Logging; +namespace CSharpier.Cli; + internal static class PipeMultipleFilesFormatter { public static async Task StartServer( diff --git a/Src/CSharpier.Cli/Program.cs b/Src/CSharpier.Cli/Program.cs index be5ddf5fe..b26db8d4f 100644 --- a/Src/CSharpier.Cli/Program.cs +++ b/Src/CSharpier.Cli/Program.cs @@ -2,12 +2,11 @@ using System.CommandLine.Invocation; using System.IO.Abstractions; using System.Text; +using CSharpier.Cli.Server; using Microsoft.Extensions.Logging; namespace CSharpier.Cli; -using CSharpier.Cli.Server; - internal class Program { public static async Task Main(string[] args) diff --git a/Src/CSharpier.Cli/Server/CSharpierServiceImplementation.cs b/Src/CSharpier.Cli/Server/CSharpierServiceImplementation.cs index 11771c437..35b17640e 100644 --- a/Src/CSharpier.Cli/Server/CSharpierServiceImplementation.cs +++ b/Src/CSharpier.Cli/Server/CSharpierServiceImplementation.cs @@ -1,9 +1,9 @@ -namespace CSharpier.Cli.Server; - using System.IO.Abstractions; using CSharpier.Cli.Options; using Microsoft.Extensions.Logging; +namespace CSharpier.Cli.Server; + internal class CSharpierServiceImplementation(string? configPath, ILogger logger) { private readonly IFileSystem fileSystem = new FileSystem(); diff --git a/Src/CSharpier.Cli/Server/FormatFileResult.cs b/Src/CSharpier.Cli/Server/FormatFileResult.cs index cffdafe7d..bf66113d1 100644 --- a/Src/CSharpier.Cli/Server/FormatFileResult.cs +++ b/Src/CSharpier.Cli/Server/FormatFileResult.cs @@ -1,7 +1,7 @@ -namespace CSharpier.Cli.Server; - using System.Text.Json.Serialization; +namespace CSharpier.Cli.Server; + public class FormatFileResult(Status status) { public string? formattedFile { get; set; } diff --git a/Src/CSharpier.Cli/Server/ServerFormatter.cs b/Src/CSharpier.Cli/Server/ServerFormatter.cs index 555ee3806..e3ff8ae0b 100644 --- a/Src/CSharpier.Cli/Server/ServerFormatter.cs +++ b/Src/CSharpier.Cli/Server/ServerFormatter.cs @@ -1,5 +1,3 @@ -namespace CSharpier.Cli.Server; - using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -9,6 +7,8 @@ namespace CSharpier.Cli.Server; using Microsoft.Extensions.Logging; using NReco.Logging.File; +namespace CSharpier.Cli.Server; + internal static class ServerFormatter { public static async Task StartServer( diff --git a/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs b/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs index 1ec251322..55cc46291 100644 --- a/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs +++ b/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs @@ -1,11 +1,10 @@ using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace CSharpier.FakeGenerators; -using Microsoft.CodeAnalysis.CSharp.Syntax; - public class SyntaxNodeComparerGenerator { // this would probably be easier to understand as a scriban template but is a lot of effort diff --git a/Src/CSharpier.Generators/NodePrinterGenerator.cs b/Src/CSharpier.Generators/NodePrinterGenerator.cs index c8f24024c..49c234f21 100644 --- a/Src/CSharpier.Generators/NodePrinterGenerator.cs +++ b/Src/CSharpier.Generators/NodePrinterGenerator.cs @@ -1,16 +1,54 @@ -using System.IO; -using System.Linq; -using Generators; +using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Scriban; namespace CSharpier.Generators; +// the magic command to get source generators to actually regenerate when they get stuck with old code +// dotnet build-server shutdown [Generator] -public class NodePrinterGenerator : TemplatedGenerator +public class NodePrinterGenerator : ISourceGenerator { - protected override string SourceName => "Node"; + public void Initialize(GeneratorInitializationContext context) { } - protected override object GetModel(GeneratorExecutionContext context) + public void Execute(GeneratorExecutionContext context) + { + var template = Template.Parse(this.GetContent(this.GetType().Name + ".sbntxt")); + var renderedSource = template.Render(this.GetModel(context), member => member.Name); + + var sourceText = SourceText.From(renderedSource, Encoding.UTF8); + + context.AddSource("Node", sourceText); + } + + public string GetContent(string relativePath) + { + var assembly = this.GetType().Assembly; + var baseName = assembly.GetName().Name; + var resourceName = relativePath + .TrimStart('.') + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + + var name = baseName + "." + resourceName; + using var stream = assembly.GetManifestResourceStream(name); + + if (stream == null) + { + var list = assembly.GetManifestResourceNames(); + + throw new Exception( + $"No embedded resource found with the name {name}. Resources found are " + + string.Join(", ", list) + ); + } + + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private object GetModel(GeneratorExecutionContext context) { var nodeTypes = context .Compilation.SyntaxTrees.Where(o => o.FilePath.Contains("SyntaxNodePrinters")) diff --git a/Src/CSharpier.Generators/TemplatedGenerator.cs b/Src/CSharpier.Generators/TemplatedGenerator.cs deleted file mode 100644 index 9c63ab5fc..000000000 --- a/Src/CSharpier.Generators/TemplatedGenerator.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Scriban; - -namespace Generators; - -// this should be in a shared project but the build hates that sometimes and it isn't worth figuring out right now -// see https://github.com/dotnet/roslyn/discussions/47517 -public abstract class TemplatedGenerator : ISourceGenerator -{ - protected abstract string SourceName { get; } - - public void Initialize(GeneratorInitializationContext context) { } - - public void Execute(GeneratorExecutionContext context) - { - var template = Template.Parse(this.GetContent(this.GetType().Name + ".sbntxt")); - var renderedSource = template.Render(this.GetModel(context), member => member.Name); - - var sourceText = SourceText.From(renderedSource, Encoding.UTF8); - - context.AddSource(this.SourceName, sourceText); - } - - protected abstract object GetModel(GeneratorExecutionContext context); - - public string GetContent(string relativePath) - { - var assembly = this.GetType().Assembly; - var baseName = assembly.GetName().Name; - var resourceName = relativePath - .TrimStart('.') - .Replace(Path.DirectorySeparatorChar, '.') - .Replace(Path.AltDirectorySeparatorChar, '.'); - - var name = baseName + "." + resourceName; - using var stream = assembly.GetManifestResourceStream(name); - - if (stream == null) - { - var list = assembly.GetManifestResourceNames(); - - throw new Exception( - $"No embedded resource found with the name {name}. Resources found are " - + string.Join(", ", list) - ); - } - - using var reader = new StreamReader(stream); - return reader.ReadToEnd(); - } -} diff --git a/Src/CSharpier.Playground/ClientApp/.idea/prettier.xml b/Src/CSharpier.Playground/ClientApp/.idea/prettier.xml new file mode 100644 index 000000000..b0ab31a95 --- /dev/null +++ b/Src/CSharpier.Playground/ClientApp/.idea/prettier.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Src/CSharpier.Playground/ClientApp/package-lock.json b/Src/CSharpier.Playground/ClientApp/package-lock.json index 2939706ec..6d6081f68 100644 --- a/Src/CSharpier.Playground/ClientApp/package-lock.json +++ b/Src/CSharpier.Playground/ClientApp/package-lock.json @@ -15,6 +15,8 @@ "@types/react-router-dom": "5.1.7", "@vitejs/plugin-react": "1.3.2", "codemirror": "5.65.4", + "mobx": "6.9.0", + "mobx-react-lite": "4.0.2", "prettier": "2.2.1", "react": "16.14.0", "react-codemirror2": "7.2.1", @@ -714,11 +716,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1291,9 +1293,9 @@ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1603,6 +1605,39 @@ "node": "*" } }, + "node_modules/mobx": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.9.0.tgz", + "integrity": "sha512-HdKewQEREEJgsWnErClfbFoVebze6rGazxFLU/XUyrII8dORfVszN1V0BMRnQSzcgsNNtkX8DHj3nC6cdWE9YQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react-lite": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.2.tgz", + "integrity": "sha512-5o7for7/5QLpgzKvA3ZjrO8TmfkZgVcSjwYalGlW6OkK/fCxmVlwbFeoWJQzWbkWSxlcaatTO0j4riAUvWoqMg==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1734,9 +1769,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dependencies": { "isarray": "0.0.1" } @@ -2199,6 +2234,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -3014,11 +3057,11 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -3335,9 +3378,9 @@ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -3571,6 +3614,19 @@ "brace-expansion": "^1.1.7" } }, + "mobx": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.9.0.tgz", + "integrity": "sha512-HdKewQEREEJgsWnErClfbFoVebze6rGazxFLU/XUyrII8dORfVszN1V0BMRnQSzcgsNNtkX8DHj3nC6cdWE9YQ==" + }, + "mobx-react-lite": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.2.tgz", + "integrity": "sha512-5o7for7/5QLpgzKvA3ZjrO8TmfkZgVcSjwYalGlW6OkK/fCxmVlwbFeoWJQzWbkWSxlcaatTO0j4riAUvWoqMg==", + "requires": { + "use-sync-external-store": "^1.2.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3663,9 +3719,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "requires": { "isarray": "0.0.1" } @@ -3988,6 +4044,12 @@ "use-isomorphic-layout-effect": "^1.0.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", diff --git a/Src/CSharpier.Playground/ClientApp/package.json b/Src/CSharpier.Playground/ClientApp/package.json index 7414dfea1..6c928b5ce 100644 --- a/Src/CSharpier.Playground/ClientApp/package.json +++ b/Src/CSharpier.Playground/ClientApp/package.json @@ -10,6 +10,8 @@ "@types/react-router-dom": "5.1.7", "@vitejs/plugin-react": "1.3.2", "codemirror": "5.65.4", + "mobx": "6.9.0", + "mobx-react-lite": "4.0.2", "prettier": "2.2.1", "react": "16.14.0", "react-codemirror2": "7.2.1", diff --git a/Src/CSharpier.Playground/ClientApp/src/App.tsx b/Src/CSharpier.Playground/ClientApp/src/App.tsx index 04ac69544..5741f69d2 100644 --- a/Src/CSharpier.Playground/ClientApp/src/App.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/App.tsx @@ -1,11 +1,18 @@ -import { AppContext, useSetupAppContext } from "./AppContext"; import React from "react"; import { Layout } from "./Layout"; +import { AppState, AppStateContext } from "./AppContext"; +import { configure } from "mobx"; + +const appState = new AppState(); + +configure({ + enforceActions: "always", +}); export const App = () => { return ( - + - + ); }; diff --git a/Src/CSharpier.Playground/ClientApp/src/AppContext.ts b/Src/CSharpier.Playground/ClientApp/src/AppContext.ts index eb654a3d1..14ef684e1 100644 --- a/Src/CSharpier.Playground/ClientApp/src/AppContext.ts +++ b/Src/CSharpier.Playground/ClientApp/src/AppContext.ts @@ -1,142 +1,140 @@ -import React, { useContext, useState } from "react"; +import { createContext, useContext } from "react"; +import { makeAutoObservable, runInAction } from "mobx"; import { formatCode, setFormattedCodeEditor } from "./FormatCode"; -export const AppContext = React.createContext({ - printWidth: 100, - setPrintWidth: (value: number) => {}, - indentSize: 4, - setIndentSize: (value: number) => {}, - useTabs: false, - setUseTabs: (value: boolean) => {}, - parser: "CSharp", - setParser: (value: string) => {}, - showAst: false, - setShowAst: (value: boolean) => {}, - showDoc: false, - setShowDoc: (value: boolean) => {}, - hideNull: false, - setHideNull: (value: boolean) => {}, - doc: "", - setDoc: (doc: string) => {}, - isLoading: false, - setIsLoading: (isLoading: boolean) => {}, - hasErrors: false, - setHasErrors: (hasErrors: boolean) => {}, - syntaxTree: undefined as object | undefined, - setSyntaxTree: (syntaxTree: undefined | object) => {}, - formattedCode: "", - setFormattedCode: (formattedCode: string) => {}, - enteredCode: "", - setEnteredCode: (enteredCode: string) => {}, - formatCode: () => {}, - setFormattedCodeEditor: (value: unknown) => {}, - setEmptyMethod: () => {}, - setEmptyClass: () => {}, - copyLeft: () => {}, -}); - -export const useAppContext = () => useContext(AppContext); - -// I regret trying out this approach to managing state.... -export const useSetupAppContext = () => { - const [doc, setDoc] = useState(""); - 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()); - const [formattedCode, setFormattedCode] = useState(""); - const [enteredCode, setEnteredCode] = useState(getInitialCode()); - const [isLoading, setIsLoading] = useState(false); - const [hasErrors, setHasErrors] = useState(false); - const [syntaxTree, setSyntaxTree] = useState(undefined); - - return { - doc, - printWidth, - setPrintWidth, - indentSize, - setIndentSize, - useTabs, - setUseTabs, - parser, - setParser, - showAst, - setShowAst: (value: boolean) => { - window.sessionStorage.setItem("showAst", value.toString()); - setShowAst(value); - }, - showDoc, - setShowDoc: (value: boolean) => { - window.sessionStorage.setItem("showDoc", value.toString()); - setShowDoc(value); - }, - hideNull, - setHideNull: (value: boolean) => { - window.sessionStorage.setItem("hideNull", value.toString()); - setHideNull(value); - }, - setDoc, - isLoading, - setIsLoading, - hasErrors, - setHasErrors, - syntaxTree, - setSyntaxTree, - formattedCode, - setFormattedCode, - enteredCode, - setEnteredCode: (value: string) => { - window.sessionStorage.setItem("enteredCode", value); - setEnteredCode(value); - }, - formatCode: async () => { - setIsLoading(true); - - const { syntaxTree, formattedCode, doc, hasErrors, syntaxValidation } = await formatCode( - enteredCode, - printWidth, - indentSize, - useTabs, - parser, - ); +const getString = (key: string, defaultValue: string) => { + const result = window.sessionStorage.getItem(key); + if (result === null) { + return defaultValue; + } + + return result; +}; + +const getBoolean = (key: string, defaultValue: boolean) => { + const result = window.sessionStorage.getItem(key); + if (result === null) { + return defaultValue; + } + + return !!result; +}; + +const getNumber = (key: string, defaultValue: number) => { + const result = window.sessionStorage.getItem(key); + if (result === null) { + return defaultValue; + } + + return parseInt(result, 10); +}; + +class AppState { + printWidth = getNumber("printWidth", 100); + indentSize = getNumber("indentSize", 4); + useTabs = getBoolean("useTabs", false); + formatter = getString("formatter", "CSharp"); + showAst = getBoolean("showAst", false); + showDoc = getBoolean("showDoc", false); + hideNull = getBoolean("hideNull", false); + doc = ""; + isLoading = false; + hasErrors = false; + syntaxTree = undefined as object | undefined; + formattedCode = ""; + enteredCode = window.sessionStorage.getItem("enteredCode") ?? defaultCs; + + constructor() { + makeAutoObservable(this); + } + + setFormatter = (value: string) => { + window.sessionStorage.setItem("formatter", value); + this.formatter = value; + }; + + setIndentSize = (value: number) => { + window.sessionStorage.setItem("indentSize", value.toString(10)); + this.indentSize = value; + }; + + setPrintWidth = (value: number) => { + window.sessionStorage.setItem("printWidth", value.toString(10)); + this.printWidth = value; + }; + + setUseTabs = (value: boolean) => { + window.sessionStorage.setItem("useTabs", value.toString()); + this.useTabs = value; + }; + + setShowAst = (value: boolean) => { + window.sessionStorage.setItem("showAst", value.toString()); + this.showAst = value; + }; - if (syntaxValidation) { - console.log(syntaxValidation); - console.log(new Date()); - } - - setIsLoading(false); - setSyntaxTree(syntaxTree); - setFormattedCode(formattedCode); - setDoc(doc); - setHasErrors(hasErrors); - }, - setFormattedCodeEditor: setFormattedCodeEditor, - setEmptyMethod: () => { - setEnteredCode(`class ClassName + setShowDoc = (value: boolean) => { + window.sessionStorage.setItem("showDoc", value.toString()); + this.showDoc = value; + }; + + setHideNull = (value: boolean) => { + window.sessionStorage.setItem("hideNull", value.toString()); + this.hideNull = value; + }; + + setEnteredCode = (value: string) => { + window.sessionStorage.setItem("enteredCode", value); + this.enteredCode = value; + }; + + setEmptyMethod = () => { + this.setEnteredCode(`class ClassName { void MethodName() { HERE } }`); - }, - setEmptyClass: () => { - setEnteredCode(`class ClassName + }; + + setEmptyClass = () => { + this.setEnteredCode(`class ClassName { HERE }`); - }, - copyLeft: () => { - setEnteredCode(formattedCode); - }, }; -}; -const defaultCode = `public class ClassName { + copyLeft = () => { + this.setEnteredCode(this.formattedCode); + }; + + formatCode = () => { + (async () => { + this.isLoading = true; + + const { syntaxTree, formattedCode, doc, hasErrors } = await formatCode( + this.enteredCode, + this.printWidth, + this.indentSize, + this.useTabs, + this.formatter, + ); + + runInAction(() => { + this.isLoading = false; + this.syntaxTree = syntaxTree; + this.formattedCode = formattedCode; + this.doc = doc; + this.hasErrors = hasErrors; + }); + })(); + }; + + setFormattedCodeEditor = setFormattedCodeEditor; +} + +export const defaultCs = `public class ClassName { public string ShortPropertyName { get; set; @@ -147,15 +145,14 @@ const defaultCode = `public class ClassName { } }`; -const getInitialCode = () => { - return window.sessionStorage.getItem("enteredCode") ?? defaultCode; -}; -const getInitialShowAst = () => { - return window.sessionStorage.getItem("showAst") === "true"; -}; -const getInitialShowDoc = () => { - return window.sessionStorage.getItem("showDoc") === "true"; -}; -const getInitialHideNull = () => { - return window.sessionStorage.getItem("hideNull") === "true"; -}; +const defaultCsProj = ` + + 4 + +`; + +export { AppState }; + +export const AppStateContext = createContext({} as any); + +export const useAppContext = () => useContext(AppStateContext); diff --git a/Src/CSharpier.Playground/ClientApp/src/CodeEditor.tsx b/Src/CSharpier.Playground/ClientApp/src/CodeEditor.tsx index db8b2cec1..3c4d1a6d3 100644 --- a/Src/CSharpier.Playground/ClientApp/src/CodeEditor.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/CodeEditor.tsx @@ -2,10 +2,11 @@ import React, { useEffect } from "react"; import { Controlled as CodeMirror } from "react-codemirror2"; import "codemirror/lib/codemirror.css"; import "codemirror/mode/clike/clike"; -import { useAppContext } from "./AppContext"; import { useOptions } from "./Hooks"; +import { useAppContext } from "./AppContext"; +import { observer } from "mobx-react-lite"; -export const CodeEditor = () => { +export const CodeEditor = observer(() => { const { formatCode, enteredCode, setEnteredCode } = useAppContext(); useEffect(() => { formatCode(); @@ -23,4 +24,4 @@ export const CodeEditor = () => { onChange={() => {}} /> ); -}; +}); diff --git a/Src/CSharpier.Playground/ClientApp/src/Controls.css b/Src/CSharpier.Playground/ClientApp/src/Controls.css index b44b77c03..2a11be3ad 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Controls.css +++ b/Src/CSharpier.Playground/ClientApp/src/Controls.css @@ -31,6 +31,12 @@ margin-top: 10px; } +.controlsWrapper select { + width: 100%; + margin-top: 10px; + padding: 3px 4px; +} + .smallButton { background-color: #ccc; border-radius: 4px; diff --git a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx index fef3d50ce..3eec52dda 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx @@ -1,9 +1,18 @@ import React from "react"; -import { useAppContext } from "./AppContext"; import "./Controls.css"; +import { observer } from "mobx-react-lite"; +import { useAppContext } from "./AppContext"; -export const Controls = () => { +export const Controls = observer(() => { const { + printWidth, + setPrintWidth, + indentSize, + setIndentSize, + useTabs, + setUseTabs, + formatter, + setFormatter, showDoc, setShowDoc, hideNull, @@ -13,14 +22,6 @@ export const Controls = () => { setEmptyMethod, setEmptyClass, copyLeft, - printWidth, - setPrintWidth, - indentSize, - setIndentSize, - useTabs, - setUseTabs, - parser, - setParser, } = useAppContext(); return (
@@ -41,9 +42,10 @@ export const Controls = () => { Use Tabs - setFormatter(e.target.value)}> +
@@ -92,4 +94,4 @@ export const Controls = () => {
); -}; +}); diff --git a/Src/CSharpier.Playground/ClientApp/src/DocTree.tsx b/Src/CSharpier.Playground/ClientApp/src/DocTree.tsx index 3b4466d78..c41b058ea 100644 --- a/Src/CSharpier.Playground/ClientApp/src/DocTree.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/DocTree.tsx @@ -1,6 +1,7 @@ -import React, { useEffect } from "react"; +import React from "react"; import { UnControlled as CodeMirror } from "react-codemirror2"; import { useAppContext } from "./AppContext"; +import { observer } from "mobx-react-lite"; const options = { mode: { @@ -14,9 +15,9 @@ const options = { const regex = /\s+Doc\.Null,/g; -export const DocTree = () => { +export const DocTree = observer(() => { const { doc, hideNull } = useAppContext(); const docToDisplay = hideNull ? doc.replaceAll(regex, "") : doc; return {}} onChange={() => {}} />; -}; +}); diff --git a/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts b/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts index 9ec4f25af..ac1e23e30 100644 --- a/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts +++ b/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts @@ -2,11 +2,17 @@ let gutters: any[] = []; let marks: any[] = []; let editor: any = undefined; -export const formatCode = async (code: string, printWidth: number, indentSize: number, useTabs: boolean, parser: string) => { +export const formatCode = async ( + code: string, + printWidth: number, + indentSize: number, + useTabs: boolean, + formatter: string, +) => { const makeRequest = async () => { const response = await fetch("/Format", { method: "POST", - body: JSON.stringify({ code, printWidth, indentSize, useTabs, parser }), + body: JSON.stringify({ code, printWidth, indentSize, useTabs, formatter }), headers: { "Content-Type": "application/json", "cache-control": "no-cache", diff --git a/Src/CSharpier.Playground/ClientApp/src/FormattedCode.tsx b/Src/CSharpier.Playground/ClientApp/src/FormattedCode.tsx index 67ca93084..ccb28b501 100644 --- a/Src/CSharpier.Playground/ClientApp/src/FormattedCode.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/FormattedCode.tsx @@ -1,9 +1,10 @@ import { Controlled as CodeMirror } from "react-codemirror2"; import React from "react"; -import { useAppContext } from "./AppContext"; import { useOptions } from "./Hooks"; +import { useAppContext } from "./AppContext"; +import { observer } from "mobx-react-lite"; -export const FormattedCode = () => { +export const FormattedCode = observer(() => { const { formattedCode, setFormattedCodeEditor } = useAppContext(); const options = useOptions(); @@ -20,4 +21,4 @@ export const FormattedCode = () => { onChange={() => {}} /> ); -}; +}); diff --git a/Src/CSharpier.Playground/ClientApp/src/Header.tsx b/Src/CSharpier.Playground/ClientApp/src/Header.tsx index feca55a3e..e5383ae58 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Header.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Header.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useState } from "react"; import { Loading } from "./Icons/Loading"; -import { useAppContext } from "./AppContext"; import "./Header.css"; +import { useAppContext } from "./AppContext"; +import { observer } from "mobx-react-lite"; -export const Header = () => { +export const Header = observer(() => { const { isLoading, formatCode, showDoc, showAst } = useAppContext(); const width = showDoc && showAst ? 25 : showDoc || showAst ? 33 : 50; const [version, setVersion] = useState(); @@ -39,4 +40,4 @@ export const Header = () => { ); -}; +}); diff --git a/Src/CSharpier.Playground/ClientApp/src/Layout.tsx b/Src/CSharpier.Playground/ClientApp/src/Layout.tsx index 5bdc6ccb6..21e0ebf93 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Layout.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Layout.tsx @@ -4,13 +4,14 @@ import "codemirror/mode/clike/clike"; import { SyntaxTree } from "./SyntaxTree"; import { DocTree } from "./DocTree"; import { Header } from "./Header"; -import { useAppContext } from "./AppContext"; import { CodeEditor } from "./CodeEditor"; import { FormattedCode } from "./FormattedCode"; import { Controls } from "./Controls"; import "./Layout.scss"; +import { useAppContext } from "./AppContext"; +import { observer } from "mobx-react-lite"; -export const Layout = () => { +export const Layout = observer(() => { const { showDoc, showAst } = useAppContext(); const width = showDoc && showAst ? 25 : showDoc || showAst ? 33 : 50; return ( @@ -32,4 +33,4 @@ export const Layout = () => {
); -}; +}); diff --git a/Src/CSharpier.Playground/ClientApp/src/SyntaxTree.tsx b/Src/CSharpier.Playground/ClientApp/src/SyntaxTree.tsx index 997d165fa..e53a682d6 100644 --- a/Src/CSharpier.Playground/ClientApp/src/SyntaxTree.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/SyntaxTree.tsx @@ -1,7 +1,8 @@ import React from "react"; import ReactJson, { CollapsedFieldProps } from "react-json-view"; -import { useAppContext } from "./AppContext"; import "./SyntaxTree.css"; +import { useAppContext } from "./AppContext"; +import { observer } from "mobx-react-lite"; const shouldCollapse = (field: CollapsedFieldProps) => { if ( @@ -15,7 +16,7 @@ const shouldCollapse = (field: CollapsedFieldProps) => { return false; }; -export const SyntaxTree = () => { +export const SyntaxTree = observer(() => { const { syntaxTree } = useAppContext(); if (!syntaxTree) { return null; @@ -34,4 +35,4 @@ export const SyntaxTree = () => { /> ); -}; +}); diff --git a/Src/CSharpier.Playground/Controllers/FormatController.cs b/Src/CSharpier.Playground/Controllers/FormatController.cs index 07a74a617..57d00decc 100644 --- a/Src/CSharpier.Playground/Controllers/FormatController.cs +++ b/Src/CSharpier.Playground/Controllers/FormatController.cs @@ -1,16 +1,9 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis; -using Microsoft.Extensions.Logging; namespace CSharpier.Playground.Controllers; -using CSharpier.Utilities; - public class FormatResult { public required string Code { get; set; } @@ -30,16 +23,10 @@ public class FormatError [Route("[controller]")] public class FormatController : ControllerBase { - private readonly IWebHostEnvironment webHostEnvironment; private readonly ILogger logger; - // ReSharper disable once SuggestBaseTypeForParameter - public FormatController( - IWebHostEnvironment webHostEnvironment, - ILogger logger - ) + public FormatController(ILogger logger) { - this.webHostEnvironment = webHostEnvironment; this.logger = logger; } @@ -49,7 +36,7 @@ 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; + public string Formatter { get; set; } = string.Empty; } [HttpPost] @@ -58,10 +45,12 @@ public async Task Post( CancellationToken cancellationToken ) { - var sourceCodeKind = model.Parser.EqualsIgnoreCase("CSharp") - ? SourceCodeKind.Regular - : SourceCodeKind.Script; - var result = await CSharpFormatter.FormatAsync( + if (!Enum.TryParse(model.Formatter, ignoreCase: true, out var parsedFormatter)) + { + throw new Exception("Invalid formatter " + model.Formatter); + } + + var result = await CodeFormatter.FormatAsync( model.Code, new PrinterOptions { @@ -70,17 +59,18 @@ CancellationToken cancellationToken Width = model.PrintWidth, IndentSize = model.IndentSize, UseTabs = model.UseTabs, + Formatter = parsedFormatter, }, - sourceCodeKind, cancellationToken ); + // TODO xml what about this? var comparer = new SyntaxNodeComparer( model.Code, result.Code, result.ReorderedModifiers, result.ReorderedUsingsWithDisabledText, - sourceCodeKind, + parsedFormatter is Formatter.CSharp ? SourceCodeKind.Regular : SourceCodeKind.Script, cancellationToken ); diff --git a/Src/CSharpier.Tests.Generators/CSharpier.Tests.Generators.csproj b/Src/CSharpier.Tests.Generators/CSharpier.Tests.Generators.csproj index 5dfcd5111..196a8fca1 100644 --- a/Src/CSharpier.Tests.Generators/CSharpier.Tests.Generators.csproj +++ b/Src/CSharpier.Tests.Generators/CSharpier.Tests.Generators.csproj @@ -14,9 +14,4 @@ - - - TemplatedGenerator.cs - - diff --git a/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.cs b/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.cs index 2320850c9..b0aa77f51 100644 --- a/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.cs +++ b/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.cs @@ -1,20 +1,57 @@ -using Generators; +using System.IO; +using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Scriban; namespace CSharpier.Tests.Generators; +// the magic command to get source generators to actually regenerate when they get stuck with old code +// dotnet build-server shutdown [Generator] -public class FormattingTestsGenerator : TemplatedGenerator +public class FormattingTestsGenerator : ISourceGenerator { - protected override string SourceName => "FormattingTests"; + public void Initialize(GeneratorInitializationContext context) { } - protected override object GetModel(GeneratorExecutionContext context) + public void Execute(GeneratorExecutionContext context) + { + var template = Template.Parse(this.GetContent(this.GetType().Name + ".sbntxt")); + + var extensions = this.GetExtensions(context).ToList(); + foreach (var extension in extensions) + { + var renderedSource = template.Render( + this.GetModel(context, extension), + member => member.Name + ); + + var sourceText = SourceText.From(renderedSource, Encoding.UTF8); + + context.AddSource("FormattingTests_" + extension, sourceText); + } + } + + private IEnumerable GetExtensions(GeneratorExecutionContext context) + { + return context + .AdditionalFiles.Where(o => + o.Path.EndsWith(".test") + && !o.Path.EndsWith(".actual.test") + && !o.Path.EndsWith(".expected.test") + ) + .Select(o => new FileInfo(o.Path).Directory!.Name) + .Distinct(); + } + + protected object GetModel(GeneratorExecutionContext context, string extension) { var tests = context .AdditionalFiles.Where(o => o.Path.EndsWith(".test") && !o.Path.EndsWith(".actual.test") && !o.Path.EndsWith(".expected.test") + && new FileInfo(o.Path).Directory!.Name == extension ) .Select(o => new { @@ -23,6 +60,32 @@ protected override object GetModel(GeneratorExecutionContext context) UseTabs = Path.GetFileNameWithoutExtension(o.Path).EndsWith("_Tabs"), }); - return new { Tests = tests }; + return new { Tests = tests, FileExtension = extension }; + } + + public string GetContent(string relativePath) + { + var assembly = this.GetType().Assembly; + var baseName = assembly.GetName().Name; + var resourceName = relativePath + .TrimStart('.') + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + + var name = baseName + "." + resourceName; + using var stream = assembly.GetManifestResourceStream(name); + + if (stream == null) + { + var list = assembly.GetManifestResourceNames(); + + throw new Exception( + $"No embedded resource found with the name {name}. Resources found are " + + string.Join(", ", list) + ); + } + + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); } } diff --git a/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.sbntxt b/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.sbntxt index 78908ab97..80f39280b 100644 --- a/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.sbntxt +++ b/Src/CSharpier.Tests.Generators/FormattingTestsGenerator.sbntxt @@ -4,7 +4,7 @@ namespace CSharpier.Tests.FormattingTests { [TestFixture] [Parallelizable(ParallelScope.All)] - public class FormattingTests : BaseTest + public class FormattingTests_{{ FileExtension }} : BaseTest { {{- for test in Tests }} [Test] diff --git a/Src/CSharpier.Tests/CSharpier.Tests.csproj b/Src/CSharpier.Tests/CSharpier.Tests.csproj index c646b2636..4a5caa83f 100644 --- a/Src/CSharpier.Tests/CSharpier.Tests.csproj +++ b/Src/CSharpier.Tests/CSharpier.Tests.csproj @@ -30,7 +30,4 @@ $([System.String]::Copy(%(Filename)).Replace('.expected', '.test')) - - - diff --git a/Src/CSharpier.Tests/CSharpierIgnoreTests.cs b/Src/CSharpier.Tests/CSharpierIgnoreTests.cs index 264d3e1d2..6f2a12399 100644 --- a/Src/CSharpier.Tests/CSharpierIgnoreTests.cs +++ b/Src/CSharpier.Tests/CSharpierIgnoreTests.cs @@ -1,10 +1,9 @@ +using CSharpier.SyntaxPrinter; +using FluentAssertions; using NUnit.Framework; namespace CSharpier.Tests; -using CSharpier.SyntaxPrinter; -using FluentAssertions; - [TestFixture] public class CSharpierIgnoreTests { diff --git a/Src/CSharpier.Tests/Cli/Options/ConfigFileParserTests.cs b/Src/CSharpier.Tests/Cli/Options/ConfigFileParserTests.cs index 8dbf3cd1f..d206bb708 100644 --- a/Src/CSharpier.Tests/Cli/Options/ConfigFileParserTests.cs +++ b/Src/CSharpier.Tests/Cli/Options/ConfigFileParserTests.cs @@ -1,9 +1,9 @@ -namespace CSharpier.Tests.Cli.Options; - using CSharpier.Cli.Options; using FluentAssertions; using NUnit.Framework; +namespace CSharpier.Tests.Cli.Options; + [TestFixture] public class ConfigFileParserTests { diff --git a/Src/CSharpier.Tests/CodeFormatterTests.cs b/Src/CSharpier.Tests/CodeFormatterTests.cs index da686609c..3c1b6cb5d 100644 --- a/Src/CSharpier.Tests/CodeFormatterTests.cs +++ b/Src/CSharpier.Tests/CodeFormatterTests.cs @@ -4,37 +4,10 @@ namespace CSharpier.Tests; -using CSharpier.Utilities; - -// TODO xml move these around [TestFixture] [Parallelizable(ParallelScope.All)] internal class CodeFormatterTests { - [TestCase(EndOfLine.LF, "\n")] - [TestCase(EndOfLine.CRLF, "\r\n")] - public void GetLineEndings_Should_Return_Easy_Cases(EndOfLine endOfLine, string expected) - { - var code = "tester\n"; - var result = PrinterOptions.GetLineEnding( - code, - new PrinterOptions { EndOfLine = endOfLine } - ); - - result.Should().Be(expected); - } - - [TestCase("tester\n", "\n")] - [TestCase("tester\r\n", "\r\n")] - [TestCase("\ntester\n", "\n")] - [TestCase("tester", "\n")] - public void GetLineEndings_With_Auto_Should_Detect(string code, string expected) - { - var result = PrinterOptions.GetLineEnding(code, new PrinterOptions()); - - result.Should().Be(expected); - } - [Test] public void Format_Should_Use_Default_Width() { diff --git a/Src/CSharpier.Tests/CommandLineFormatterTests.cs b/Src/CSharpier.Tests/CommandLineFormatterTests.cs index 94c5748dc..160867e8f 100644 --- a/Src/CSharpier.Tests/CommandLineFormatterTests.cs +++ b/Src/CSharpier.Tests/CommandLineFormatterTests.cs @@ -2,12 +2,11 @@ using System.Text; using CSharpier.Cli; using FluentAssertions; +using Microsoft.Extensions.Logging; using NUnit.Framework; namespace CSharpier.Tests; -using Microsoft.Extensions.Logging; - [TestFixture] [Parallelizable(ParallelScope.All)] public class CommandLineFormatterTests diff --git a/Src/CSharpier.Tests/Formatters/CSharp/PreprocessorSymbolsTests.cs b/Src/CSharpier.Tests/Formatters/CSharp/PreprocessorSymbolsTests.cs index ffff8d9ad..65ad6acee 100644 --- a/Src/CSharpier.Tests/Formatters/CSharp/PreprocessorSymbolsTests.cs +++ b/Src/CSharpier.Tests/Formatters/CSharp/PreprocessorSymbolsTests.cs @@ -1,9 +1,9 @@ -namespace CSharpier.Tests.Formatters.CSharp; - using CSharpier.Formatters.CSharp; using FluentAssertions; using NUnit.Framework; +namespace CSharpier.Tests.Formatters.CSharp; + [TestFixture] public class PreprocessorSymbolsTests { @@ -199,8 +199,6 @@ public void GetSets_Should_Handle_Nested_If() #endif #endif ", - // TODO because the second is a subset of the first, we don't actually need it - // but it's more work to figure that out for now "ONE,TWO", "ONE" ); diff --git a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs index 5c6ad7da6..72a8c780e 100644 --- a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs +++ b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs @@ -2,25 +2,28 @@ using System.Text; using CSharpier.Cli; using CSharpier.SyntaxPrinter; +using CSharpier.Utilities; using DiffEngine; using FluentAssertions; +using Microsoft.CodeAnalysis; namespace CSharpier.Tests.FormattingTests; -using CSharpier.Utilities; -using Microsoft.CodeAnalysis; - public class BaseTest { private readonly DirectoryInfo rootDirectory = DirectoryFinder.FindParent("CSharpier.Tests"); - protected async Task RunTest(string fileName, string fileExtension, bool useTabs = false) + protected async Task RunTest( + string fileName, + string fileExtensionWithoutDot, + bool useTabs = false + ) { var filePath = Path.Combine( this.rootDirectory.FullName, "FormattingTests", "TestFiles", - fileExtension, + fileExtensionWithoutDot, fileName + ".test" ); var fileReaderResult = await FileReader.ReadFileAsync( @@ -29,10 +32,22 @@ protected async Task RunTest(string fileName, string fileExtension, bool useTabs CancellationToken.None ); - var result = await CSharpFormatter.FormatAsync( + var formatter = fileExtensionWithoutDot switch + { + "cs" => Formatter.CSharp, + "csx" => Formatter.CSharpScript, + "xml" => Formatter.XML, + _ => Formatter.Unknown, + }; + + var result = await CodeFormatter.FormatAsync( fileReaderResult.FileContents, - new PrinterOptions { Width = PrinterOptions.WidthUsedByTests, UseTabs = useTabs }, - fileExtension.EqualsIgnoreCase("csx") ? SourceCodeKind.Script : SourceCodeKind.Regular, + new PrinterOptions + { + Width = PrinterOptions.WidthUsedByTests, + UseTabs = useTabs, + Formatter = formatter, + }, CancellationToken.None ); @@ -58,6 +73,7 @@ protected async Task RunTest(string fileName, string fileExtension, bool useTabs normalizedCode = normalizedCode.Replace("\r\n", "\n"); } + // TODO xml what about this? var comparer = new SyntaxNodeComparer( expectedCode, normalizedCode, diff --git a/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs b/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs index 74596cb11..02cd37407 100644 --- a/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs +++ b/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs @@ -1,9 +1,9 @@ -namespace CSharpier.Tests.FormattingTests; - using System.Text; using FluentAssertions; using NUnit.Framework; +namespace CSharpier.Tests.FormattingTests; + [TestFixture] internal class LineEndingEdgeCase { diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Attributes.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Attributes.test new file mode 100644 index 000000000..f5c97bca6 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Attributes.test @@ -0,0 +1,14 @@ + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/BasicProject.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/BasicProject.test new file mode 100644 index 000000000..8c1a5dc4b --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/BasicProject.test @@ -0,0 +1,5 @@ + + + 4 + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Comments.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Comments.test new file mode 100644 index 000000000..7ad24f919 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Comments.test @@ -0,0 +1,4 @@ + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Conditions.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Conditions.test new file mode 100644 index 000000000..f0a27341a --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Conditions.test @@ -0,0 +1,9 @@ + + + v4.5.2 + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements.test new file mode 100644 index 000000000..8d38d2d14 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements.test @@ -0,0 +1,14 @@ + + + + Text + TextValue + TextValue + TextValue + TextValue + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/LongAttributes.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/LongAttributes.test new file mode 100644 index 000000000..036644c51 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/LongAttributes.test @@ -0,0 +1,98 @@ + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/RetainXmlElement.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/RetainXmlElement.test new file mode 100644 index 000000000..820a64750 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/RetainXmlElement.test @@ -0,0 +1,2 @@ + + diff --git a/Src/CSharpier.Tests/OptionsProviderTests.cs b/Src/CSharpier.Tests/OptionsProviderTests.cs index be40d21ba..acff098aa 100644 --- a/Src/CSharpier.Tests/OptionsProviderTests.cs +++ b/Src/CSharpier.Tests/OptionsProviderTests.cs @@ -1,11 +1,11 @@ -namespace CSharpier.Tests; - using System.IO.Abstractions.TestingHelpers; using CSharpier.Cli.Options; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; +namespace CSharpier.Tests; + [TestFixture] [Parallelizable(ParallelScope.Fixtures)] public class OptionsProviderTests diff --git a/Src/CSharpier.Tests/PrinterOptionsTests.cs b/Src/CSharpier.Tests/PrinterOptionsTests.cs new file mode 100644 index 000000000..080e071e9 --- /dev/null +++ b/Src/CSharpier.Tests/PrinterOptionsTests.cs @@ -0,0 +1,33 @@ +using FluentAssertions; +using NUnit.Framework; + +namespace CSharpier.Tests; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +internal class PrinterOptionsTests +{ + [TestCase(EndOfLine.LF, "\n")] + [TestCase(EndOfLine.CRLF, "\r\n")] + public void GetLineEndings_Should_Return_Easy_Cases(EndOfLine endOfLine, string expected) + { + var code = "tester\n"; + var result = PrinterOptions.GetLineEnding( + code, + new PrinterOptions { EndOfLine = endOfLine } + ); + + result.Should().Be(expected); + } + + [TestCase("tester\n", "\n")] + [TestCase("tester\r\n", "\r\n")] + [TestCase("\ntester\n", "\n")] + [TestCase("tester", "\n")] + public void GetLineEndings_With_Auto_Should_Detect(string code, string expected) + { + var result = PrinterOptions.GetLineEnding(code, new PrinterOptions()); + + result.Should().Be(expected); + } +} diff --git a/Src/CSharpier.Tests/Samples/AllInOne.test b/Src/CSharpier.Tests/Samples/AllInOne.test index 5f282702b..1d8ef2950 100644 --- a/Src/CSharpier.Tests/Samples/AllInOne.test +++ b/Src/CSharpier.Tests/Samples/AllInOne.test @@ -1 +1 @@ - \ No newline at end of file +Doc.HardLineIfNoPreviousLine \ No newline at end of file diff --git a/Src/CSharpier.Tests/Samples/Samples.cs b/Src/CSharpier.Tests/Samples/Samples.cs index 8cc6e5f76..5392747f3 100644 --- a/Src/CSharpier.Tests/Samples/Samples.cs +++ b/Src/CSharpier.Tests/Samples/Samples.cs @@ -1,11 +1,10 @@ using System.Text; using FluentAssertions; +using Microsoft.CodeAnalysis; using NUnit.Framework; namespace CSharpier.Tests.Samples; -using Microsoft.CodeAnalysis; - [TestFixture] [Parallelizable(ParallelScope.All)] public class Samples @@ -51,12 +50,12 @@ public static async Task RunTest(string fileName) compareResult.Should().BeEmpty(); await File.WriteAllTextAsync( - file.Replace(".test", ".actual.test"), + file.Replace(".cst", ".actual.test"), result.Code, Encoding.UTF8 ); await File.WriteAllTextAsync( - file.Replace(".test", ".doctree.txt"), + file.Replace(".cst", ".doctree.txt"), result.DocTree, Encoding.UTF8 ); diff --git a/Src/CSharpier.Tests/Samples/Scratch.test b/Src/CSharpier.Tests/Samples/Scratch.test index 5f282702b..1d8ef2950 100644 --- a/Src/CSharpier.Tests/Samples/Scratch.test +++ b/Src/CSharpier.Tests/Samples/Scratch.test @@ -1 +1 @@ - \ No newline at end of file +Doc.HardLineIfNoPreviousLine \ No newline at end of file diff --git a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs index b4ae576e2..aa9f457f0 100644 --- a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs +++ b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs @@ -1,10 +1,9 @@ using FluentAssertions; +using Microsoft.CodeAnalysis; using NUnit.Framework; namespace CSharpier.Tests; -using Microsoft.CodeAnalysis; - [TestFixture] [Parallelizable(ParallelScope.All)] public class SyntaxNodeComparerTests diff --git a/Src/CSharpier/CSharpFormatter.cs b/Src/CSharpier/CSharpFormatter.cs index 46ad87820..45630d66e 100644 --- a/Src/CSharpier/CSharpFormatter.cs +++ b/Src/CSharpier/CSharpFormatter.cs @@ -1,25 +1,14 @@ -namespace CSharpier; - using System.Text; using System.Text.Json; using CSharpier.Formatters.CSharp; using CSharpier.SyntaxPrinter; -internal class CSharpScriptFormatter : CSharpFormatter { } +namespace CSharpier; -internal class CSharpFormatter : IFormatter +internal static class CSharpFormatter { internal static readonly LanguageVersion LanguageVersion = LanguageVersion.Preview; - Task IFormatter.FormatAsync( - string code, - PrinterOptions printerOptions, - CancellationToken cancellationToken - ) - { - return FormatAsync(code, printerOptions, cancellationToken); - } - internal static Task FormatAsync( string code, PrinterOptions printerOptions diff --git a/Src/CSharpier/CodeFormatter.cs b/Src/CSharpier/CodeFormatter.cs index 47a4466ef..ef0645c0c 100644 --- a/Src/CSharpier/CodeFormatter.cs +++ b/Src/CSharpier/CodeFormatter.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using CSharpier.Formatters.Xml; using CSharpier.SyntaxPrinter; namespace CSharpier; @@ -28,7 +29,7 @@ public static Task FormatAsync( IndentSize = options.IndentSize, EndOfLine = options.EndOfLine, IncludeGenerated = options.IncludeGenerated, - Formatter = "csharp", + Formatter = Formatter.CSharp, }, cancellationToken ); @@ -58,10 +59,37 @@ public static Task FormatAsync( UseTabs = options.IndentStyle == IndentStyle.Tabs, IndentSize = options.IndentSize, EndOfLine = options.EndOfLine, - Formatter = "csharp", + Formatter = Formatter.CSharp, }, SourceCodeKind.Regular, cancellationToken ); } + + internal static async Task FormatAsync( + string fileContents, + PrinterOptions options, + CancellationToken cancellationToken + ) + { + return options.Formatter switch + { + Formatter.CSharp + => await CSharpFormatter.FormatAsync( + fileContents, + options, + SourceCodeKind.Regular, + cancellationToken + ), + Formatter.CSharpScript + => await CSharpFormatter.FormatAsync( + fileContents, + options, + SourceCodeKind.Script, + cancellationToken + ), + Formatter.XML => XmlFormatter.Format(fileContents, options), + _ => new CodeFormatterResult { FailureMessage = "Is an unsupported file type." } + }; + } } diff --git a/Src/CSharpier/DocPrinter/DocFitter.cs b/Src/CSharpier/DocPrinter/DocFitter.cs index fa4b7f677..838d376d7 100644 --- a/Src/CSharpier/DocPrinter/DocFitter.cs +++ b/Src/CSharpier/DocPrinter/DocFitter.cs @@ -90,8 +90,9 @@ void Push(Doc doc, PrintMode printMode, Indent indent) case IfBreak ifBreak: { var ifBreakMode = - ifBreak.GroupId != null && groupModeMap!.ContainsKey(ifBreak.GroupId) - ? groupModeMap[ifBreak.GroupId] + ifBreak.GroupId != null + && groupModeMap.TryGetValue(ifBreak.GroupId, out var value) + ? value : currentMode; var contents = diff --git a/Src/CSharpier/DocTypes/Doc.cs b/Src/CSharpier/DocTypes/Doc.cs index 10edb115b..e6f02b13e 100644 --- a/Src/CSharpier/DocTypes/Doc.cs +++ b/Src/CSharpier/DocTypes/Doc.cs @@ -102,6 +102,17 @@ public static Group GroupWithId(string groupId, Doc contents) return group; } + private static int groupNumber; + + public static Group GroupWithNewId(out string groupId, Doc contents) + { + groupId = "Group_" + groupNumber; + Interlocked.Increment(ref groupNumber); + var group = Group(contents); + group.GroupId = groupId; + return group; + } + public static Group GroupWithId(string groupId, params Doc[] contents) { var group = Group(contents); diff --git a/Src/CSharpier/Formatters/CSharp/BooleanExpressionParser.cs b/Src/CSharpier/Formatters/CSharp/BooleanExpressionParser.cs index 8c8f62f28..ff7f112ea 100644 --- a/Src/CSharpier/Formatters/CSharp/BooleanExpressionParser.cs +++ b/Src/CSharpier/Formatters/CSharp/BooleanExpressionParser.cs @@ -1,8 +1,8 @@ -namespace CSharpier.Formatters.CSharp; - using System.Collections.Concurrent; using System.Linq.Expressions; +namespace CSharpier.Formatters.CSharp; + internal abstract class BooleanExpressionParser { private static readonly ConcurrentDictionary parsedExpressions = diff --git a/Src/CSharpier/Formatters/Xml/XmlFormatter.cs b/Src/CSharpier/Formatters/Xml/XmlFormatter.cs new file mode 100644 index 000000000..9567c72cd --- /dev/null +++ b/Src/CSharpier/Formatters/Xml/XmlFormatter.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using System.Xml; +using CSharpier.Formatters.Xml.XmlNodePrinters; + +namespace CSharpier.Formatters.Xml; + +internal static class XmlFormatter +{ + internal static CodeFormatterResult Format(string xml, PrinterOptions printerOptions) + { + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(xml); + + var lineEnding = PrinterOptions.GetLineEnding(xml, printerOptions); + var doc = Node.Print(xmlDocument); + var formattedXml = DocPrinter.DocPrinter.Print(doc, printerOptions, lineEnding); + + return new CodeFormatterResult + { + Code = formattedXml, + DocTree = printerOptions.IncludeDocTree ? DocSerializer.Serialize(doc) : string.Empty, + AST = printerOptions.IncludeAST ? JsonSerializer.Serialize(xmlDocument) : string.Empty, + }; + } +} diff --git a/Src/CSharpier/Formatters/Xml/XmlNodePrinters/Element.cs b/Src/CSharpier/Formatters/Xml/XmlNodePrinters/Element.cs new file mode 100644 index 000000000..60f201d6d --- /dev/null +++ b/Src/CSharpier/Formatters/Xml/XmlNodePrinters/Element.cs @@ -0,0 +1,372 @@ +using System.Xml; + +namespace CSharpier.Formatters.Xml.XmlNodePrinters; + +// TODO it may be worth looking +// at how this works instead of the html one +// https://github.com/prettier/plugin-xml/blob/main/src/printer.js +internal static class Element +{ + internal static Doc Print(XmlElement node) + { + var shouldHugContent = false; + // TODO need any of this? + // node.ChildNodes.Count == 1 && + // (node.firstChild.type == "interpolation" || + // node.firstChild.type == "angularIcuExpression") && + // node.firstChild.isLeadingSpaceSensitive && + // !node.firstChild.hasLeadingSpaces && + // node.lastChild.isTrailingSpaceSensitive && + // !node.lastChild.hasTrailingSpaces; + + // TODO good idea for group names + var attrGroupId = Guid.NewGuid().ToString(); // Symbol("element-attr-group-id"); + + Group PrintTag(Doc doc) => + Doc.Group( + Doc.GroupWithId(attrGroupId, PrintOpeningTag(node)), + doc, + PrintClosingTag(node) + ); + + Doc PrintChildrenDoc(params Doc[] childrenDoc) + { + if (shouldHugContent) + { + return Doc.IndentIfBreak(Doc.Concat(childrenDoc), attrGroupId); + } + + return Doc.Indent(childrenDoc); + } + + Doc PrintLineBeforeChildren() + { + if (shouldHugContent) + { + return Doc.IfBreak(Doc.SoftLine, "", attrGroupId); + } + + // if ( + // node.firstChild.hasLeadingSpaces && + // node.firstChild.isLeadingSpaceSensitive + // ) { + // return Doc.Line; + // } + + // if ( + // node.firstChild.type == "text" && + // node.isWhitespaceSensitive && + // node.isIndentationSensitive + // ) { + // return dedentToRoot(Doc.SoftLine); + // } + + return Doc.SoftLine; + } + ; + + Doc PrintLineAfterChildren() + { + // var needsToBorrow = node.next + // ? needsToBorrowPrevClosingTagEndMarker(node.next) + // : needsToBorrowLastChildClosingTagEndMarker(node.parent); + // if (needsToBorrow) { + // if ( + // node.lastChild.hasTrailingSpaces && + // node.lastChild.isTrailingSpaceSensitive + // ) { + // return " "; + // } + // return ""; + // } + if (shouldHugContent) + { + return Doc.IfBreak(Doc.SoftLine, "", attrGroupId); + } + + // if ( + // node.lastChild.hasTrailingSpaces && + // node.lastChild.isTrailingSpaceSensitive + // ) { + // return line; + // } + // if ( + // (node.lastChild.type == "comment" || + // (node.lastChild.type == "text" && + // node.isWhitespaceSensitive && + // node.isIndentationSensitive)) && + // new RegExp( + // `\\n[\\t ]{${options.tabWidth * (path.ancestors.length - 1)}}$`, + // ).test(node.lastChild.value) + // ) { + // return ""; + // } + + return Doc.SoftLine; + } + ; + + if (node.ChildNodes.Count == 0) + { + return PrintTag( + "" + // node.hasDanglingSpaces && node.isDanglingSpaceSensitive ? Doc.Line : "" + ); + } + + return PrintTag( + Doc.Concat( + ForceBreakContent(node) ? Doc.BreakParent : "", + PrintChildrenDoc(PrintLineBeforeChildren(), ElementChildren.Print(node)), + PrintLineAfterChildren() + ) + ); + + // if (element.IsEmpty) + // { + // return Doc.Doc.Group("<" + element.Name, PrintAttributes(element), Doc.Line, "/>"); + // } + // + // return Doc.Doc.Group( + // Doc.Doc.Group("<" + element.Name, PrintAttributes(element)), + // Doc.Indent(Doc.SoftLine, ">", PrintChildren(element), "" + // ); + + // return Doc.Doc.Group( + // Doc.GroupWithNewId( + // out var groupId, + // Doc.Indent( + // Doc.Doc.Group("<" + element.Name, PrintAttributes(element), Doc.SoftLine, ">"), + // PrintChildren(element) + // ) + // ), + // "" + // ); + } + + private static Doc PrintOpeningTag(XmlElement node) + { + return Doc.Concat("<" + node.Name, PrintAttributes(node), node.IsEmpty ? Doc.Null : ">"); + + // return [ + // printOpeningTagStart(node, options), + // printAttributes(path, options, print), + // node.isSelfClosing ? "" : printOpeningTagEnd(node), + // ]; + } + + private static Doc PrintClosingTag(XmlElement node) + { + return Doc.Concat( + node.IsEmpty ? Doc.Null : PrintClosingTagStart(node), + PrintClosingTagEnd(node) + ); + // return [ + // node.isSelfClosing ? "" : printClosingTagStart(node, options), + // printClosingTagEnd(node, options), + // ]; + } + + private static Doc PrintClosingTagStart(XmlElement node) + { + // return node.lastChild is not null && + // needsToBorrowParentClosingTagStartMarker(node.lastChild) + // ? Doc.Null + // : + + return Doc.Concat(PrintClosingTagPrefix(node), PrintClosingTagStartMarker(node)); + } + + private static Doc PrintClosingTagStartMarker(XmlElement node) + { + // if (shouldNotPrintClosingTag(node)) { + // return ""; + // } + // switch (node.type) { + // case "ieConditionalComment": + // return ""; + // case "ieConditionalStartComment": + // return "]>"; + // case "interpolation": + // return "}}"; + // case "angularIcuExpression": + // return "}"; + // case "element": + // if (node.isSelfClosing) { + // return "/>"; + // } + // // fall through + // default: + + return node.IsEmpty ? "/>" : ">"; + } + + private static Doc PrintClosingTagSuffix(XmlElement node) + { + return Doc.Null; + // return needsToBorrowParentClosingTagStartMarker(node) + // ? printClosingTagStartMarker(node.parent, options) + // : needsToBorrowNextOpeningTagStartMarker(node) + // ? printOpeningTagStartMarker(node.next) + // : ""; + } + + private static Doc PrintClosingTagPrefix(XmlElement node) + { + return Doc.Null; + // return needsToBorrowLastChildClosingTagEndMarker(node) + // ? printClosingTagEndMarker(node.lastChild, options) + // : ""; + } + + private static bool ForceBreakContent(XmlElement node) + { + return false; + // return ( + // forceBreakChildren(node) || + // (node.type === "element" && + // node.children.length > 0 && + // (["body", "script", "style"].includes(node.name) || + // node.children.some((child) => hasNonTextChild(child)))) || + // (node.firstChild && + // node.firstChild === node.lastChild && + // node.firstChild.type !== "text" && + // hasLeadingLineBreak(node.firstChild) && + // (!node.lastChild.isTrailingSpaceSensitive || + // hasTrailingLineBreak(node.lastChild))) + // ); + } + + private static Doc PrintAttributes(XmlElement node) + { + if (node.Attributes.Count == 0) + { + return node.IsEmpty ? " " : Doc.Null; + } + + // this is just shoved in here for now + var result = new List(); + foreach (XmlAttribute attribute in node.Attributes) + { + result.Add(Doc.Line, attribute.Name, "=\"", attribute.Value, "\""); + } + + return Doc.Indent(result); + // const ignoreAttributeData = + // node.prev?.type === "comment" && + // getPrettierIgnoreAttributeCommentData(node.prev.value); + // + // const hasPrettierIgnoreAttribute = + // typeof ignoreAttributeData === "boolean" + // ? () => ignoreAttributeData + // : Array.isArray(ignoreAttributeData) + // ? (attribute) => ignoreAttributeData.includes(attribute.rawName) + // : () => false; + // + // const printedAttributes = path.map( + // ({ node: attribute }) => + // hasPrettierIgnoreAttribute(attribute) + // ? replaceEndOfLine( + // options.originalText.slice( + // locStart(attribute), + // locEnd(attribute), + // ), + // ) + // : print(), + // "attrs", + // ); + // + // const forceNotToBreakAttrContent = + // node.type === "element" && + // node.fullName === "script" && + // node.attrs.length === 1 && + // node.attrs[0].fullName === "src" && + // node.children.length === 0; + // + // const shouldPrintAttributePerLine = + // options.singleAttributePerLine && + // node.attrs.length > 1 && + // !isVueSfcBlock(node, options); + // const attributeLine = shouldPrintAttributePerLine ? hardline : line; + // + // /** @type {Doc[]} */ + // const parts = [ + // indent([ + // forceNotToBreakAttrContent ? " " : line, + // join(attributeLine, printedAttributes), + // ]), + // ]; + // + // if ( + // /** + // * 123456 + // */ + // (node.firstChild && + // needsToBorrowParentOpeningTagEndMarker(node.firstChild)) || + // /** + // * 123 + // */ + // (node.isSelfClosing && + // needsToBorrowLastChildClosingTagEndMarker(node.parent)) || + // forceNotToBreakAttrContent + // ) { + // parts.push(node.isSelfClosing ? " " : ""); + // } else { + // parts.push( + // options.bracketSameLine + // ? node.isSelfClosing + // ? " " + // : "" + // : node.isSelfClosing + // ? line + // : softline, + // ); + // } + // + // return parts; + } +} diff --git a/Src/CSharpier/Formatters/Xml/XmlNodePrinters/ElementChildren.cs b/Src/CSharpier/Formatters/Xml/XmlNodePrinters/ElementChildren.cs new file mode 100644 index 000000000..5994f3132 --- /dev/null +++ b/Src/CSharpier/Formatters/Xml/XmlNodePrinters/ElementChildren.cs @@ -0,0 +1,152 @@ +using System.Xml; + +namespace CSharpier.Formatters.Xml.XmlNodePrinters; + +internal static class ElementChildren +{ + public static Doc Print(XmlElement node) + { + return PrintChildren(node, Node.Print); + + // if (node.ChildNodes.Count == 0) + // { + // return Doc.Null; + // } + // + // if (node.FirstChild is XmlText) + // { + // return Node.Print(node.FirstChild); + // } + // + // var result = new List(); + // + // foreach (XmlNode xmlNode in node.ChildNodes) + // { + // result.Add(Doc.Line); + // result.Add(Node.Print(xmlNode)); + // } + // + // return Doc.Concat(Doc.BreakParent, Doc.Concat(result), Doc.Line); + } + + public static Doc PrintChildren(XmlElement node, Func print) + { + // this force breaks html, head, ul, ol, etc + // if (ForceBreakChildren(node)) + // { + // var result = new List { Doc.BreakParent }; + // + // foreach (XmlNode child in node.ChildNodes) + // { + // var prevBetweenLine = + // child.PreviousSibling == null + // ? Doc.Null + // : PrintBetweenLine(child.PreviousSibling, child); + // result.AddRange( + // prevBetweenLine == Doc.Null + // ? new List() + // : new List + // { + // prevBetweenLine, + // ForceNextEmptyLine(child.Prev) ? Doc.HardLine : Doc.Null, + // } + // ); + // result.AddRange(PrintChild(child, print)); + // } + // + // return result; + // } + + var groupIds = new List(); // Assuming CSharpier has grouping feature + foreach (var child in node.ChildNodes) + { + groupIds.Add(Guid.NewGuid().ToString()); + } + + var docs = new List(); + foreach (XmlNode child in node.ChildNodes) + { + var prevBetweenLine = + child.PreviousSibling == null + ? Doc.Null + : PrintBetweenLine(child.PreviousSibling, child); + var nextBetweenLine = + child.NextSibling == null ? Doc.Null : PrintBetweenLine(child, child.NextSibling); + + var parts = new List(); + if (prevBetweenLine is not NullDoc) + { + parts.Add(prevBetweenLine == Doc.HardLine ? Doc.HardLine : Doc.SoftLine); + } + + parts.Add(PrintChild(child, print)); + + if (nextBetweenLine is not NullDoc) + { + parts.Add(nextBetweenLine == Doc.HardLine ? Doc.HardLine : Doc.SoftLine); + } + + docs.Add(Doc.Group(parts)); + } + + return Doc.Concat(docs); + } + + public static Doc PrintChild(XmlNode child, Func print) + { + // should we try to support csharpier-ignore some day? + // if (HasPrettierIgnore(child)) + // { + // int endLocation = GetEndLocation(child); + // + // return new List + // { + // PrintOpeningTagPrefix(child, options), + // ReplaceEndOfLine(TrimEnd(options.OriginalText.Substring( + // LocStart(child) + (child.Prev != null && NeedsToBorrowNextOpeningTagStartMarker(child.Prev) + // ? PrintOpeningTagStartMarker(child).Length : 0), + // endLocation - (child.Next != null && NeedsToBorrowPrevClosingTagEndMarker(child.Next) + // ? PrintClosingTagEndMarker(child, options).Length : 0) + // ))), + // PrintClosingTagSuffix(child, options) + // }; + // } + + return print(child); + } + + // public static int GetEndLocation(XmlElement node) + // { + // int endLocation = LocEnd(node); + // + // // Element can be unclosed + // if (node.Type == "element" && node.Children != null && node.Children.Count > 0) + // { + // return Math.Max(endLocation, GetEndLocation(node.Children[^1])); // Using C# ^ for last element + // } + // + // return endLocation; + // } + + public static Doc PrintBetweenLine(XmlNode prevNode, XmlNode nextNode) + { + return Doc.Line; + + // if (prevNode is XmlText && nextNode is XmlText) + // { + // if (prevNode.IsTrailingSpaceSensitive) + // { + // return prevNode.HasTrailingSpaces + // ? PreferHardlineAsLeadingSpaces(nextNode) + // ? Doc.HardLine + // : Line() + // : Doc.Empty; + // } + // return PreferHardlineAsLeadingSpaces(nextNode) ? Doc.HardLine : Doc.SoftLine; + // } + // + // return ShouldBreakBetweenNodes(prevNode, nextNode) ? Doc.HardLine + // : nextNode.HasLeadingSpaces ? Line() + // : Doc.SoftLine; + } +} diff --git a/Src/CSharpier/Formatters/Xml/XmlNodePrinters/Node.cs b/Src/CSharpier/Formatters/Xml/XmlNodePrinters/Node.cs new file mode 100644 index 000000000..1b96dc964 --- /dev/null +++ b/Src/CSharpier/Formatters/Xml/XmlNodePrinters/Node.cs @@ -0,0 +1,44 @@ +using System.Xml; + +namespace CSharpier.Formatters.Xml.XmlNodePrinters; + +internal static class Node +{ + internal static Doc Print(XmlNode xmlNode) + { + if (xmlNode is XmlDocument xmlDocument) + { + var result = new List(); + foreach (XmlNode node in xmlDocument.ChildNodes) + { + result.Add(Print(node), Doc.HardLine); + } + + result.Add(Doc.HardLine); + + return Doc.Concat(result); + } + + if (xmlNode is XmlDeclaration xmlDeclaration) + { + return xmlDeclaration.OuterXml; + } + + if (xmlNode is XmlElement xmlElement) + { + return Element.Print(xmlElement); + } + + if (xmlNode is XmlText) + { + return xmlNode.OuterXml; + } + + if (xmlNode is XmlComment) + { + return xmlNode.OuterXml; + } + + throw new Exception("Need to handle + " + xmlNode); + } +} diff --git a/Src/CSharpier/PrinterOptions.cs b/Src/CSharpier/PrinterOptions.cs index 895176012..7cb4b4ae8 100644 --- a/Src/CSharpier/PrinterOptions.cs +++ b/Src/CSharpier/PrinterOptions.cs @@ -10,7 +10,7 @@ internal class PrinterOptions public EndOfLine EndOfLine { get; set; } = EndOfLine.Auto; public bool TrimInitialLines { get; init; } = true; public bool IncludeGenerated { get; set; } - public string Formatter { get; init; } = string.Empty; + public Formatter Formatter { get; init; } = Formatter.Unknown; public const int WidthUsedByTests = 100; @@ -34,3 +34,11 @@ internal static string GetLineEnding(string code, PrinterOptions printerOptions) return "\n"; } } + +internal enum Formatter +{ + Unknown, + CSharp, + CSharpScript, + XML, +} diff --git a/Src/CSharpier/SyntaxNodeJsonWriter.cs b/Src/CSharpier/SyntaxNodeJsonWriter.cs index 38874ea68..ec0b15bd8 100644 --- a/Src/CSharpier/SyntaxNodeJsonWriter.cs +++ b/Src/CSharpier/SyntaxNodeJsonWriter.cs @@ -1,7 +1,7 @@ -namespace CSharpier; - using System.Text.Json; +namespace CSharpier; + internal static partial class SyntaxNodeJsonWriter { private static string? WriteBoolean(string name, bool value) diff --git a/Src/CSharpier/SyntaxPrinter/CSharpierIgnore.cs b/Src/CSharpier/SyntaxPrinter/CSharpierIgnore.cs index fb4e750bd..523c54e98 100644 --- a/Src/CSharpier/SyntaxPrinter/CSharpierIgnore.cs +++ b/Src/CSharpier/SyntaxPrinter/CSharpierIgnore.cs @@ -1,8 +1,8 @@ -namespace CSharpier.SyntaxPrinter; - using System.Text; using System.Text.RegularExpressions; +namespace CSharpier.SyntaxPrinter; + internal static class CSharpierIgnore { private static readonly Regex IgnoreRegex = new("^// csharpier-ignore($| -)"); diff --git a/Src/CSharpier/SyntaxPrinter/FormattingContext.cs b/Src/CSharpier/SyntaxPrinter/FormattingContext.cs index 810ce602c..cee102fc1 100644 --- a/Src/CSharpier/SyntaxPrinter/FormattingContext.cs +++ b/Src/CSharpier/SyntaxPrinter/FormattingContext.cs @@ -10,6 +10,7 @@ internal class FormattingContext public required bool UseTabs { get; init; } // TODO the rest of these go into State + // context.State.PrintingDepth public int PrintingDepth { get; set; } public bool NextTriviaNeedsLine { get; set; } diff --git a/Src/CSharpier/SyntaxPrinter/MembersWithForcedLines.cs b/Src/CSharpier/SyntaxPrinter/MembersWithForcedLines.cs index 6e858fb89..885d4f72b 100644 --- a/Src/CSharpier/SyntaxPrinter/MembersWithForcedLines.cs +++ b/Src/CSharpier/SyntaxPrinter/MembersWithForcedLines.cs @@ -1,9 +1,8 @@ using System.Collections.Immutable; +using System.Text; namespace CSharpier.SyntaxPrinter; -using System.Text; - internal static class MembersWithForcedLines { public static List Print( diff --git a/Src/CSharpier/SyntaxPrinter/SeparatedSyntaxList.cs b/Src/CSharpier/SyntaxPrinter/SeparatedSyntaxList.cs index 45e730f55..270a66e60 100644 --- a/Src/CSharpier/SyntaxPrinter/SeparatedSyntaxList.cs +++ b/Src/CSharpier/SyntaxPrinter/SeparatedSyntaxList.cs @@ -1,7 +1,7 @@ -namespace CSharpier.SyntaxPrinter; - using System.Text; +namespace CSharpier.SyntaxPrinter; + internal static class SeparatedSyntaxList { public static Doc Print( diff --git a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/BaseMethodDeclaration.cs b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/BaseMethodDeclaration.cs index 69d36fa04..bf7fe4746 100644 --- a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/BaseMethodDeclaration.cs +++ b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/BaseMethodDeclaration.cs @@ -1,7 +1,7 @@ -namespace CSharpier.SyntaxPrinter.SyntaxNodePrinters; - using System.Text.RegularExpressions; +namespace CSharpier.SyntaxPrinter.SyntaxNodePrinters; + internal static class BaseMethodDeclaration { public static Doc Print(CSharpSyntaxNode node, FormattingContext context) diff --git a/Src/CSharpier/SyntaxPrinter/Token.cs b/Src/CSharpier/SyntaxPrinter/Token.cs index f7291e2c7..26d646772 100644 --- a/Src/CSharpier/SyntaxPrinter/Token.cs +++ b/Src/CSharpier/SyntaxPrinter/Token.cs @@ -1,7 +1,7 @@ -namespace CSharpier.SyntaxPrinter; - using System.Text.RegularExpressions; +namespace CSharpier.SyntaxPrinter; + internal static class Token { public static Doc PrintWithoutLeadingTrivia(SyntaxToken syntaxToken, FormattingContext context) diff --git a/Src/CSharpier/Utilities/StackExtensions.cs b/Src/CSharpier/Utilities/StackExtensions.cs index 78ff09c64..a465419cd 100644 --- a/Src/CSharpier/Utilities/StackExtensions.cs +++ b/Src/CSharpier/Utilities/StackExtensions.cs @@ -1,7 +1,7 @@ -namespace CSharpier.Utilities; - using System.Diagnostics.CodeAnalysis; +namespace CSharpier.Utilities; + internal static class StackExtensions { #if NETSTANDARD2_0 diff --git a/Src/CSharpier/XmlFormatter.cs b/Src/CSharpier/XmlFormatter.cs deleted file mode 100644 index f40076234..000000000 --- a/Src/CSharpier/XmlFormatter.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CSharpier; - -internal class XmlFormatter : IFormatter -{ - public Task FormatAsync( - string code, - PrinterOptions printerOptions, - CancellationToken cancellationToken - ) - { - throw new NotImplementedException(); - } -} diff --git a/Src/SyntaxFinder/SyntaxFinderWalker.cs b/Src/SyntaxFinder/SyntaxFinderWalker.cs index 79bd461a1..849b375e0 100644 --- a/Src/SyntaxFinder/SyntaxFinderWalker.cs +++ b/Src/SyntaxFinder/SyntaxFinderWalker.cs @@ -1,8 +1,8 @@ -namespace SyntaxFinder; - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +namespace SyntaxFinder; + public abstract class SyntaxFinderWalker(string file) : CSharpSyntaxWalker { private bool wroteFile; diff --git a/Src/SyntaxFinder/SyntaxTriviaExtensions.cs b/Src/SyntaxFinder/SyntaxTriviaExtensions.cs index 3d67990b0..79d545180 100644 --- a/Src/SyntaxFinder/SyntaxTriviaExtensions.cs +++ b/Src/SyntaxFinder/SyntaxTriviaExtensions.cs @@ -1,8 +1,8 @@ -namespace SyntaxFinder; - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +namespace SyntaxFinder; + public static class SyntaxTriviaExtensions { public static bool IsComment(this SyntaxTrivia syntaxTrivia) diff --git a/Src/SyntaxFinder/Walkers/ExampleWalker.cs b/Src/SyntaxFinder/Walkers/ExampleWalker.cs index 43424381f..451c45aa9 100644 --- a/Src/SyntaxFinder/Walkers/ExampleWalker.cs +++ b/Src/SyntaxFinder/Walkers/ExampleWalker.cs @@ -1,10 +1,10 @@ -namespace SyntaxFinder.Walkers; - using System.Collections.Concurrent; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +namespace SyntaxFinder.Walkers; + public class ExampleWalker(string file) : SyntaxFinderWalker(file) { public static readonly ConcurrentDictionary> MembersInType = new(); diff --git a/Src/SyntaxFinder/Walkers/ModifiersWalker.cs b/Src/SyntaxFinder/Walkers/ModifiersWalker.cs index 868184db4..a7ab5f0ba 100644 --- a/Src/SyntaxFinder/Walkers/ModifiersWalker.cs +++ b/Src/SyntaxFinder/Walkers/ModifiersWalker.cs @@ -1,7 +1,7 @@ -namespace SyntaxFinder.Walkers; - using Microsoft.CodeAnalysis.CSharp.Syntax; +namespace SyntaxFinder.Walkers; + public class ModifiersWalker : SyntaxFinderWalker { public ModifiersWalker(string file) diff --git a/Src/SyntaxFinder/Walkers/NestedConditionalExpressionsWalker.cs b/Src/SyntaxFinder/Walkers/NestedConditionalExpressionsWalker.cs index 13ccd77cc..6ed98bf15 100644 --- a/Src/SyntaxFinder/Walkers/NestedConditionalExpressionsWalker.cs +++ b/Src/SyntaxFinder/Walkers/NestedConditionalExpressionsWalker.cs @@ -1,10 +1,10 @@ -namespace SyntaxFinder.Walkers; - using System.Collections.Concurrent; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +namespace SyntaxFinder.Walkers; + public class NestedConditionalExpressionsWalker(string file) : SyntaxFinderWalker(file) { public static readonly ConcurrentDictionary> MembersInType = new(); diff --git a/Src/SyntaxFinder/Walkers/ObjectInitializerWalker.cs b/Src/SyntaxFinder/Walkers/ObjectInitializerWalker.cs index 903540254..81194aac3 100644 --- a/Src/SyntaxFinder/Walkers/ObjectInitializerWalker.cs +++ b/Src/SyntaxFinder/Walkers/ObjectInitializerWalker.cs @@ -1,8 +1,8 @@ -namespace SyntaxFinder.Walkers; - using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +namespace SyntaxFinder.Walkers; + public class ObjectInitializerWalker : CSharpSyntaxWalker { private static int total; diff --git a/Src/SyntaxFinder/Walkers/SpreadWalker.cs b/Src/SyntaxFinder/Walkers/SpreadWalker.cs index ba5ae061f..6f2c098b3 100644 --- a/Src/SyntaxFinder/Walkers/SpreadWalker.cs +++ b/Src/SyntaxFinder/Walkers/SpreadWalker.cs @@ -1,8 +1,8 @@ -namespace SyntaxFinder.Walkers; - using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +namespace SyntaxFinder.Walkers; + public class SpreadWalker : CSharpSyntaxWalker { private static int total;