From 9940656dd0b6ddd150c7c9931cdf053d991d6739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bry=C5=82ka?= Date: Sun, 17 Dec 2023 23:13:35 +0100 Subject: [PATCH] Add Github Actions for build. Remove integration with AppVeyor. --- .github/workflows/ci.yml | 109 +++++++++ .github/workflows/codeql-analysis.yml | 56 ++--- .github/workflows/license-scanning.yml | 27 +++ .github/workflows/test-report.yml | 21 ++ Benchmarks/AggressionBasedBench.cs | 3 +- Benchmarks/ArrayParserBench.cs | 5 +- Benchmarks/BenchmarkInput.cs | 4 +- Benchmarks/CollectionParserBench.cs | 3 +- Benchmarks/CollectionSerializerSlow.cs | 7 +- Benchmarks/DeconstructablesBench.cs | 4 +- Benchmarks/DynamicMethodGenerator.cs | 4 +- Benchmarks/EnumBench.cs | 7 +- Benchmarks/LinqBench/Linq_Count_Vs_Any.cs | 2 - .../LinqBench/Linq_WhereAndFirst_Vs_First.cs | 2 - Benchmarks/MultiKeyDictionaryBench.cs | 6 +- Benchmarks/ParserBench.cs | 5 +- Benchmarks/StructureOfArraysBench.cs | 4 +- Directory.Build.props | 26 ++- Directory.Build.targets | 19 ++ Directory.Packages.props | 6 + GetNetCoreVersions.ps1 | 12 - LICENSE.txt | 2 +- .../Domain}/SettingsPersistenceTests.cs | 12 +- .../Domain}/SettingsValidationTests.cs | 2 +- .../Nemesis.TextParsers.ArchTests.csproj | 14 +- Nemesis.TextParsers.CodeGen.Sample/Program.cs | 48 ++-- ...toDeconstructableGeneratorApprovalTests.cs | 9 +- ...orTests.cs => AutoDeconstructableTests.cs} | 20 +- ....cs => AutoDeconstructable_Diagnostics.cs} | 47 ++-- .../CodeGenUtils.cs | 125 +++++++++++ .../EndToEndCases.cs | 18 +- .../Nemesis.TextParsers.CodeGen.Tests.csproj | 10 +- Nemesis.TextParsers.CodeGen.Tests/Utils.cs | 81 ------- .../Nemesis.TextParsers.CodeGen.csproj | 31 +-- .../Collections/DictionaryTests.cs | 4 - Nemesis.TextParsers.Tests/ExploratoryTests.cs | 28 ++- .../Nemesis.TextParsers.Tests.csproj | 10 +- .../Utils/RandomSource.cs | 7 +- .../Utils/RandomSourceTests.cs | 7 +- Nemesis.TextParsers.Tests/Utils/Sut.cs | 210 +++++++++--------- Nemesis.TextParsers.Tests/Utils/TestHelper.cs | 2 +- Nemesis.TextParsers.sln | 16 +- .../Legacy/Number.NumberBuffer.cs | 4 +- Nemesis.TextParsers/Legacy/Number.Parsing.cs | 2 +- Nemesis.TextParsers/Legacy/Number.Small.cs | 10 +- .../Nemesis.TextParsers.csproj | 35 +-- Nemesis.TextParsers/Parsers/03_SimpleTypes.cs | 10 +- README.md | 31 +-- Specification.md | 2 +- WebDemo/Entities.cs | 6 + WebDemo/Program.cs | 57 +++-- WebDemo/Settings.cs | 32 +++ WebDemo/WebDemo.csproj | 1 + WebDemo/WebDemo.http | 22 +- appveyor.yml | 57 ----- global.json | 6 + images/{review-icon.png => icon.png} | Bin 57 files changed, 715 insertions(+), 595 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/license-scanning.yml create mode 100644 .github/workflows/test-report.yml delete mode 100644 GetNetCoreVersions.ps1 rename {Nemesis.TextParsers.Tests/Infrastructure => Nemesis.TextParsers.ArchTests/Domain}/SettingsPersistenceTests.cs (94%) rename {Nemesis.TextParsers.Tests/Infrastructure => Nemesis.TextParsers.ArchTests/Domain}/SettingsValidationTests.cs (98%) rename Nemesis.TextParsers.CodeGen.Tests/{AutoDeconstructableGeneratorTests.cs => AutoDeconstructableTests.cs} (89%) rename Nemesis.TextParsers.CodeGen.Tests/{AutoDeconstructableGeneratorTests.Diagnostics.cs => AutoDeconstructable_Diagnostics.cs} (74%) create mode 100644 Nemesis.TextParsers.CodeGen.Tests/CodeGenUtils.cs delete mode 100644 Nemesis.TextParsers.CodeGen.Tests/Utils.cs create mode 100644 WebDemo/Entities.cs create mode 100644 WebDemo/Settings.cs delete mode 100644 appveyor.yml create mode 100644 global.json rename images/{review-icon.png => icon.png} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6ac46bb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,109 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: 'CI' +on: + workflow_dispatch: + push: + branches: + - 'main' + pull_request: + branches: + - '*' + release: + types: + - published + +env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: true + NuGetDirectory: ${{ github.workspace}}/nuget + +defaults: + run: + shell: pwsh + +jobs: + create_nuget: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Get all history to allow automatic versioning using MinVer + + - name: Setup .NET # Install the .NET SDK indicated in the global.json file + uses: actions/setup-dotnet@v3 + + + - name: Release Pack (update release notes) + if: github.event_name == 'release' + env: + RELEASE_NAME: ${{ github.event.release.name }} + RELEASE_BODY: ${{ github.event.release.body }} + run: | + $name = $env:RELEASE_NAME + $body = $env:RELEASE_BODY + $releaseNotes = "# Release ${{ github.event.release.tag_name }}" + + if($name){ + $releaseNotes = $releaseNotes + " - " + $name + } + if($body){ + $releaseNotes = $releaseNotes + "`r`n`r`n" + $body + } + + Write-Host "`tSetting release notes to '$releaseNotes'" + + dotnet pack --configuration Release --output ${{ env.NuGetDirectory }} -p:PackageReleaseNotes=$releaseNotes + + + - name: Non-release Pack + if: github.event_name != 'release' + run: dotnet pack --configuration Release --output ${{ env.NuGetDirectory }} + + - uses: actions/upload-artifact@v3 + with: + name: nuget + if-no-files-found: error + retention-days: 7 + path: ${{ env.NuGetDirectory }}/*.nupkg + + run_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + - name: Run tests + run: dotnet test --configuration Release --logger "trx;LogFilePrefix=T" + - name: Upload test results + uses: actions/upload-artifact@v3 + if: success() || failure() # run this step even if previous step failed + with: + name: TestResult + if-no-files-found: error + retention-days: 7 + path: "**/*.trx" + + deploy: + if: github.event_name == 'release' || (github.event_name == 'push' && github.ref_name == 'main') + runs-on: ubuntu-latest + needs: [ create_nuget, run_test ] + steps: + - uses: actions/download-artifact@v3 + with: + name: nuget + path: ${{ env.NuGetDirectory }} + + - name: Setup .NET Core + uses: actions/setup-dotnet@v3 + + - name: Publish NuGet package + env: + EVENT_NAME: ${{ github.event_name }} + run: | + $key = ($env:EVENT_NAME -eq 'release') ? "${{ secrets.NUGET_API_KEY }}" : "${{ secrets.GH_PACKAGE_REGISTRY_API_KEY }}" + $source = ($env:EVENT_NAME -eq 'release') ? "https://api.nuget.org/v3/index.json" : "https://nuget.pkg.github.com/MichalBrylka/index.json" + + foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) { + dotnet nuget push $file --api-key "$key" --source "$source" --skip-duplicate + } \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 15d7a52..85c28ec 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,24 +1,13 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: "CodeQL" on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] + workflow_dispatch: + pull_request: + branches: [ main ] schedule: - - cron: '40 18 * * 0' + - cron: '40 16 * * 0' + +run-name: "QL" jobs: analyze: @@ -28,40 +17,23 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'csharp' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: + language: [ 'csharp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + languages: ${{ matrix.language }} - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + - name: Setup .NET + uses: actions/setup-dotnet@v3 - #- run: | - # make bootstrap - # make release + - run: dotnet build --configuration Release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/license-scanning.yml b/.github/workflows/license-scanning.yml new file mode 100644 index 0000000..553ff81 --- /dev/null +++ b/.github/workflows/license-scanning.yml @@ -0,0 +1,27 @@ +name: License Scanning + +on: + workflow_dispatch: + pull_request: + branches: + - '*' + +defaults: + run: + shell: pwsh + + +jobs: + scan: + runs-on: ubuntu-latest + + steps: + - name: Checkout tree + uses: actions/checkout@v4 + + + + - name: Run FOSSA scan and upload build data + uses: fossa-contrib/fossa-action@v3 + with: + fossa-api-key: ${{ secrets.FOSSA_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml new file mode 100644 index 0000000..a45b26d --- /dev/null +++ b/.github/workflows/test-report.yml @@ -0,0 +1,21 @@ +name: 'Test Report' +on: + workflow_run: + workflows: ['CI'] + types: + - completed + +run-name: "Test for '${{ github.event.workflow_run.head_commit.message }}'" + +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: 'TestResult' + name: "Tests results" + reporter: dotnet-trx + path: "**/*.trx" + fail-on-error: 'true' + fail-on-empty: 'true' \ No newline at end of file diff --git a/Benchmarks/AggressionBasedBench.cs b/Benchmarks/AggressionBasedBench.cs index ec7d67d..984f762 100644 --- a/Benchmarks/AggressionBasedBench.cs +++ b/Benchmarks/AggressionBasedBench.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; // ReSharper disable CommentTypo diff --git a/Benchmarks/ArrayParserBench.cs b/Benchmarks/ArrayParserBench.cs index 372bd1d..040d0fd 100644 --- a/Benchmarks/ArrayParserBench.cs +++ b/Benchmarks/ArrayParserBench.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Nemesis.TextParsers; // ReSharper disable CommentTypo diff --git a/Benchmarks/BenchmarkInput.cs b/Benchmarks/BenchmarkInput.cs index 684a7c3..45956c0 100644 --- a/Benchmarks/BenchmarkInput.cs +++ b/Benchmarks/BenchmarkInput.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections; +using System.Collections; using System.Globalization; -using System.Linq; namespace Benchmarks { diff --git a/Benchmarks/CollectionParserBench.cs b/Benchmarks/CollectionParserBench.cs index 5ed2532..6d8d854 100644 --- a/Benchmarks/CollectionParserBench.cs +++ b/Benchmarks/CollectionParserBench.cs @@ -1,5 +1,4 @@ -using System; -using System.Buffers; +using System.Buffers; using System.ComponentModel; using BenchmarkDotNet.Attributes; using Nemesis.TextParsers; diff --git a/Benchmarks/CollectionSerializerSlow.cs b/Benchmarks/CollectionSerializerSlow.cs index 96dd32d..e5a6703 100644 --- a/Benchmarks/CollectionSerializerSlow.cs +++ b/Benchmarks/CollectionSerializerSlow.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; +using System.ComponentModel; namespace Benchmarks { diff --git a/Benchmarks/DeconstructablesBench.cs b/Benchmarks/DeconstructablesBench.cs index 8bff22a..13fb0d0 100644 --- a/Benchmarks/DeconstructablesBench.cs +++ b/Benchmarks/DeconstructablesBench.cs @@ -1,7 +1,5 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; -using System.Linq; using BenchmarkDotNet.Attributes; using Nemesis.TextParsers; using Nemesis.TextParsers.Utils; diff --git a/Benchmarks/DynamicMethodGenerator.cs b/Benchmarks/DynamicMethodGenerator.cs index 871c2d1..4c370cc 100644 --- a/Benchmarks/DynamicMethodGenerator.cs +++ b/Benchmarks/DynamicMethodGenerator.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Reflection.Emit; +using System.Reflection.Emit; namespace Benchmarks { diff --git a/Benchmarks/EnumBench.cs b/Benchmarks/EnumBench.cs index 21ed9eb..23f15bb 100644 --- a/Benchmarks/EnumBench.cs +++ b/Benchmarks/EnumBench.cs @@ -1,9 +1,4 @@ -using System; -using System.IO; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; +using System.Reflection.Emit; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; diff --git a/Benchmarks/LinqBench/Linq_Count_Vs_Any.cs b/Benchmarks/LinqBench/Linq_Count_Vs_Any.cs index 4e73330..7a4754f 100644 --- a/Benchmarks/LinqBench/Linq_Count_Vs_Any.cs +++ b/Benchmarks/LinqBench/Linq_Count_Vs_Any.cs @@ -1,6 +1,4 @@ using System.Buffers; -using System.Collections.Generic; -using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; diff --git a/Benchmarks/LinqBench/Linq_WhereAndFirst_Vs_First.cs b/Benchmarks/LinqBench/Linq_WhereAndFirst_Vs_First.cs index 4b7b0aa..ff2aa0b 100644 --- a/Benchmarks/LinqBench/Linq_WhereAndFirst_Vs_First.cs +++ b/Benchmarks/LinqBench/Linq_WhereAndFirst_Vs_First.cs @@ -1,6 +1,4 @@ using System.Buffers; -using System.Collections.Generic; -using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; diff --git a/Benchmarks/MultiKeyDictionaryBench.cs b/Benchmarks/MultiKeyDictionaryBench.cs index 50b2a88..1d0ffc7 100644 --- a/Benchmarks/MultiKeyDictionaryBench.cs +++ b/Benchmarks/MultiKeyDictionaryBench.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; // ReSharper disable UseDeconstruction // ReSharper disable SuggestVarOrType_SimpleTypes diff --git a/Benchmarks/ParserBench.cs b/Benchmarks/ParserBench.cs index 2e483e9..8d85317 100644 --- a/Benchmarks/ParserBench.cs +++ b/Benchmarks/ParserBench.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; -using System.Linq; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Nemesis.TextParsers; using Newtonsoft.Json; diff --git a/Benchmarks/StructureOfArraysBench.cs b/Benchmarks/StructureOfArraysBench.cs index bf500fd..334eeb7 100644 --- a/Benchmarks/StructureOfArraysBench.cs +++ b/Benchmarks/StructureOfArraysBench.cs @@ -1,6 +1,4 @@ -using System.Linq; - -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; // ReSharper disable MemberCanBePrivate.Local diff --git a/Directory.Build.props b/Directory.Build.props index 6eda337..967cd26 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,19 +1,13 @@ - 1.0.0 - + 0.0.1 + 12.0 enable - - - Michał Bryłka, Leszek Kowalski - MIT OR Apache-2.0 - Copyright (c) Michał Bryłka. Icon by http://www.iconka.com + false - true - https://github.com/nemesissoft/Nemesis.TextParsers true @@ -25,6 +19,20 @@ ..\Nemesis.TextParsers.Public.snk + + + properties\icon.png + + + + properties\README.md + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets index 582e676..251abed 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -13,6 +13,25 @@ $(PackageDescription) This package was built from the source at $(RepositoryUrl.TrimEnd('.git'))/tree/$(SourceRevisionId) + + Michał Bryłka, Leszek Kowalski + MIT OR Apache-2.0 + Copyright © Michał Bryłka. Icon by http://www.iconka.com + true + https://github.com/nemesissoft/Nemesis.TextParsers + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + embedded + true + + properties\icon.png + properties\README.md + + true + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 923f615..46c35e3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,6 +16,12 @@ + + + all + runtime; build; native; contentfiles; analyzers + + diff --git a/GetNetCoreVersions.ps1 b/GetNetCoreVersions.ps1 deleted file mode 100644 index 815261d..0000000 --- a/GetNetCoreVersions.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -Write-Host Path: -(Get-Command dotnet).Path - -Write-Host - -Write-Host Runtimes: -(dir (Get-Command dotnet).Path.Replace('dotnet.exe', 'shared\Microsoft.NETCore.App')).Name - -Write-Host - -Write-Host SDK: -(dir (Get-Command dotnet).Path.Replace('dotnet.exe', 'sdk')).Name \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index 0b4795a..6511579 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Nemesis +Copyright (c) 2018-2023 Nemesis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Nemesis.TextParsers.Tests/Infrastructure/SettingsPersistenceTests.cs b/Nemesis.TextParsers.ArchTests/Domain/SettingsPersistenceTests.cs similarity index 94% rename from Nemesis.TextParsers.Tests/Infrastructure/SettingsPersistenceTests.cs rename to Nemesis.TextParsers.ArchTests/Domain/SettingsPersistenceTests.cs index c301132..57de314 100644 --- a/Nemesis.TextParsers.Tests/Infrastructure/SettingsPersistenceTests.cs +++ b/Nemesis.TextParsers.ArchTests/Domain/SettingsPersistenceTests.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Configuration; using Nemesis.TextParsers.Settings; -namespace Nemesis.TextParsers.Tests.Infrastructure; +namespace Nemesis.TextParsers.ArchTests.Domain; [TestFixture] public class SettingsPersistenceTests @@ -109,13 +109,14 @@ public void SettingsCanBeReadFromAndWrittenToJsonFile_UsingCodeGen(ISettings set private static void SettingsPersistanceHelper(ISettings settings, Func serializer, - Func deserializer) + Func deserializer) { var settingsType = settings.GetType(); - var doc = JsonNode.Parse(ConfigJsonFile); - var section = doc[settingsType.Name].ToString(); + JsonNode doc = JsonNode.Parse(ConfigJsonFile)!; + var section = doc[settingsType.Name]?.ToString() ?? throw new InvalidDataException("Section does not exist"); var deser1 = deserializer(section); + Assert.That(deser1, Is.Not.Null); AssertMutualEquivalence(deser1, settings); @@ -125,6 +126,9 @@ public void SettingsCanBeReadFromAndWrittenToJsonFile_UsingCodeGen(ISettings set var deser2 = deserializer(fromText); var deser3 = deserializer(fromTestData); + Assert.That(deser2, Is.Not.Null); + Assert.That(deser3, Is.Not.Null); + AssertMutualEquivalence(deser2, settings); AssertMutualEquivalence(deser3, settings); AssertMutualEquivalence(deser2, deser3); diff --git a/Nemesis.TextParsers.Tests/Infrastructure/SettingsValidationTests.cs b/Nemesis.TextParsers.ArchTests/Domain/SettingsValidationTests.cs similarity index 98% rename from Nemesis.TextParsers.Tests/Infrastructure/SettingsValidationTests.cs rename to Nemesis.TextParsers.ArchTests/Domain/SettingsValidationTests.cs index 4987db1..658b9bf 100644 --- a/Nemesis.TextParsers.Tests/Infrastructure/SettingsValidationTests.cs +++ b/Nemesis.TextParsers.ArchTests/Domain/SettingsValidationTests.cs @@ -1,6 +1,6 @@ using Nemesis.TextParsers.Settings; -namespace Nemesis.TextParsers.Tests.Infrastructure; +namespace Nemesis.TextParsers.ArchTests.Domain; [TestFixture] public class SettingsValidationTests diff --git a/Nemesis.TextParsers.ArchTests/Nemesis.TextParsers.ArchTests.csproj b/Nemesis.TextParsers.ArchTests/Nemesis.TextParsers.ArchTests.csproj index cfaaa66..a23a4d7 100644 --- a/Nemesis.TextParsers.ArchTests/Nemesis.TextParsers.ArchTests.csproj +++ b/Nemesis.TextParsers.ArchTests/Nemesis.TextParsers.ArchTests.csproj @@ -11,16 +11,18 @@ - - - + - - + + + + + - + + diff --git a/Nemesis.TextParsers.CodeGen.Sample/Program.cs b/Nemesis.TextParsers.CodeGen.Sample/Program.cs index eb1dcc2..576039d 100644 --- a/Nemesis.TextParsers.CodeGen.Sample/Program.cs +++ b/Nemesis.TextParsers.CodeGen.Sample/Program.cs @@ -1,35 +1,33 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; -namespace Nemesis.TextParsers.CodeGen.Sample +namespace Nemesis.TextParsers.CodeGen.Sample; + +class Program { - class Program + static void Main() { - static void Main() - { - //new StructPoint3d(1.23, 4.56, 7.89).DebuggerHook(); - FormatAndParse(new StructPoint3d(1.23, 4.56, 7.89), "〈1.23_4.56_7.89〉"); + //new StructPoint3d(1.23, 4.56, 7.89).DebuggerHook(); + FormatAndParse(new StructPoint3d(1.23, 4.56, 7.89), "〈1.23_4.56_7.89〉"); - FormatAndParse(new RecordPoint3d(1.23, 4.56, 7.89), "⟪1.23,4.56,7.89⟫"); - FormatAndParse(new RecordPoint2d(1.23, 4.56), "(1.23;4.56)"); - } + FormatAndParse(new RecordPoint3d(1.23, 4.56, 7.89), "⟪1.23,4.56,7.89⟫"); + FormatAndParse(new RecordPoint2d(1.23, 4.56), "(1.23;4.56)"); + } - private static void FormatAndParse(T instance, string text) - { - var sut = TextTransformer.Default.GetTransformer(); + private static void FormatAndParse(T instance, string text) + { + var sut = TextTransformer.Default.GetTransformer(); - var actualFormatted = sut.Format(instance); - Console.WriteLine(actualFormatted); - Debug.Assert(actualFormatted == text, "actualFormatted == text"); + var actualFormatted = sut.Format(instance); + Console.WriteLine(actualFormatted); + Debug.Assert(actualFormatted == text, "actualFormatted == text"); - var actualParsed1 = sut.Parse(text); - var actualParsed2 = sut.Parse(actualFormatted); - Debug.Assert(actualParsed1.Equals(instance), "actualParsed1.Equals(instance)"); - Debug.Assert(actualParsed2.Equals(instance), "actualParsed2.Equals(instance)"); - Debug.Assert(actualParsed1.Equals(actualParsed2), "actualParsed1.Equals(actualParsed2)"); + var actualParsed1 = sut.Parse(text); + var actualParsed2 = sut.Parse(actualFormatted); + Debug.Assert(actualParsed1.Equals(instance), "actualParsed1.Equals(instance)"); + Debug.Assert(actualParsed2.Equals(instance), "actualParsed2.Equals(instance)"); + Debug.Assert(actualParsed1.Equals(actualParsed2), "actualParsed1.Equals(actualParsed2)"); - Console.WriteLine(); - } - } + Console.WriteLine(); + } } diff --git a/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs b/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs index daa89ad..671b1d9 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs @@ -3,12 +3,12 @@ using ApprovalTests.Reporters; using ApprovalTests.Writers; -using static Nemesis.TextParsers.CodeGen.Tests.Utils; +using static Nemesis.TextParsers.CodeGen.Tests.CodeGenUtils; namespace Nemesis.TextParsers.CodeGen.Tests.ApprovalTests; [TestFixture, Explicit] -[UseReporter(typeof(TortoiseDiffReporter), typeof(ClipboardReporter))] +[UseReporter(typeof(VisualStudioReporter), typeof(ClipboardReporter))] internal class AutoDeconstructableGeneratorApprovalTests { [Test] public void ApprovalTestsRecord() => RunCase("Record"); @@ -27,12 +27,11 @@ internal class AutoDeconstructableGeneratorApprovalTests private static void RunCase(string index) { - var (_, source, _) = EndToEndCases.AutoDeconstructableCases().SingleOrDefault(t => t.name == index); + var (_, source, _) = EndToEndCases.GetAutoDeconstructableCases().SingleOrDefault(t => t.name == index); Assert.That(source, Is.Not.Null); Assert.That(source, Is.Not.Empty); - var compilation = CreateCompilation( - $@"using Nemesis.TextParsers.Settings; namespace Nemesis.TextParsers.CodeGen.Tests {{ {source} }}"); + var compilation = CreateValidCompilation(source); var generatedTrees = GetGeneratedTreesOnly(compilation); diff --git a/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs b/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableTests.cs similarity index 89% rename from Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs rename to Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableTests.cs index 79563af..dd440b6 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableTests.cs @@ -1,21 +1,19 @@ extern alias original; -using static Nemesis.TextParsers.CodeGen.Tests.Utils; +using static Nemesis.TextParsers.CodeGen.Tests.CodeGenUtils; namespace Nemesis.TextParsers.CodeGen.Tests; [TestFixture] -public partial class AutoDeconstructableGeneratorTests +public class AutoDeconstructableTests { - private static readonly IEnumerable _endToEndCases = EndToEndCases.AutoDeconstructableCases() - .Select((t, i) => new TestCaseData($@" -using Nemesis.TextParsers.Settings; -namespace Nemesis.TextParsers.CodeGen.Tests {{ {t.source} }}", t.expectedCode) + private static readonly IEnumerable _endToEndCases = EndToEndCases.GetAutoDeconstructableCases() + .Select((t, i) => new TCD(t.source, t.expectedCode) .SetName($"E2E_{i + 1:00}_{t.name}")); [TestCaseSource(nameof(_endToEndCases))] public void EndToEndTests(string source, string expectedCode) { - var compilation = CreateCompilation(source); + var compilation = CreateValidCompilation(source); var generatedTrees = GetGeneratedTreesOnly(compilation); @@ -25,7 +23,7 @@ public void EndToEndTests(string source, string expectedCode) } - private static readonly IEnumerable _settingsCases = new (string typeDefinition, string expectedCodePart)[] + private static readonly IEnumerable _settingsCases = new (string typeDefinition, string expectedCodePart)[] { (@"[DeconstructableSettings(':', '␀', '/', '{', '}')] readonly partial struct Child @@ -64,7 +62,7 @@ partial record T(byte B) { }", @"public override T GetEmpty() => new T(_transfor (@"[DeconstructableSettings(useDeconstructableEmpty: false)] partial record T(byte B) { }", @"NOT CONTAIN:GetEmpty()"), } - .Select((t, i) => new TestCaseData($@"using Nemesis.TextParsers.Settings; namespace Tests {{ [Auto.AutoDeconstructable] {t.typeDefinition} }}", t.expectedCodePart) + .Select((t, i) => new TCD($@"using Nemesis.TextParsers.Settings; namespace Tests {{ [Auto.AutoDeconstructable] {t.typeDefinition} }}", t.expectedCodePart) .SetName($"Settings{i + 1:00}")); [TestCaseSource(nameof(_settingsCases))] @@ -75,7 +73,7 @@ public void SettingsRetrieval_ShouldEmitProperValues(string source, string expec expectedCodePart = expectedCodePart.Substring(12); //arrange - var compilation = CreateCompilation(source); + var compilation = CreateValidCompilation(source); //act var generatedTrees = GetGeneratedTreesOnly(compilation); @@ -89,7 +87,7 @@ public void SettingsRetrieval_ShouldEmitProperValues(string source, string expec [Test] public void Generate_When_StaticUsing_And_Mnemonics() { - var compilation = CreateCompilation(@"using SD = System.Double; + var compilation = CreateValidCompilation(@"using SD = System.Double; using static Tests3.ContainerClass3; namespace Tests1 diff --git a/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.Diagnostics.cs b/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructable_Diagnostics.cs similarity index 74% rename from Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.Diagnostics.cs rename to Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructable_Diagnostics.cs index b2484d5..76a704e 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.Diagnostics.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructable_Diagnostics.cs @@ -1,21 +1,24 @@ extern alias original; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Nemesis.CodeAnalysis; using Nemesis.TextParsers.CodeGen.Deconstructable; -using static Nemesis.TextParsers.CodeGen.Tests.Utils; +using static Nemesis.TextParsers.CodeGen.Tests.CodeGenUtils; namespace Nemesis.TextParsers.CodeGen.Tests; [TestFixture] -public partial class AutoDeconstructableGeneratorTests +public class AutoDeconstructable_Diagnostics { - [TestCase(@"namespace Tests { [Auto.AutoDeconstructable] public partial record RecordPoint2d(double X, double Y) { } }")] + [TestCase(""" + namespace Tests; + [Auto.AutoDeconstructable] + public partial record RecordPoint2d(double X, double Y); + """)] public void DiagnosticsRemoval_LackOfAutoAttribute(string source) { - var compilation = CreateCompilation(source); + var compilation = CreateValidCompilation(source); var initialDiagnostics = CompilationUtils.GetCompilationIssues(compilation).ToList(); Assert.That(initialDiagnostics, Has.Count.EqualTo(1)); @@ -26,12 +29,18 @@ public void DiagnosticsRemoval_LackOfAutoAttribute(string source) Assert.That(diagnostics, Is.Empty); } - [TestCaseSource(nameof(_endToEndCases))] - public void DiagnosticsRemoval_LackOfAutoAttribute_EndToEnd(string source, string _) => DiagnosticsRemoval_LackOfAutoAttribute(source); + [Test] + public void DiagnosticsRemoval_LackOfAutoAttribute_EndToEnd() => Assert.Multiple(() => + { + foreach (var (_, source, _) in EndToEndCases.GetAutoDeconstructableCases()) + { + DiagnosticsRemoval_LackOfAutoAttribute(source); + } + }); - private static readonly IEnumerable _negativeDiagnostics = new (string source, string rule, string expectedMessagePart)[] + private static readonly IEnumerable _negativeDiagnostics = new (string source, string rule, string expectedMessagePart)[] { (@"[AutoDeconstructable] class NonPartial { }", nameof(AutoDeconstructableGenerator.NonPartialTypeRule), "Type decorated with AutoDeconstructableAttribute must be also declared partial"), @@ -59,7 +68,7 @@ partial class Ctor1Deconstruct2 partial class ContainingType { [Auto.AutoDeconstructable] partial class Test{} } }", nameof(AutoDeconstructableGenerator.NamespaceAndTypeNamesEqualRule), "Test: Type name cannot be equal to containing namespace: 'Nemesis.TextParsers.CodeGen.Tests.Test'"), } - .Select((t, i) => new TestCaseData($@"using Auto; using Nemesis.TextParsers.Settings; namespace Nemesis.TextParsers.CodeGen.Tests {{ {t.source} }}", t.rule, t.expectedMessagePart) + .Select((t, i) => new TCD($@"using Auto; using Nemesis.TextParsers.Settings; namespace Nemesis.TextParsers.CodeGen.Tests {{ {t.source} }}", t.rule, t.expectedMessagePart) .SetName($"Negative{i + 1:00}_{t.rule}")); [TestCaseSource(nameof(_negativeDiagnostics))] @@ -69,7 +78,7 @@ public void Diagnostics_CheckNegativeCases(string source, string ruleName, strin .GetField(ruleName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) ?.GetValue(null) ?? throw new NotSupportedException($"Rule '{ruleName}' does not exist"); - var compilation = CreateCompilation(source); + var compilation = CreateValidCompilation(source); CompilationUtils.RunGenerators(compilation, out var diagnostics, new AutoDeconstructableGenerator()); @@ -85,22 +94,10 @@ public void Diagnostics_CheckNegativeCases(string source, string ruleName, strin } [Test] - public void Diagnostics_RemoveSettingsAttribute() + public void Diagnostics_NoSettingsAttributeReference() { - var source = @"[AutoDeconstructable] partial class DoesNotMatter { }"; - - var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location) ?? throw new InvalidOperationException("The location of the .NET assemblies cannot be retrieved"); - - var compilation = CSharpCompilation.Create("compilation", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest)) }, - new[] - { - MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")), - MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location), - //Important - no NTP reference - }, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - + //Important - no NTP reference - test is about removal of NTP library and expected code gen (lack of) ability to generate code + var compilation = CreateTestCompilation(@"[AutoDeconstructable] partial class DoesNotMatter { }"); CompilationUtils.RunGenerators(compilation, out var diagnostics, new AutoDeconstructableGenerator()); var diagnosticsList = diagnostics.ToList(); diff --git a/Nemesis.TextParsers.CodeGen.Tests/CodeGenUtils.cs b/Nemesis.TextParsers.CodeGen.Tests/CodeGenUtils.cs new file mode 100644 index 0000000..8af266c --- /dev/null +++ b/Nemesis.TextParsers.CodeGen.Tests/CodeGenUtils.cs @@ -0,0 +1,125 @@ +#nullable enable +extern alias original; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +using Nemesis.CodeAnalysis; +using Nemesis.TextParsers.CodeGen.Deconstructable; + +namespace Nemesis.TextParsers.CodeGen.Tests; + +internal static class CodeGenUtils +{ + public static Compilation CreateValidCompilation(string source, [CallerMemberName] string? memberName = null) => + CreateTestCompilation(source, [typeof(original::Nemesis.TextParsers.ITransformer).GetTypeInfo().Assembly], memberName); + + public static Compilation CreateTestCompilation(string source, Assembly[]? additionalAssemblies = null, [CallerMemberName] string? memberName = null) + { + var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location) ?? throw new InvalidOperationException("The location of the .NET assemblies cannot be retrieved"); + + static SyntaxTree Parse(string source) => + CSharpSyntaxTree + .ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest)); + + SyntaxTree[] trees = + [ + Parse(source) +#if !NET + , + Parse(""" + namespace System.Runtime.CompilerServices + { + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + internal static class IsExternalInit { } + } + + """) +#endif + ]; + + var references = new List(8); + void AddRef(string path) => + references.Add(MetadataReference.CreateFromFile(path)); + + foreach (var t in new[] { typeof(Binder), typeof(BigInteger) }) + AddRef(t.GetTypeInfo().Assembly.Location); + + if (additionalAssemblies is not null) + foreach (var ass in additionalAssemblies) + AddRef(ass.Location); +#if NET + AddRef(Path.Combine(assemblyPath, "System.Runtime.dll")); +#else + var standardAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == "netstandard"); + + AddRef(standardAssembly?.Location + ?? throw new NotSupportedException("netstandard is needed for legacy framework tests") + ); + + AddRef(typeof(System.ComponentModel.EditorBrowsableAttribute).GetTypeInfo().Assembly.Location); +#endif + + return CSharpCompilation.Create($"{memberName}_Compilation", trees, + references, new(OutputKind.DynamicallyLinkedLibrary)); + } + + + private static readonly Regex _headerPattern = new(@"/\*\s* .+? \s*\*/", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); + private static readonly Regex _generatorPattern = new(@""".*Generator""\s*,\s*""([0-9.]+)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); + + public static string ScrubGeneratorComments(string text) + { + text = _generatorPattern.Replace(text, "string.Empty, string.Empty"); + text = _headerPattern.Replace(text, "//HEAD"); + return text; + } + + public static IReadOnlyList GetGeneratedTreesOnly(Compilation compilation, int requiredCardinality = 1) + { + var newComp = CompilationUtils.RunGenerators(compilation, out var diagnostics, new AutoDeconstructableGenerator()); + Assert.That(diagnostics, Is.Empty); + + SyntaxTree? attributeTree = null; + foreach (var tree in newComp.SyntaxTrees) + { + var attributeDeclaration = tree.GetRoot().DescendantNodes().OfType() + .FirstOrDefault(cds => string.Equals(cds.Identifier.ValueText, AutoDeconstructableGenerator.ATTRIBUTE_NAME, StringComparison.Ordinal)); + if (attributeDeclaration != null) + { + attributeTree = tree; + break; + } + } + Assert.That(attributeTree, Is.Not.Null, "Auto attribute not found among generated trees"); + + var toRemove = compilation.SyntaxTrees.Append(attributeTree!); + + var generatedTrees = newComp.RemoveSyntaxTrees(toRemove).SyntaxTrees.ToList(); + Assert.That(generatedTrees, Has.Count.EqualTo(requiredCardinality)); + + return generatedTrees.Select(tree => + ((CompilationUnitSyntax)tree.GetRoot()) + .ToFullString()).ToList(); + } +} + + +internal class IgnoreNewLinesComparer : IComparer, IEqualityComparer +{ + public static readonly IComparer Comparer = new IgnoreNewLinesComparer(); + + public static readonly IEqualityComparer EqualityComparer = new IgnoreNewLinesComparer(); + + public int Compare(string? x, string? y) => string.CompareOrdinal(NormalizeNewLines(x), NormalizeNewLines(y)); + + public bool Equals(string? x, string? y) => NormalizeNewLines(x) == NormalizeNewLines(y); + + public int GetHashCode(string s) => NormalizeNewLines(s)?.GetHashCode() ?? 0; + + public static string? NormalizeNewLines(string? s) => s? + .Replace(Environment.NewLine, "") + .Replace("\n", "") + .Replace("\r", ""); +} diff --git a/Nemesis.TextParsers.CodeGen.Tests/EndToEndCases.cs b/Nemesis.TextParsers.CodeGen.Tests/EndToEndCases.cs index 6d32730..7472358 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/EndToEndCases.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/EndToEndCases.cs @@ -2,8 +2,20 @@ static class EndToEndCases { - public static IReadOnlyList<(string name, string source, string expectedCode)> AutoDeconstructableCases() => new[] - { + public static IReadOnlyList<(string name, string source, string expectedCode)> GetAutoDeconstructableCases() => + GetData().Select(t => + ( + t.name, + $$""" + using Nemesis.TextParsers.Settings; + namespace Nemesis.TextParsers.CodeGen.Tests; + {{t.source}} + """, + t.expectedCode) + ).ToList(); + + private static IReadOnlyList<(string name, string source, string expectedCode)> GetData() => + [ ("SimpleWrapperRecord", @"[Auto.AutoDeconstructable] partial record eDoubleRecord(double Value) { }", @"//HEAD using System; @@ -529,5 +541,5 @@ public override string Format(ComplexTypes element) } } }") - }; + ]; } diff --git a/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj b/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj index 4bbe678..7e6c89d 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj +++ b/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj @@ -1,13 +1,13 @@  - - - net6.0 + + net6.0;net48 true $(NoWarn);NU1603 - - + + + diff --git a/Nemesis.TextParsers.CodeGen.Tests/Utils.cs b/Nemesis.TextParsers.CodeGen.Tests/Utils.cs deleted file mode 100644 index 6cb33ac..0000000 --- a/Nemesis.TextParsers.CodeGen.Tests/Utils.cs +++ /dev/null @@ -1,81 +0,0 @@ -extern alias original; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using Nemesis.CodeAnalysis; -using Nemesis.TextParsers.CodeGen.Deconstructable; - -namespace Nemesis.TextParsers.CodeGen.Tests; - -internal static class Utils -{ - public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) - { - var (compilation, _, _) = CompilationUtils.CreateTestCompilation(source, new[] - { - typeof(BigInteger).GetTypeInfo().Assembly, - typeof(original::Nemesis.TextParsers.ITransformer).GetTypeInfo().Assembly - }, outputKind); - - return compilation; - } - - - private static readonly Regex _headerPattern = new(@"/\*\s* .+? \s*\*/", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); - private static readonly Regex _generatorPattern = new(@""".*Generator""\s*,\s*""([0-9.]+)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); - - public static string ScrubGeneratorComments(string text) - { - text = _generatorPattern.Replace(text, "string.Empty, string.Empty"); - text = _headerPattern.Replace(text, "//HEAD"); - - return text; - } - - public static IReadOnlyList GetGeneratedTreesOnly(Compilation compilation, int requiredCardinality = 1) - { - var newComp = CompilationUtils.RunGenerators(compilation, out var diagnostics, new AutoDeconstructableGenerator()); - Assert.That(diagnostics, Is.Empty); - - SyntaxTree attributeTree = null; - foreach (var tree in newComp.SyntaxTrees) - { - var attributeDeclaration = tree.GetRoot().DescendantNodes().OfType() - .FirstOrDefault(cds => string.Equals(cds.Identifier.ValueText, AutoDeconstructableGenerator.ATTRIBUTE_NAME, StringComparison.Ordinal)); - if (attributeDeclaration != null) - { - attributeTree = tree; - break; - } - } - Assert.That(attributeTree, Is.Not.Null, "Auto attribute not found among generated trees"); - - var toRemove = compilation.SyntaxTrees.Append(attributeTree); - - var generatedTrees = newComp.RemoveSyntaxTrees(toRemove).SyntaxTrees.ToList(); - Assert.That(generatedTrees, Has.Count.EqualTo(requiredCardinality)); - - return generatedTrees.Select(tree => - ((CompilationUnitSyntax)tree.GetRoot()) - .ToFullString()).ToList(); - } -} - - -internal class IgnoreNewLinesComparer : IComparer, IEqualityComparer -{ - public static readonly IComparer Comparer = new IgnoreNewLinesComparer(); - - public static readonly IEqualityComparer EqualityComparer = new IgnoreNewLinesComparer(); - - public int Compare(string x, string y) => string.CompareOrdinal(NormalizeNewLines(x), NormalizeNewLines(y)); - - public bool Equals(string x, string y) => NormalizeNewLines(x) == NormalizeNewLines(y); - - public int GetHashCode(string s) => NormalizeNewLines(s)?.GetHashCode() ?? 0; - - public static string NormalizeNewLines(string s) => s? - .Replace(Environment.NewLine, "") - .Replace("\n", "") - .Replace("\r", ""); -} diff --git a/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj b/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj index deef128..10f6ebe 100644 --- a/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj +++ b/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj @@ -5,29 +5,14 @@ true true - - - - true - $(PackageIdPrefix)$(AssemblyName) - codegen codegeneration generation trnasformer parse ReadOnlySpan Span Memory fast allocation noAllocation - Contains various parser optimized for speed and no allocation - - - RELEASE_NOTES_PLACEHOLDER - - true false true - - review-icon.png - - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - true - true - embedded - true + + + true + codegen codegeneration generation trnasformer parse ReadOnlySpan Span Memory fast allocation noAllocation + Contains various code generators for parser optimized for speed and no allocation @@ -42,16 +27,12 @@ - - - - + - <_Parameter1>$(MSBuildProjectName).Tests, PublicKey=00240000048000001a010000060200000024000052534131300800000100010035c8d69e21b106b1164c8fc9c108ed2c08b283d13af6028fc6d6dd07ddd98039bcd99689793df5eef77230ce0a469dfb3ba7575ec699a6e001224ef90b3ce3437e873f0e5bc267a992a78ce1ecb85545d021f17ce51dccf9b3b2cb418aa9adcd2cf93fcc53ab12cb80a5cd51dcf6f3f3be70777b5dbf6d43dc20801be7f9d8220d8ac1082391647e650ff596673c8cd40257f113c8d59f8b150cebc991eeedc69a9c1d442f93089a276aad3122cf90feafb02a384524fcab4d269de23aa5666c6fcc8b89766455d8e0fe9e65d1034673382c596cc60ee8d1b1b4fedb767ff05d7d6cdae0c0db091c24311ae373f98887826256298d72a772a3a8abee357a28f6a5bb4f4369ab diff --git a/Nemesis.TextParsers.Tests/Collections/DictionaryTests.cs b/Nemesis.TextParsers.Tests/Collections/DictionaryTests.cs index d5d060f..c4b3878 100644 --- a/Nemesis.TextParsers.Tests/Collections/DictionaryTests.cs +++ b/Nemesis.TextParsers.Tests/Collections/DictionaryTests.cs @@ -1,9 +1,5 @@ // ReSharper disable RedundantUsingDirective -using System; -using System.Collections.Generic; -using System.Linq; using Nemesis.TextParsers.Tests.Utils; -using NUnit.Framework; using static Nemesis.TextParsers.Tests.Utils.TestHelper; using Dss = System.Collections.Generic.SortedDictionary; // ReSharper restore RedundantUsingDirective diff --git a/Nemesis.TextParsers.Tests/ExploratoryTests.cs b/Nemesis.TextParsers.Tests/ExploratoryTests.cs index 065f07c..c3a61b1 100644 --- a/Nemesis.TextParsers.Tests/ExploratoryTests.cs +++ b/Nemesis.TextParsers.Tests/ExploratoryTests.cs @@ -49,7 +49,7 @@ static void GetTestCases(RandomSource randomSource) typeof(Int64Enum), typeof(UInt64Enum), typeof(LowPrecisionFloat), typeof(CarrotAndOnionFactors), typeof(BasisPoint), - typeof(Fruits[]), typeof(Dictionary), + typeof(Dictionary), typeof(SortedDictionary), typeof(SortedList), typeof(ReadOnlyDictionary>), } @@ -174,7 +174,7 @@ Regex GetRandomRegex() _fixture.Register(GetRandomRegex); _fixture.Register(() => new Version(_randomSource.Next(10), _randomSource.Next(10), _randomSource.Next(10), _randomSource.Next(10))); - _fixture.Register(() => new IPAddress(new[] { (byte)_randomSource.Next(255), (byte)_randomSource.Next(255), (byte)_randomSource.Next(255), (byte)_randomSource.Next(255) })); + _fixture.Register(() => new IPAddress([(byte)_randomSource.Next(255), (byte)_randomSource.Next(255), (byte)_randomSource.Next(255), (byte)_randomSource.Next(255)])); _fixture.Register(() => (EmptyEnum)_randomSource.Next(0, 2)); @@ -232,7 +232,7 @@ Regex GetRandomRegex() public void BeforeEachTest() { _randomSource.SetNewSeed(); - Console.WriteLine($"{GetType().Name}.{TestContext.CurrentContext.Test.Name} - seed = {_randomSource.Seed}"); + Console.WriteLine($"Seed = {_randomSource.Seed}"); //{GetType().Name}.{TestContext.CurrentContext.Test.Name} } [Test] @@ -272,7 +272,7 @@ public void TestCategory(ExploratoryTestCategory category) if (failed.Count > 0) Assert.Fail($"Failed cases({failed.Count} cases):{Environment.NewLine}{string.Join(Environment.NewLine, failed)}"); - Console.WriteLine($"Test cases run:{caseNo}"); + Console.WriteLine($"Run:{caseNo}"); void ShouldParseAndFormat(Type testType) { @@ -370,7 +370,7 @@ T ParseAndAssert(string text) } catch (AssertionException ae) { - throw new Exception($"Failed for {friendlyName} during: {reason} due to '{ae.Message}'"); + throw new Exception($"Assertion failed for {friendlyName} during: {reason} due to '{ae.Message}'"); } catch (Exception e) { @@ -462,8 +462,16 @@ static Type GetNullableCounterpart(Type t) => t.IsNullable(out var underlyingTyp structs.UnionWith(structs.Select(GetNullableCounterpart).ToList()); - arrays.UnionWith(simpleTypes.Select(t => t.MakeArrayType())); - arrays.UnionWith(simpleTypes.Select(t => t.MakeArrayType().MakeArrayType())); + arrays.UnionWith(simpleTypes +#if NETFRAMEWORK + .Where(t => !t.IsEnum) +#endif + .Select(t => t.MakeArrayType())); + arrays.UnionWith(simpleTypes +#if NETFRAMEWORK + .Where(t => !t.IsEnum) +#endif + .Select(t => t.MakeArrayType().MakeArrayType())); collections.UnionWith(simpleTypes.Select(t => typeof(List<>).MakeGenericType(t))); @@ -544,7 +552,7 @@ public static void RegisterAllNullable(Fixture fixture, RandomSource randomSourc foreach (var elementType in structs) { var concreteMethod = registerMethod.MakeGenericMethod(elementType); - concreteMethod.Invoke(null, new object[] { fixture, randomSource }); + concreteMethod.Invoke(null, [fixture, randomSource]); } } @@ -566,7 +574,7 @@ public static void RegisterAllCollections(Fixture fixture, RandomSource randomSo foreach (var elementType in elementTypes) { var concreteMethod = registerMethod.MakeGenericMethod(elementType); - concreteMethod.Invoke(null, new object[] { fixture, randomSource }); + concreteMethod.Invoke(null, [fixture, randomSource]); } } @@ -624,7 +632,7 @@ public static void RegisterAllValueTuples(Fixture fixture, IEnumerable tup method = method.MakeGenericMethod(elementTypes); - method.Invoke(null, new object[] { fixture }); + method.Invoke(null, [fixture]); } } diff --git a/Nemesis.TextParsers.Tests/Nemesis.TextParsers.Tests.csproj b/Nemesis.TextParsers.Tests/Nemesis.TextParsers.Tests.csproj index 33ca0e8..604b9bf 100644 --- a/Nemesis.TextParsers.Tests/Nemesis.TextParsers.Tests.csproj +++ b/Nemesis.TextParsers.Tests/Nemesis.TextParsers.Tests.csproj @@ -14,10 +14,6 @@ - - - - @@ -32,11 +28,7 @@ - - - - - + diff --git a/Nemesis.TextParsers.Tests/Utils/RandomSource.cs b/Nemesis.TextParsers.Tests/Utils/RandomSource.cs index 22c5139..4304653 100644 --- a/Nemesis.TextParsers.Tests/Utils/RandomSource.cs +++ b/Nemesis.TextParsers.Tests/Utils/RandomSource.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using JetBrains.Annotations; +using JetBrains.Annotations; using Nemesis.TextParsers.Parsers; namespace Nemesis.TextParsers.Tests.Utils; diff --git a/Nemesis.TextParsers.Tests/Utils/RandomSourceTests.cs b/Nemesis.TextParsers.Tests/Utils/RandomSourceTests.cs index 9a205df..5c1ec77 100644 --- a/Nemesis.TextParsers.Tests/Utils/RandomSourceTests.cs +++ b/Nemesis.TextParsers.Tests/Utils/RandomSourceTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; - -namespace Nemesis.TextParsers.Tests.Utils; +namespace Nemesis.TextParsers.Tests.Utils; [TestFixture] internal class RandomSourceTests diff --git a/Nemesis.TextParsers.Tests/Utils/Sut.cs b/Nemesis.TextParsers.Tests/Utils/Sut.cs index 7917107..e85e37a 100644 --- a/Nemesis.TextParsers.Tests/Utils/Sut.cs +++ b/Nemesis.TextParsers.Tests/Utils/Sut.cs @@ -1,130 +1,126 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Nemesis.Essentials.Runtime; +using Nemesis.Essentials.Runtime; using Nemesis.TextParsers.Settings; -namespace Nemesis.TextParsers.Tests.Utils +namespace Nemesis.TextParsers.Tests.Utils; + +internal static class Sut { - internal static class Sut + public static ITransformerStore DefaultStore { get; } = TextTransformer.Default; + public static ITransformerStore ThrowOnDuplicateStore { get; } + public static ITransformerStore BorderedStore { get; } + public static ITransformerStore RandomStore { get; } + + static Sut() { - public static ITransformerStore DefaultStore { get; } = TextTransformer.Default; - public static ITransformerStore ThrowOnDuplicateStore { get; } - public static ITransformerStore BorderedStore { get; } - public static ITransformerStore RandomStore { get; } + ThrowOnDuplicateStore = TextTransformer.GetDefaultStoreWith( + SettingsStoreBuilder.GetDefault() + .AddOrUpdate( + DictionarySettings.Default + .With(s => s.Behaviour, DictionaryBehaviour.ThrowOnDuplicate) + ).Build()); - static Sut() - { - ThrowOnDuplicateStore = TextTransformer.GetDefaultStoreWith( - SettingsStoreBuilder.GetDefault() - .AddOrUpdate( - DictionarySettings.Default - .With(s => s.Behaviour, DictionaryBehaviour.ThrowOnDuplicate) - ).Build()); + BorderedStore = BuildBorderedStore(); + RandomStore = BuildRandomStore(); + } - BorderedStore = BuildBorderedStore(); - RandomStore = BuildRandomStore(); - } + private static ITransformerStore BuildRandomStore() + { + static bool IsChar(Type t) => t == typeof(char) || t == typeof(char?); - private static ITransformerStore BuildRandomStore() + var settingTypes = new[] { - static bool IsChar(Type t) => t == typeof(char) || t == typeof(char?); + typeof(ArraySettings), typeof(CollectionSettings), typeof(DictionarySettings), + typeof(DeconstructableSettings), typeof(KeyValuePairSettings), typeof(ValueTupleSettings), + }; + int seed = Environment.TickCount / 10; + var special = new[] { '\\', '|', ';', '=', '∅', ',', '{', '}', '[', ']', '(', ')', '⮿', '␀', '!', '@', '#', '$', '%', '&' }; - var settingTypes = new[] - { - typeof(ArraySettings), typeof(CollectionSettings), typeof(DictionarySettings), - typeof(DeconstructableSettings), typeof(KeyValuePairSettings), typeof(ValueTupleSettings), - }; - int seed = Environment.TickCount / 10; - var special = new[] { '\\', '|', ';', '=', '∅', ',', '{', '}', '[', ']', '(', ')', '⮿', '␀', '!', '@', '#', '$', '%', '&' }; - - Stack GetRandomChars(int length, string reason) - { - seed += 10; - var rand = new Random(seed); - var result = new HashSet( + Stack GetRandomChars(int length, string reason) + { + seed += 10; + var rand = new Random(seed); + var result = new HashSet( #if !NET462 - length + length #endif - ); - do - { - result.Add( - special[rand.Next(special.Length)] - ); - } while (result.Count < length); - Console.WriteLine($"Seed for {reason} = {seed} [{string.Join(", ", result.Select(c => $"'{c}'"))}]"); - return new Stack(result); - } - - - var settings = new List(); - - foreach (var settingType in settingTypes) + ); + do { - var ctor = settingType.GetConstructors().Select(c => (Ctor: c, Params: c.GetParameters())) - .Where(pair => (pair.Params?.Length ?? 0) > 0) - .OrderByDescending(p => p.Params.Length) - .FirstOrDefault().Ctor ?? throw new NotSupportedException($"No suitable constructor found for {settingType}"); - var @params = ctor.GetParameters(); - var charNum = @params.Count(p => IsChar(p.ParameterType)); - var chars = GetRandomChars(charNum, settingType.Name); + result.Add( + special[rand.Next(special.Length)] + ); + } while (result.Count < length); + Console.WriteLine($"Seed for {reason} = {seed} [{string.Join(", ", result.Select(c => $"'{c}'"))}]"); + return new Stack(result); + } - var args = @params - .Select(p => p.ParameterType) - .Select(t => IsChar(t) ? chars.Pop() : TypeMeta.GetDefault(t)) - .ToArray(); - settings.Add((ISettings)ctor.Invoke(args)); - } + var settings = new List(); - var settingsStoreBuilder = SettingsStoreBuilder.GetDefault(); + foreach (var settingType in settingTypes) + { + var ctor = settingType.GetConstructors().Select(c => (Ctor: c, Params: c.GetParameters())) + .Where(pair => (pair.Params?.Length ?? 0) > 0) + .OrderByDescending(p => p.Params.Length) + .FirstOrDefault().Ctor ?? throw new NotSupportedException($"No suitable constructor found for {settingType}"); + var @params = ctor.GetParameters(); + var charNum = @params.Count(p => IsChar(p.ParameterType)); + var chars = GetRandomChars(charNum, settingType.Name); + + var args = @params + .Select(p => p.ParameterType) + .Select(t => IsChar(t) ? chars.Pop() : TypeMeta.GetDefault(t)) + .ToArray(); + settings.Add((ISettings)ctor.Invoke(args)); + } - foreach (var s in settings) - settingsStoreBuilder.AddOrUpdate(s); - return TextTransformer.GetDefaultStoreWith(settingsStoreBuilder.Build()); - } + var settingsStoreBuilder = SettingsStoreBuilder.GetDefault(); - private static ITransformerStore BuildBorderedStore() - { - //F# influenced settings - var borderedDictionary = DictionarySettings.Default - .With(s => s.Start, '{') - .With(s => s.End, '}') - .With(s => s.DictionaryKeyValueDelimiter, ',') - ; - var borderedCollection = CollectionSettings.Default - .With(s => s.Start, '[') - .With(s => s.End, ']') - .With(s => s.ListDelimiter, ';') - ; - var borderedArray = ArraySettings.Default - .With(s => s.ListDelimiter, ',') - .With(s => s.Start, '|') - .With(s => s.End, '|') - ; - var weirdTuple = ValueTupleSettings.Default - .With(s => s.NullElementMarker, '␀') - .With(s => s.Delimiter, '⮿') - .With(s => s.Start, '/') - .With(s => s.End, '/') - ; - var borderedStore = SettingsStoreBuilder.GetDefault() - .AddOrUpdate(borderedArray) - .AddOrUpdate(borderedCollection) - .AddOrUpdate(borderedDictionary) - .AddOrUpdate(weirdTuple) - .Build(); - - return TextTransformer.GetDefaultStoreWith(borderedStore); - } + foreach (var s in settings) + settingsStoreBuilder.AddOrUpdate(s); - public static ITransformer GetTransformer() - => DefaultStore.GetTransformer(); + return TextTransformer.GetDefaultStoreWith(settingsStoreBuilder.Build()); + } - public static ITransformer GetTransformer(Type type) - => DefaultStore.GetTransformer(type); + private static ITransformerStore BuildBorderedStore() + { + //F# influenced settings + var borderedDictionary = DictionarySettings.Default + .With(s => s.Start, '{') + .With(s => s.End, '}') + .With(s => s.DictionaryKeyValueDelimiter, ',') + ; + var borderedCollection = CollectionSettings.Default + .With(s => s.Start, '[') + .With(s => s.End, ']') + .With(s => s.ListDelimiter, ';') + ; + var borderedArray = ArraySettings.Default + .With(s => s.ListDelimiter, ',') + .With(s => s.Start, '|') + .With(s => s.End, '|') + ; + var weirdTuple = ValueTupleSettings.Default + .With(s => s.NullElementMarker, '␀') + .With(s => s.Delimiter, '⮿') + .With(s => s.Start, '/') + .With(s => s.End, '/') + ; + var borderedStore = SettingsStoreBuilder.GetDefault() + .AddOrUpdate(borderedArray) + .AddOrUpdate(borderedCollection) + .AddOrUpdate(borderedDictionary) + .AddOrUpdate(weirdTuple) + .Build(); + + return TextTransformer.GetDefaultStoreWith(borderedStore); } + + public static ITransformer GetTransformer() + => DefaultStore.GetTransformer(); + + public static ITransformer GetTransformer(Type type) + => DefaultStore.GetTransformer(type); } diff --git a/Nemesis.TextParsers.Tests/Utils/TestHelper.cs b/Nemesis.TextParsers.Tests/Utils/TestHelper.cs index d27c82b..c88c19b 100644 --- a/Nemesis.TextParsers.Tests/Utils/TestHelper.cs +++ b/Nemesis.TextParsers.Tests/Utils/TestHelper.cs @@ -54,7 +54,7 @@ public static void IsMutuallyEquivalent(object o1, object o2, string because = " var method = typeof(TestHelper).GetMethod(nameof(ArraySegmentEquals), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(TestHelper), nameof(ArraySegmentEquals)); method = method.MakeGenericMethod(ase1); - var equals = (bool)method.Invoke(null, new[] { o1, o2 }); + var equals = (bool)method.Invoke(null, [o1, o2]); Assert.That(equals, Is.True, "o1 != o2 when treated as ArraySegment"); } diff --git a/Nemesis.TextParsers.sln b/Nemesis.TextParsers.sln index 27e1fd5..1cbd0a7 100644 --- a/Nemesis.TextParsers.sln +++ b/Nemesis.TextParsers.sln @@ -13,12 +13,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore - appveyor.yml = appveyor.yml CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Directory.Packages.props = Directory.Packages.props - GetNetCoreVersions.ps1 = GetNetCoreVersions.ps1 + global.json = global.json LICENSE.txt = LICENSE.txt Nemesis.TextParsers.Public.snk = Nemesis.TextParsers.Public.snk README.md = README.md @@ -33,7 +32,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nemesis.TextParsers.CodeGen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebDemo", "WebDemo\WebDemo.csproj", "{0D0D7B92-8060-479C-BC59-0EF5E4E85C18}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nemesis.TextParsers.ArchTests", "Nemesis.TextParsers.ArchTests\Nemesis.TextParsers.ArchTests.csproj", "{EC0AC278-C20E-43F9-916A-A5BAA702C9DA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nemesis.TextParsers.ArchTests", "Nemesis.TextParsers.ArchTests\Nemesis.TextParsers.ArchTests.csproj", "{EC0AC278-C20E-43F9-916A-A5BAA702C9DA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{07A21CBE-BC37-4086-ACB9-9D1F3DF0332E}" + ProjectSection(SolutionItems) = preProject + .github\workflows\ci.yml = .github\workflows\ci.yml + .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + .github\workflows\license-scanning.yml = .github\workflows\license-scanning.yml + .github\workflows\test-report.yml = .github\workflows\test-report.yml + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -77,6 +84,9 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {07A21CBE-BC37-4086-ACB9-9D1F3DF0332E} = {9E1DB4A9-1567-47CD-A602-6625864E752A} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6A540D32-2D88-4946-806B-3E613476F833} EndGlobalSection diff --git a/Nemesis.TextParsers/Legacy/Number.NumberBuffer.cs b/Nemesis.TextParsers/Legacy/Number.NumberBuffer.cs index bf27c4b..aadb319 100644 --- a/Nemesis.TextParsers/Legacy/Number.NumberBuffer.cs +++ b/Nemesis.TextParsers/Legacy/Number.NumberBuffer.cs @@ -4,14 +4,14 @@ #if NETSTANDARD2_0 || NETFRAMEWORK using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; // ReSharper disable once CheckNamespace namespace Legacy { [SuppressMessage("ReSharper", "InconsistentNaming")] - public static partial class Number + internal static partial class Number { private const int NumberMaxDigits = 50; // needs to == NUMBER_MAXDIGITS in coreclr's src/classlibnative/bcltype/number.h. diff --git a/Nemesis.TextParsers/Legacy/Number.Parsing.cs b/Nemesis.TextParsers/Legacy/Number.Parsing.cs index 5984d6f..5d34b33 100644 --- a/Nemesis.TextParsers/Legacy/Number.Parsing.cs +++ b/Nemesis.TextParsers/Legacy/Number.Parsing.cs @@ -28,7 +28,7 @@ namespace Legacy // NaNs or Infinities. [SuppressMessage("ReSharper", "InconsistentNaming")] - public static partial class Number + internal static partial class Number { private const int Int32Precision = 10; private const int UInt32Precision = Int32Precision; diff --git a/Nemesis.TextParsers/Legacy/Number.Small.cs b/Nemesis.TextParsers/Legacy/Number.Small.cs index 8b9b885..a5f02e5 100644 --- a/Nemesis.TextParsers/Legacy/Number.Small.cs +++ b/Nemesis.TextParsers/Legacy/Number.Small.cs @@ -12,7 +12,7 @@ // ReSharper disable once CheckNamespace namespace Legacy { - public static partial class Number + internal static partial class Number { private const NumberStyles INVALID_NUMBER_STYLES = ~( NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign | NumberStyles.AllowParentheses | @@ -36,7 +36,7 @@ internal static void ValidateParseStyleInteger(NumberStyles style) } } - public static class ByteParser + internal static class ByteParser { private static byte MinValue => byte.MinValue; private static byte MaxValue => byte.MaxValue; @@ -90,7 +90,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor } - public static class SByteParser + internal static class SByteParser { private static sbyte MinValue => sbyte.MinValue; private static sbyte MaxValue => sbyte.MaxValue; @@ -164,7 +164,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor } - public static class Int16Parser + internal static class Int16Parser { private static short MinValue => short.MinValue; private static short MaxValue => short.MaxValue; @@ -243,7 +243,7 @@ private static bool TryParse(ReadOnlySpan s, NumberStyles style, NumberFor } - public static class UInt16Parser + internal static class UInt16Parser { #pragma warning disable IDE0051 // Remove unused private members private static ushort MinValue => ushort.MinValue; diff --git a/Nemesis.TextParsers/Nemesis.TextParsers.csproj b/Nemesis.TextParsers/Nemesis.TextParsers.csproj index e95a143..408dba5 100644 --- a/Nemesis.TextParsers/Nemesis.TextParsers.csproj +++ b/Nemesis.TextParsers/Nemesis.TextParsers.csproj @@ -2,44 +2,15 @@ net7.0;net6.0;netstandard2.1;netstandard2.0 + true split stringSplit tokenize token parse format list dictionary TextConverter ReadOnlySpan Span Memory fast allocation noAllocation Contains various parser optimized for speed and no allocation. - - true - $(PackageIdPrefix)$(AssemblyName) - properties\review-icon.png - properties\README.md - - - RELEASE_NOTES_PLACEHOLDER - - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - true - true - - - - - - Properties\icon.png - - - - Properties\README.md - - - - - true - - - - - + + diff --git a/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs b/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs index 72f68cc..84b540c 100644 --- a/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs +++ b/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs @@ -118,7 +118,7 @@ public sealed class BooleanTransformer : SimpleTransformer { #if NETSTANDARD2_0 || NETFRAMEWORK [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - internal static bool EqualsOrdinalIgnoreCase(ReadOnlySpan span, ReadOnlySpan value) + private static bool EqualsOrdinalIgnoreCase(ReadOnlySpan span, ReadOnlySpan value) { if (span.Length != value.Length) return false; @@ -131,9 +131,9 @@ internal static bool EqualsOrdinalIgnoreCase(ReadOnlySpan span, ReadOnlySp return true; } - internal const string TRUE_LITERAL = "True"; - internal const string FALSE_LITERAL = "False"; - public static bool TryParseBool(ReadOnlySpan value, out bool result) + private const string TRUE_LITERAL = "True"; + private const string FALSE_LITERAL = "False"; + private static bool TryParseBool(ReadOnlySpan value, out bool result) { ReadOnlySpan trueSpan = TRUE_LITERAL.AsSpan(); if (EqualsOrdinalIgnoreCase(trueSpan, value)) @@ -168,7 +168,7 @@ public static bool TryParseBool(ReadOnlySpan value, out bool result) return false; } - public static bool ParseBool(ReadOnlySpan value) => + private static bool ParseBool(ReadOnlySpan value) => TryParseBool(value, out bool result) ? result : throw new FormatException($"Boolean supports only case insensitive '{TRUE_LITERAL}' or '{FALSE_LITERAL}'"); private static ReadOnlySpan TrimWhiteSpaceAndNull(ReadOnlySpan value) diff --git a/README.md b/README.md index faa68ec..e7f35ce 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# ![Logo](https://raw.githubusercontent.com/nemesissoft/Nemesis.TextParsers/master/images/review-icon.png) Nemesis.TextParsers +# ![Logo](https://raw.githubusercontent.com/nemesissoft/Nemesis.TextParsers/main/images/icon.png) Nemesis.TextParsers -[![Build status - master](https://img.shields.io/appveyor/ci/Nemesis/nemesis-textparsers?style=flat-square)](https://ci.appveyor.com/project/Nemesis/nemesis-textparsers/branch/master) -[![Tests](https://img.shields.io/appveyor/tests/Nemesis/nemesis-textparsers?compact_message&style=flat-square)](https://ci.appveyor.com/project/Nemesis/nemesis-textparsers/build/tests) -[![Last commit](https://img.shields.io/github/last-commit/nemesissoft/Nemesis.TextParsers?style=flat-square)](https://github.com/nemesissoft/Nemesis.TextParsers) -[![Last release](https://img.shields.io/github/release-date/nemesissoft/Nemesis.TextParsers?style=flat-square)](https://ci.appveyor.com/project/Nemesis/nemesis-textparsers/build/artifacts) +[![Build status - main](https://img.shields.io/github/actions/workflow/status/nemesissoft/Nemesis.TextParsers/ci.yml?style=flat-square&label=build&logo=github)](https://github.com/nemesissoft/Nemesis.TextParsers/actions/workflows/ci.yml) +[![Tests](https://img.shields.io/github/actions/workflow/status/nemesissoft/Nemesis.TextParsers/test-report.yml?style=flat-square&label=tests&logo=github)](https://github.com/nemesissoft/Nemesis.TextParsers/actions/workflows/test-report.yml) +[![Last commit](https://img.shields.io/github/last-commit/nemesissoft/Nemesis.TextParsers?style=flat-square)](https://github.com/nemesissoft/Nemesis.TextParsers/commits/main/) +[![Last release](https://img.shields.io/github/release-date/nemesissoft/Nemesis.TextParsers?style=flat-square)](https://github.com/nemesissoft/Nemesis.TextParsers/releases/) [![Code size](https://img.shields.io/github/languages/code-size/nemesissoft/Nemesis.TextParsers.svg?style=flat-square)](https://github.com/nemesissoft/Nemesis.TextParsers) [![Issues](https://img.shields.io/github/issues/nemesissoft/Nemesis.TextParsers.svg?style=flat-square)](https://github.com/nemesissoft/Nemesis.TextParsers/issues) @@ -11,10 +11,12 @@ [![GitHub stars](https://img.shields.io/github/stars/nemesissoft/Nemesis.TextParsers?style=flat-square)](https://github.com/nemesissoft/Nemesis.TextParsers/stargazers) -[ - ![NuGet version](https://img.shields.io/nuget/v/Nemesis.TextParsers.svg?style=flat-square) - ![Downloads](https://img.shields.io/nuget/dt/Nemesis.TextParsers.svg?style=flat-square) -](https://www.nuget.org/packages/Nemesis.TextParsers/) +[![NuGet version](https://img.shields.io/nuget/v/Nemesis.TextParsers.svg?style=flat-square)](https://www.nuget.org/packages/Nemesis.TextParsers/) +![Downloads](https://img.shields.io/nuget/dt/Nemesis.TextParsers.svg?style=flat-square) + +![License](https://img.shields.io/github/license/nemesissoft/Nemesis.TextParsers) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fnemesissoft%2FNemesis.TextParsers.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fnemesissoft%2FNemesis.TextParsers?ref=badge_shield) + *** ## Benefits and Features @@ -39,7 +41,7 @@ TL;DR - are you looking for performant, non allocating serializer from structura | TextJsonNet | 1000 | 8,708.12 us | 1.07 | 3880800 B | | TextParsers | 1000 | 4,384.00 us | 0.54 | 402400 B | -More comprehensive examples are [here](https://github.com/nemesissoft/Nemesis.TextParsers/blob/master/Specification.md) +More comprehensive examples are [here](https://github.com/nemesissoft/Nemesis.TextParsers/blob/main/Specification.md) ### Other popular choices @@ -144,7 +146,7 @@ return accumulator.AsSpanTo(accumulator.Length > 0 ? accumulator.Length - 1 : 0) ## Funding Open source software is free to use but creating and maintaining is a laborious effort. Should you wish to support us in our noble endeavour, please consider the following donation methods: -[![Donate using Liberapay](https://raw.githubusercontent.com/nemesissoft/Nemesis.TextParsers/master/images/donate.svg)](https://liberapay.com/Michal.Brylka/donate) ![Liberapay receiving](https://img.shields.io/liberapay/receives/Michal.Brylka?color=blue&style=flat-square) +[![Donate using Liberapay](https://raw.githubusercontent.com/nemesissoft/Nemesis.TextParsers/main/images/donate.svg)](https://liberapay.com/Michal.Brylka/donate) ![Liberapay receiving](https://img.shields.io/liberapay/receives/Michal.Brylka?color=blue&style=flat-square) ## Todo / road map @@ -154,6 +156,9 @@ Open source software is free to use but creating and maintaining is a laborious ## Links -- [Documentation](https://github.com/nemesissoft/Nemesis.TextParsers/blob/master/Specification.md) +- [Documentation](https://github.com/nemesissoft/Nemesis.TextParsers/blob/main/Specification.md) - [NuGet Package](https://www.nuget.org/packages/Nemesis.TextParsers/) -- [Release Notes](https://github.com/nemesissoft/Nemesis.TextParsers/releases) \ No newline at end of file +- [Release Notes](https://github.com/nemesissoft/Nemesis.TextParsers/releases) + +## License +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fnemesissoft%2FNemesis.TextParsers.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fnemesissoft%2FNemesis.TextParsers?ref=badge_large) \ No newline at end of file diff --git a/Specification.md b/Specification.md index dc4d39e..584da53 100644 --- a/Specification.md +++ b/Specification.md @@ -1,4 +1,4 @@ -# ![Logo](https://raw.githubusercontent.com/nemesissoft/Nemesis.TextParsers/master/images/review-icon.png) Nemesis.TextParsers +# ![Logo](https://raw.githubusercontent.com/nemesissoft/Nemesis.TextParsers/main/images/icon.png) Nemesis.TextParsers *** diff --git a/WebDemo/Entities.cs b/WebDemo/Entities.cs new file mode 100644 index 0000000..6d65a61 --- /dev/null +++ b/WebDemo/Entities.cs @@ -0,0 +1,6 @@ +namespace WebDemo; + +class InvalidClass +{ + public string? Name { get; set; } +} \ No newline at end of file diff --git a/WebDemo/Program.cs b/WebDemo/Program.cs index 00f4f81..9551ab9 100644 --- a/WebDemo/Program.cs +++ b/WebDemo/Program.cs @@ -1,3 +1,4 @@ +using WebDemo; using WebDemo.Services; var builder = WebApplication.CreateBuilder(args); @@ -18,6 +19,7 @@ //TODO set JsonSerializerIsReflectionEnabledByDefault to false and make use of defined TypeInfoResolver below //options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseUpper; //options.SerializerOptions.TypeInfoResolver = AppJsonSerializerContext.Default; + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); }); @@ -39,45 +41,36 @@ app.MapPost("/parse/{type}", (string type, [FromQuery] string text) => { - var parsed = transformerStore.GetTransformer(Type.GetType(type)).ParseObject(text); - return parsed; -}).WithName("Parse").WithOpenApi(); + var t = Type.GetType(type); + if (t is null) return Results.BadRequest($"Type '{type}' cannot be parsed"); -app.Run(); - -sealed class ParsingSettings -{ - public CollectionSettings CollectionSettings { get; init; } = CollectionSettings.Default; - public ArraySettings ArraySettings { get; init; } = ArraySettings.Default; - public DictionarySettings DictionarySettings { get; init; } = DictionarySettings.Default; + ITransformer transformer; + try + { + transformer = transformerStore.GetTransformer(t); + } + catch (Exception ex) when (ex.InnerException is NotSupportedException) + { + return Results.NotFound($"Type '{type}' is not supported for text transformation"); + } - public EnumSettings EnumSettings { get; init; } = EnumSettings.Default; + try + { + var parsed = transformer.ParseObject(text); + return parsed; + } + catch (Exception e) + { + return Results.Problem($"Text '{text}' failed to parse with message: {e.Message}"); + } +}).WithName("Parse").WithOpenApi(); - public FactoryMethodSettings FactoryMethodSettings { get; init; } = FactoryMethodSettings.Default; +app.Run(); - public ValueTupleSettings ValueTupleSettings { get; init; } = ValueTupleSettings.Default; - public KeyValuePairSettings KeyValuePairSettings { get; init; } = KeyValuePairSettings.Default; - public DeconstructableSettings DeconstructableSettings { get; init; } = DeconstructableSettings.Default; - public ITransformerStore ToTransformerStore() - { - var settingsStore = SettingsStoreBuilder.GetDefault() - .AddOrUpdate(CollectionSettings) - .AddOrUpdate(ArraySettings) - .AddOrUpdate(DictionarySettings) - .AddOrUpdate(EnumSettings) - .AddOrUpdate(FactoryMethodSettings) - .AddOrUpdate(ValueTupleSettings) - .AddOrUpdate(KeyValuePairSettings) - .AddOrUpdate(DeconstructableSettings) - .Build(); - - return TextTransformer.GetDefaultStoreWith(settingsStore); - } -} [JsonSourceGenerationOptions( - PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseLower, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = true, UseStringEnumConverter = true )] diff --git a/WebDemo/Settings.cs b/WebDemo/Settings.cs new file mode 100644 index 0000000..6e18980 --- /dev/null +++ b/WebDemo/Settings.cs @@ -0,0 +1,32 @@ +namespace WebDemo; + +sealed class ParsingSettings +{ + public CollectionSettings CollectionSettings { get; init; } = CollectionSettings.Default; + public ArraySettings ArraySettings { get; init; } = ArraySettings.Default; + public DictionarySettings DictionarySettings { get; init; } = DictionarySettings.Default; + + public EnumSettings EnumSettings { get; init; } = EnumSettings.Default; + + public FactoryMethodSettings FactoryMethodSettings { get; init; } = FactoryMethodSettings.Default; + + public ValueTupleSettings ValueTupleSettings { get; init; } = ValueTupleSettings.Default; + public KeyValuePairSettings KeyValuePairSettings { get; init; } = KeyValuePairSettings.Default; + public DeconstructableSettings DeconstructableSettings { get; init; } = DeconstructableSettings.Default; + + public ITransformerStore ToTransformerStore() + { + var settingsStore = SettingsStoreBuilder.GetDefault() + .AddOrUpdate(CollectionSettings) + .AddOrUpdate(ArraySettings) + .AddOrUpdate(DictionarySettings) + .AddOrUpdate(EnumSettings) + .AddOrUpdate(FactoryMethodSettings) + .AddOrUpdate(ValueTupleSettings) + .AddOrUpdate(KeyValuePairSettings) + .AddOrUpdate(DeconstructableSettings) + .Build(); + + return TextTransformer.GetDefaultStoreWith(settingsStore); + } +} \ No newline at end of file diff --git a/WebDemo/WebDemo.csproj b/WebDemo/WebDemo.csproj index 3818829..b48a984 100644 --- a/WebDemo/WebDemo.csproj +++ b/WebDemo/WebDemo.csproj @@ -6,6 +6,7 @@ enable true + false diff --git a/WebDemo/WebDemo.http b/WebDemo/WebDemo.http index fef7d08..b194d37 100644 --- a/WebDemo/WebDemo.http +++ b/WebDemo/WebDemo.http @@ -3,4 +3,24 @@ GET {{WebDemo_HostAddress}}/showConfigurations Accept: application/json -### + +### Valid +POST {{WebDemo_HostAddress}}/parse/System.Int32[]?text={1;22;333} +Accept: application/json + +### Not existing type +POST {{WebDemo_HostAddress}}/parse/System.Int32[?text={1;22;333} +Accept: application/json + + +### Wrong class for transformation +POST {{WebDemo_HostAddress}}/parse/WebDemo.InvalidClass?text=ABCD +Accept: application/json + + +### Wrong payload +POST {{WebDemo_HostAddress}}/parse/System.Int32[]?text=ABCD +Accept: application/json + + + diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 4941fd7..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,57 +0,0 @@ -version: '{build}' -image: Visual Studio 2022 -configuration: Release -init: -- pwsh: >- - if ($env:APPVEYOR_REPO_TAG -eq "true") - { - $newVersion = "$($env:APPVEYOR_REPO_TAG_NAME.TrimStart("v"))" - Add-AppveyorMessage -Message ("Running release build") - } - else - { - $newVersion = "0.0.$env:APPVEYOR_BUILD_NUMBER+dev-$($env:APPVEYOR_REPO_COMMIT.Substring(0, 7))" - } - Add-AppveyorMessage -Message ("Change build version to " + $newVersion) - - Update-AppveyorBuild -Version $newVersion -dotnet_csproj: - patch: true - file: '**\*.csproj;**\*.props' - version: '{version}' - package_version: '{version}' - assembly_version: '{version}' - file_version: '{version}' - informational_version: '{version}' -before_build: -- ps: >- - nuget restore - - if ($env:APPVEYOR_REPO_TAG_NAME) - { - $releaseNotes = & $([scriptblock]::Create((New-Object Net.WebClient).DownloadString( 'https://raw.githubusercontent.com/nemesissoft/BuildTools/master/GetGithubReleaseNotes.ps1' ))) -tagName "$env:APPVEYOR_REPO_TAG_NAME" -repoName "$env:APPVEYOR_REPO_NAME" - - if($releaseNotes) - { - $shortDesc = $releaseNotes.Item1 - $longDesc = $releaseNotes.Item2 - - Update-AppveyorBuild -Message "$shortDesc" - - & $([scriptblock]::Create((New-Object Net.WebClient).DownloadString( 'https://raw.githubusercontent.com/nemesissoft/BuildTools/master/UpdateReleaseNotes.ps1' ))) -basePath "$env:APPVEYOR_BUILD_FOLDER" -releaseNotes "$longDesc" - } - } -build: - verbosity: normal -artifacts: -- path: '**\*.nupkg' - name: NuGet Packages -- path: '**\*.snupkg' - name: NuGet Symbols Packages -deploy: -- provider: NuGet - api_key: - secure: w42JHKrNEHSu2D8FBykmZLRgYF2D67nXhk3ze/u99YPb4uxMUTWnMHLYVT3y13I0 - on: - branch: master - APPVEYOR_REPO_TAG: true \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..b93fcd8 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "rollForward": "feature", + "version": "8.0.100" + } +} \ No newline at end of file diff --git a/images/review-icon.png b/images/icon.png similarity index 100% rename from images/review-icon.png rename to images/icon.png