diff --git a/.editorconfig b/.editorconfig index e95e12d08e..09547bc855 100644 --- a/.editorconfig +++ b/.editorconfig @@ -265,6 +265,10 @@ dotnet_diagnostic.MA0006.severity = none # Rationale: See https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0009.md dotnet_diagnostic.MA0009.severity = suggestion +# Purpose: Use string.Create instead of FormattableString.Invariant +# Rationale: See https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0111.md +dotnet_diagnostic.MA0111.severity = error + # Purpose: Use an overload of 'System.ArgumentException' with the parameter name # Rationale: We don't want to force this dotnet_diagnostic.MA0015.severity = suggestion diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..c1eb48e7e0 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +The guidelines for this repository are maintained in [agents.md](../agents.md) at the root of the repository. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e97fb5f22f..e14858b7bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,11 +7,16 @@ jobs: name: "Build, Test, Analyze and Publish" runs-on: windows-latest + permissions: + id-token: write + attestations: write + contents: read + env: DOTNET_NOLOGO: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -21,10 +26,10 @@ jobs: dotnet-version: | 6.0.x 8.0.x - 9.0.x + 10.0.x - name: Cache .nuke/temp - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | .nuke/temp @@ -49,9 +54,22 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} file: TestResults/reports/lcov.info + - name: Check for NuGet package existence + id: check_nupkg + if: startsWith(github.ref, 'refs/tags/') + uses: andstor/file-existence-action@v3 + with: + files: "Artifacts/*.nupkg" + + - name: Attest NuGet package + uses: actions/attest@v4 + if: startsWith(github.ref, 'refs/tags/') && steps.check_nupkg.outputs.files_exists == 'true' + with: + subject-path: ./Artifacts/*.nupkg + - name: Upload artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: windows-artifacts path: | @@ -67,7 +85,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -77,14 +95,14 @@ jobs: dotnet-version: | 6.0.x 8.0.x - 9.0.x + 10.0.x - name: Run NUKE run: ./build.sh UnitTests - name: Upload artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ runner.os }}-artifacts path: | @@ -101,7 +119,7 @@ jobs: steps: - name: Download Artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: path: artifacts diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 9e5c7b0b6e..94699ce1c2 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -13,15 +13,15 @@ jobs: name: Qodana Scan runs-on: windows-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.2 + uses: JetBrains/qodana-action@v2026.1 with: upload-result: ${{ github.ref_name == 'main' || github.ref_name == 'develop' }} - args: --baseline,qodana.sarif.json,--ide,QDNET + args: --baseline qodana.sarif.json --linter qodana-dotnet --within-docker false pr-mode: ${{ github.event_name == 'pull_request_target' }} env: QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index da02270af2..3681484b35 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,11 +28,11 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: | - 9.0.x + 10.0.x - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -48,8 +48,8 @@ jobs: - name: Build run: | - dotnet restore FluentAssertions.sln --configfile nuget.config - dotnet build FluentAssertions.sln --configuration CI --no-restore + dotnet restore FluentAssertions.slnx --configfile nuget.config + dotnet build FluentAssertions.slnx --configuration CI --no-restore - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 0000000000..c1eb48e7e0 --- /dev/null +++ b/.junie/guidelines.md @@ -0,0 +1 @@ +The guidelines for this repository are maintained in [agents.md](../agents.md) at the root of the repository. diff --git a/.nuke/parameters.json b/.nuke/parameters.json index fc3a2420e0..9f8039c0ec 100644 --- a/.nuke/parameters.json +++ b/.nuke/parameters.json @@ -1,4 +1,4 @@ { "$schema": "./build.schema.json", - "Solution": "FluentAssertions.sln" + "Solution": "FluentAssertions.slnx" } \ No newline at end of file diff --git a/.packageguard/cache.bin b/.packageguard/cache.bin index 68eba45c8e..509299b601 100644 Binary files a/.packageguard/cache.bin and b/.packageguard/cache.bin differ diff --git a/Build/Build.cs b/Build/Build.cs index a089ea93b1..829ac29256 100644 --- a/Build/Build.cs +++ b/Build/Build.cs @@ -61,7 +61,7 @@ class Build : NukeBuild readonly Solution Solution; [Required] - [GitVersion(Framework = "net9.0", NoCache = true, NoFetch = true)] + [GitVersion(Framework = "net10.0", NoCache = true, NoFetch = true)] readonly GitVersion GitVersion; [Required] @@ -129,7 +129,7 @@ class Build : NukeBuild DotNetBuild(s => s .SetProjectFile(Solution) .SetConfiguration(Configuration) - .When(_ => GenerateBinLog is true, c => c + .When(_ => GenerateBinLog == true, c => c .SetBinaryLog(ArtifactsDirectory / $"{Solution.Core.FluentAssertions.Name}.binlog") ) .EnableNoLogo() @@ -228,7 +228,7 @@ class Build : NukeBuild { ReportGenerator(s => s .SetProcessToolPath(NuGetToolPathResolver.GetPackageExecutable("ReportGenerator", "ReportGenerator.dll", - framework: "net9.0")) + framework: "net10.0")) .SetTargetDirectory(TestResultsDirectory / "reports") .AddReports(TestResultsDirectory / "**/coverage.cobertura.xml") .AddReportTypes( @@ -299,7 +299,7 @@ from project in projects from framework in frameworks select new { project, framework }; - DotNetTest(s => s + DotNetRun(s => s .SetConfiguration(Configuration.Debug) .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") .EnableNoBuild() diff --git a/Build/CompressionExtensions.cs b/Build/CompressionExtensions.cs index 334960480d..89753805b2 100644 --- a/Build/CompressionExtensions.cs +++ b/Build/CompressionExtensions.cs @@ -9,7 +9,7 @@ public static void UnTarXzTo(this AbsolutePath archive, AbsolutePath directory) { using Stream stream = File.OpenRead(archive); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); while (reader.MoveToNextEntry()) { diff --git a/Build/_build.csproj b/Build/_build.csproj index f6891e6651..6d7e45f810 100644 --- a/Build/_build.csproj +++ b/Build/_build.csproj @@ -1,23 +1,24 @@  Exe - net9.0 + net10.0 CS0649;CS0169 ..\ ..\ - 9.0.4 + 10.1.0 1 - - + + + - - + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..79709e3ef5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@agents.md diff --git a/Directory.Build.props b/Directory.Build.props index 79f0d8f4e9..69be3ee979 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 13.0 + 14.0 false true @@ -13,7 +13,7 @@ true - 9.0 + 10.0 All true @@ -30,11 +30,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/FluentAssertions.sln b/FluentAssertions.sln deleted file mode 100644 index 3766cd7232..0000000000 --- a/FluentAssertions.sln +++ /dev/null @@ -1,233 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32505.426 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E5A0B454-22D4-4694-99FF-D6A8B7DE7DA3}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - Tests\Default.testsettings = Tests\Default.testsettings - Directory.Build.props = Directory.Build.props - nuget.config = nuget.config - README.md = README.md - docs\_pages\releases.md = docs\_pages\releases.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "Build\_build.csproj", "{364DD16C-D759-49DC-A04A-64D40205295B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{963262D0-9FD5-4741-8C0E-E2F34F110EF3}" - ProjectSection(SolutionItems) = preProject - Tests\.editorconfig = Tests\.editorconfig - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyA", "Tests\AssemblyA\AssemblyA.csproj", "{7144BD9D-2A5F-45B6-AC5B-E35578D03350}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyB", "Tests\AssemblyB\AssemblyB.csproj", "{D9CCCAF6-FF9E-4688-9D96-645E27616AC4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestFrameworks", "TestFrameworks", "{4D8FA213-8724-4C43-B68A-F018148D238C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnit2.Specs", "Tests\TestFrameworks\XUnit2.Specs\XUnit2.Specs.csproj", "{F50C8F77-3778-4432-84EC-B9B2B502E708}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{31891850-3EDC-480A-9B6C-F60540E9C90F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTestV2.Specs", "Tests\TestFrameworks\MSTestV2.Specs\MSTestV2.Specs.csproj", "{A4E37052-5581-4E70-A9C3-FF8364B2F332}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions", "Src\FluentAssertions\FluentAssertions.csproj", "{34E3713D-C02F-4868-BBE7-47DAD2C7F03D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit3.Specs", "Tests\TestFrameworks\NUnit3.Specs\NUnit3.Specs.csproj", "{C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit4.Specs", "Tests\TestFrameworks\NUnit4.Specs\NUnit4.Specs.csproj", "{23891E7D-05CE-4893-AC38-A95CC9D3603E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSpec.Specs", "Tests\TestFrameworks\MSpec.Specs\MSpec.Specs.csproj", "{4F210C41-7E8E-424A-B956-FC1AA47663C9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Tests\Benchmarks\Benchmarks.csproj", "{FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Approval.Tests", "Tests\Approval.Tests\Approval.Tests.csproj", "{F5115158-A038-4D14-A04E-46E7863E40B9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Specs", "Tests\FluentAssertions.Specs\FluentAssertions.Specs.csproj", "{6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UWP.Specs", "Tests\UWP.Specs\UWP.Specs.csproj", "{08087654-2C32-4818-95E4-45362373441D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Equivalency.Specs", "Tests\FluentAssertions.Equivalency.Specs\FluentAssertions.Equivalency.Specs.csproj", "{A946043D-D3F8-46A4-B485-A88412C417FE}" -EndProject -Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VB.Specs", "Tests\VB.Specs\VB.Specs.vbproj", "{0C0211B6-D185-4518-A15A-38AC092EDC50}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Specs", "Tests\FSharp.Specs\FSharp.Specs.fsproj", "{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleExtensions", "Tests\ExampleExtensions\ExampleExtensions.csproj", "{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Extensibility.Specs", "Tests\FluentAssertions.Extensibility.Specs\FluentAssertions.Extensibility.Specs.csproj", "{450FC408-A4E2-4483-B064-2007024D6CF1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Specs", "Tests\TestFrameworks\TUnit.Specs\TUnit.Specs.csproj", "{6540564E-9B6E-4E1E-8881-6F0DD0F35576}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnit3.Specs", "Tests\TestFrameworks\XUnit3.Specs\XUnit3.Specs.csproj", "{AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnit3Core.Specs", "Tests\TestFrameworks\XUnit3Core.Specs\XUnit3Core.Specs.csproj", "{77BB9496-169D-43DA-BCED-7DB3ACD9179A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSTestV4.Specs", "Tests\TestFrameworks\MSTestV4.Specs\MSTestV4.Specs.csproj", "{06D5201F-65F6-AA25-8592-A78668E25B66}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - CI|Any CPU = CI|Any CPU - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {364DD16C-D759-49DC-A04A-64D40205295B}.CI|Any CPU.ActiveCfg = Release|Any CPU - {364DD16C-D759-49DC-A04A-64D40205295B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {364DD16C-D759-49DC-A04A-64D40205295B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7144BD9D-2A5F-45B6-AC5B-E35578D03350}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {7144BD9D-2A5F-45B6-AC5B-E35578D03350}.CI|Any CPU.Build.0 = Debug|Any CPU - {7144BD9D-2A5F-45B6-AC5B-E35578D03350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7144BD9D-2A5F-45B6-AC5B-E35578D03350}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7144BD9D-2A5F-45B6-AC5B-E35578D03350}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {7144BD9D-2A5F-45B6-AC5B-E35578D03350}.Release|Any CPU.Build.0 = Debug|Any CPU - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4}.CI|Any CPU.Build.0 = Debug|Any CPU - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4}.Release|Any CPU.Build.0 = Debug|Any CPU - {F50C8F77-3778-4432-84EC-B9B2B502E708}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {F50C8F77-3778-4432-84EC-B9B2B502E708}.CI|Any CPU.Build.0 = Debug|Any CPU - {F50C8F77-3778-4432-84EC-B9B2B502E708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F50C8F77-3778-4432-84EC-B9B2B502E708}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F50C8F77-3778-4432-84EC-B9B2B502E708}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {F50C8F77-3778-4432-84EC-B9B2B502E708}.Release|Any CPU.Build.0 = Debug|Any CPU - {A4E37052-5581-4E70-A9C3-FF8364B2F332}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {A4E37052-5581-4E70-A9C3-FF8364B2F332}.CI|Any CPU.Build.0 = Debug|Any CPU - {A4E37052-5581-4E70-A9C3-FF8364B2F332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4E37052-5581-4E70-A9C3-FF8364B2F332}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4E37052-5581-4E70-A9C3-FF8364B2F332}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {A4E37052-5581-4E70-A9C3-FF8364B2F332}.Release|Any CPU.Build.0 = Debug|Any CPU - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D}.CI|Any CPU.ActiveCfg = Release|Any CPU - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D}.CI|Any CPU.Build.0 = Release|Any CPU - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D}.Release|Any CPU.Build.0 = Release|Any CPU - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.CI|Any CPU.Build.0 = Debug|Any CPU - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Release|Any CPU.Build.0 = Debug|Any CPU - {23891E7D-05CE-4893-AC38-A95CC9D3603E}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {23891E7D-05CE-4893-AC38-A95CC9D3603E}.CI|Any CPU.Build.0 = Debug|Any CPU - {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Release|Any CPU.Build.0 = Release|Any CPU - {4F210C41-7E8E-424A-B956-FC1AA47663C9}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {4F210C41-7E8E-424A-B956-FC1AA47663C9}.CI|Any CPU.Build.0 = Debug|Any CPU - {4F210C41-7E8E-424A-B956-FC1AA47663C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F210C41-7E8E-424A-B956-FC1AA47663C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F210C41-7E8E-424A-B956-FC1AA47663C9}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {4F210C41-7E8E-424A-B956-FC1AA47663C9}.Release|Any CPU.Build.0 = Debug|Any CPU - {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}.CI|Any CPU.ActiveCfg = Release|Any CPU - {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}.Release|Any CPU.Build.0 = Release|Any CPU - {F5115158-A038-4D14-A04E-46E7863E40B9}.CI|Any CPU.ActiveCfg = Release|Any CPU - {F5115158-A038-4D14-A04E-46E7863E40B9}.CI|Any CPU.Build.0 = Release|Any CPU - {F5115158-A038-4D14-A04E-46E7863E40B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5115158-A038-4D14-A04E-46E7863E40B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5115158-A038-4D14-A04E-46E7863E40B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5115158-A038-4D14-A04E-46E7863E40B9}.Release|Any CPU.Build.0 = Release|Any CPU - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.CI|Any CPU.Build.0 = Debug|Any CPU - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Release|Any CPU.Build.0 = Release|Any CPU - {08087654-2C32-4818-95E4-45362373441D}.CI|Any CPU.ActiveCfg = Debug|x64 - {08087654-2C32-4818-95E4-45362373441D}.Debug|Any CPU.ActiveCfg = Debug|x64 - {08087654-2C32-4818-95E4-45362373441D}.Release|Any CPU.ActiveCfg = Release|x64 - {A946043D-D3F8-46A4-B485-A88412C417FE}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {A946043D-D3F8-46A4-B485-A88412C417FE}.CI|Any CPU.Build.0 = Debug|Any CPU - {A946043D-D3F8-46A4-B485-A88412C417FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A946043D-D3F8-46A4-B485-A88412C417FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A946043D-D3F8-46A4-B485-A88412C417FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A946043D-D3F8-46A4-B485-A88412C417FE}.Release|Any CPU.Build.0 = Release|Any CPU - {0C0211B6-D185-4518-A15A-38AC092EDC50}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {0C0211B6-D185-4518-A15A-38AC092EDC50}.CI|Any CPU.Build.0 = Debug|Any CPU - {0C0211B6-D185-4518-A15A-38AC092EDC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C0211B6-D185-4518-A15A-38AC092EDC50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C0211B6-D185-4518-A15A-38AC092EDC50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C0211B6-D185-4518-A15A-38AC092EDC50}.Release|Any CPU.Build.0 = Release|Any CPU - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.CI|Any CPU.Build.0 = Debug|Any CPU - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Release|Any CPU.Build.0 = Release|Any CPU - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.CI|Any CPU.Build.0 = Debug|Any CPU - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Release|Any CPU.Build.0 = Release|Any CPU - {450FC408-A4E2-4483-B064-2007024D6CF1}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {450FC408-A4E2-4483-B064-2007024D6CF1}.CI|Any CPU.Build.0 = Debug|Any CPU - {450FC408-A4E2-4483-B064-2007024D6CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {450FC408-A4E2-4483-B064-2007024D6CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {450FC408-A4E2-4483-B064-2007024D6CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {450FC408-A4E2-4483-B064-2007024D6CF1}.Release|Any CPU.Build.0 = Release|Any CPU - {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.CI|Any CPU.Build.0 = Debug|Any CPU - {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Release|Any CPU.Build.0 = Release|Any CPU - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.CI|Any CPU.Build.0 = Debug|Any CPU - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Release|Any CPU.Build.0 = Release|Any CPU - {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.CI|Any CPU.Build.0 = Debug|Any CPU - {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Release|Any CPU.Build.0 = Release|Any CPU - {06D5201F-65F6-AA25-8592-A78668E25B66}.CI|Any CPU.ActiveCfg = Debug|Any CPU - {06D5201F-65F6-AA25-8592-A78668E25B66}.CI|Any CPU.Build.0 = Debug|Any CPU - {06D5201F-65F6-AA25-8592-A78668E25B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06D5201F-65F6-AA25-8592-A78668E25B66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06D5201F-65F6-AA25-8592-A78668E25B66}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06D5201F-65F6-AA25-8592-A78668E25B66}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {7144BD9D-2A5F-45B6-AC5B-E35578D03350} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {D9CCCAF6-FF9E-4688-9D96-645E27616AC4} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {F50C8F77-3778-4432-84EC-B9B2B502E708} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {A4E37052-5581-4E70-A9C3-FF8364B2F332} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D} = {31891850-3EDC-480A-9B6C-F60540E9C90F} - {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {23891E7D-05CE-4893-AC38-A95CC9D3603E} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {4F210C41-7E8E-424A-B956-FC1AA47663C9} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {F5115158-A038-4D14-A04E-46E7863E40B9} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {08087654-2C32-4818-95E4-45362373441D} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {A946043D-D3F8-46A4-B485-A88412C417FE} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {0C0211B6-D185-4518-A15A-38AC092EDC50} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {450FC408-A4E2-4483-B064-2007024D6CF1} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} - {6540564E-9B6E-4E1E-8881-6F0DD0F35576} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {77BB9496-169D-43DA-BCED-7DB3ACD9179A} = {4D8FA213-8724-4C43-B68A-F018148D238C} - {06D5201F-65F6-AA25-8592-A78668E25B66} = {4D8FA213-8724-4C43-B68A-F018148D238C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {75DDA3D8-9D6F-4865-93F4-DDE11DEE8290} - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = Src\FluentAssertions\FluentAssertions.csproj - EndGlobalSection -EndGlobal diff --git a/FluentAssertions.slnx b/FluentAssertions.slnx new file mode 100644 index 0000000000..15ff2cba12 --- /dev/null +++ b/FluentAssertions.slnx @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FluentAssertions.sln.DotSettings b/FluentAssertions.slnx.DotSettings similarity index 100% rename from FluentAssertions.sln.DotSettings rename to FluentAssertions.slnx.DotSettings diff --git a/NodeVersion b/NodeVersion index 3516580bbb..91d5f6ff8e 100644 --- a/NodeVersion +++ b/NodeVersion @@ -1 +1 @@ -20.17.0 +22.18.0 diff --git a/README.md b/README.md index 0adfe90c96..c4200f674f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Expected numbers to contain 4 item(s) because we thought we put four items in th Visit https://www.fluentassertions.com for [background information](https://fluentassertions.com/about/), [usage documentation](https://fluentassertions.com/introduction), an [extensibility guide](https://fluentassertions.com/extensibility/), support information and more [tips & tricks](https://fluentassertions.com/tips/). # Building -Install Visual Studio 2022 17.14+ or JetBrains Rider 2024.3 as well as the Build Tools 2022 (including the Universal Windows Platform build tools). You will also need to have .NET Framework 4.7 SDK and .NET 9.0 SDK installed. Check the [global.json](global.json) for the current minimum required version. +Install Visual Studio 2026 18.0+ or JetBrains Rider 2025.3. You will also need to have .NET Framework 4.7 SDK and .NET 10.0 SDK installed. Check the [global.json](global.json) for the current minimum required version. You can also build, run the unit tests and package the code using the following command-line: diff --git a/Src/FluentAssertions/AggregateExceptionExtractor.cs b/Src/FluentAssertions/AggregateExceptionExtractor.cs index 9f697b2776..1262e57d60 100644 --- a/Src/FluentAssertions/AggregateExceptionExtractor.cs +++ b/Src/FluentAssertions/AggregateExceptionExtractor.cs @@ -6,6 +6,7 @@ namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public class AggregateExceptionExtractor : IExtractExceptions { public IEnumerable OfType(Exception actualException) diff --git a/Src/FluentAssertions/AndConstraint.cs b/Src/FluentAssertions/AndConstraint.cs index 796336bd30..fd46c5ee30 100644 --- a/Src/FluentAssertions/AndConstraint.cs +++ b/Src/FluentAssertions/AndConstraint.cs @@ -2,6 +2,7 @@ namespace FluentAssertions; +[StackTraceHidden] [DebuggerNonUserCode] public class AndConstraint { diff --git a/Src/FluentAssertions/AndWhichConstraint.cs b/Src/FluentAssertions/AndWhichConstraint.cs index 37d6183a47..9b0e83e1ae 100644 --- a/Src/FluentAssertions/AndWhichConstraint.cs +++ b/Src/FluentAssertions/AndWhichConstraint.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using FluentAssertions.Execution; using FluentAssertions.Formatting; @@ -10,6 +11,8 @@ namespace FluentAssertions; /// Provides a property that can be used in chained assertions where the prior assertions returns a /// single object that the assertion continues on. /// +[StackTraceHidden] +[DebuggerNonUserCode] public class AndWhichConstraint : AndConstraint { private readonly AssertionChain assertionChain; @@ -98,7 +101,7 @@ private static TSubject Single(IEnumerable subjects) matchedElements.Select(ele => "\t" + Formatter.ToString(ele))); string message = "More than one object found. FluentAssertions cannot determine which object is meant." - + $" Found objects:{Environment.NewLine}{foundObjects}"; + + $" Found objects:{Environment.NewLine}{foundObjects}"; AssertionEngine.TestFramework.Throw(message); } diff --git a/Src/FluentAssertions/AssertionConfiguration.cs b/Src/FluentAssertions/AssertionConfiguration.cs index 82a2833716..463118084f 100644 --- a/Src/FluentAssertions/AssertionConfiguration.cs +++ b/Src/FluentAssertions/AssertionConfiguration.cs @@ -1,10 +1,11 @@ -using FluentAssertions.Configuration; +using FluentAssertions.Configuration; namespace FluentAssertions; /// /// Provides access to the global configuration and options to customize the behavior of FluentAssertions. /// +[System.Diagnostics.StackTraceHidden] public static class AssertionConfiguration { public static GlobalConfiguration Current => AssertionEngine.Configuration; diff --git a/Src/FluentAssertions/AssertionEngine.cs b/Src/FluentAssertions/AssertionEngine.cs index ef04504101..69d8bd808f 100644 --- a/Src/FluentAssertions/AssertionEngine.cs +++ b/Src/FluentAssertions/AssertionEngine.cs @@ -12,10 +12,10 @@ namespace FluentAssertions; /// /// Represents the run-time configuration of the assertion library. /// +[StackTraceHidden] public static class AssertionEngine { private static readonly object Lockable = new(); - private static ITestFramework testFramework; private static bool isInitialized; static AssertionEngine() @@ -30,24 +30,21 @@ public static ITestFramework TestFramework { get { - if (testFramework is not null) + if (field is not null) { - return testFramework; + return field; } lock (Lockable) { #pragma warning disable CA1508 - if (testFramework is null) + field ??= TestFrameworkFactory.GetFramework(Configuration.TestFramework); #pragma warning restore CA1508 - { - testFramework = TestFrameworkFactory.GetFramework(Configuration.TestFramework); - } } - return testFramework; + return field; } - set => testFramework = value; + set; } /// @@ -63,7 +60,7 @@ public static void ResetToDefaults() { isInitialized = false; Configuration = new GlobalConfiguration(); - testFramework = null; + TestFramework = null; EnsureInitialized(); } @@ -152,3 +149,4 @@ private static bool IsFramework(Assembly assembly) #endif } } + diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index 624adec0b6..35d7a6f5e6 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Xml.Linq; using FluentAssertions.Collections; @@ -161,7 +162,7 @@ public static ExecutionTime ExecutionTime(this Func action) /// /// Returns an object that can be used to assert the - /// current . + /// current . /// [Pure] public static ExecutionTimeAssertions Should(this ExecutionTime executionTime) @@ -210,7 +211,7 @@ public static XAttributeAssertions Should([NotNull] this XAttribute actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -220,7 +221,7 @@ public static StreamAssertions Should([NotNull] this Stream actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -229,6 +230,75 @@ public static BufferedStreamAssertions Should([NotNull] this BufferedStream actu return new BufferedStreamAssertions(actualValue, AssertionChain.GetOrCreate()); } +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + /// Returns a object that can be used to assert the + /// current . + /// + [Pure] + [OverloadResolutionPriority(-1)] + public static SpanAssertions Should(this Span actualValue) + { + return new SpanAssertions(actualValue.ToArray(), AssertionChain.GetOrCreate()); + } + + /// + /// Returns a object that can be used to assert the + /// current . + /// + [Pure] + [OverloadResolutionPriority(-1)] + public static SpanAssertions Should(this ReadOnlySpan actualValue) + { + return new SpanAssertions(actualValue.ToArray(), AssertionChain.GetOrCreate()); + } + + /// + /// Returns a object that can be used to assert the + /// current of strings. + /// + [Pure] + [OverloadResolutionPriority(-1)] + public static StringCollectionAssertions Should(this Span actualValue) + { + return new StringCollectionAssertions(actualValue.ToArray(), AssertionChain.GetOrCreate()); + } + + /// + /// Returns a object that can be used to assert the + /// current of strings. + /// + [Pure] + [OverloadResolutionPriority(-1)] + public static StringCollectionAssertions Should(this ReadOnlySpan actualValue) + { + return new StringCollectionAssertions(actualValue.ToArray(), AssertionChain.GetOrCreate()); + } + + /// + /// Returns a object that can be used to assert the + /// current . + /// + [Pure] + [OverloadResolutionPriority(-1)] + public static GenericCollectionAssertions Should(this Memory actualValue) + { + return new GenericCollectionAssertions(actualValue.ToArray(), AssertionChain.GetOrCreate()); + } + + /// + /// Returns a object that can be used to assert the + /// current . + /// + [Pure] + [OverloadResolutionPriority(-1)] + public static GenericCollectionAssertions Should(this ReadOnlyMemory actualValue) + { + return new GenericCollectionAssertions(actualValue.ToArray(), AssertionChain.GetOrCreate()); + } + +#endif + /// /// Forces enumerating a collection. Should be used to assert that a method that uses the /// keyword throws a particular exception. @@ -281,6 +351,7 @@ private static void ForceEnumeration(T subject, Func enumerab /// current . /// [Pure] + [OverloadResolutionPriority(-2)] public static ObjectAssertions Should([NotNull] this object actualValue) { return new ObjectAssertions(actualValue, AssertionChain.GetOrCreate()); @@ -289,7 +360,7 @@ public static ObjectAssertions Should([NotNull] this object actualValue) #pragma warning disable AV1564 // Avoid signatures that take a bool parameter /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -299,7 +370,7 @@ public static BooleanAssertions Should(this bool actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -311,7 +382,7 @@ public static NullableBooleanAssertions Should([NotNull] this bool? actualValue) #pragma warning restore AV1564 /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -321,7 +392,7 @@ public static GuidAssertions Should(this Guid actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -331,7 +402,7 @@ public static NullableGuidAssertions Should([NotNull] this Guid? actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -341,7 +412,7 @@ public static GenericCollectionAssertions Should([NotNull] this IEnumerabl } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -351,7 +422,7 @@ public static StringCollectionAssertions Should([NotNull] this IEnumerable - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -363,7 +434,7 @@ public static GenericDictionaryAssertions, TKey, TValu } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current of . /// [Pure] @@ -375,7 +446,7 @@ public static GenericDictionaryAssertions } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -387,7 +458,7 @@ public static GenericDictionaryAssertions Should - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -397,7 +468,7 @@ public static DateTimeAssertions Should(this DateTime actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -407,7 +478,7 @@ public static DateTimeOffsetAssertions Should(this DateTimeOffset actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -417,7 +488,7 @@ public static NullableDateTimeAssertions Should([NotNull] this DateTime? actualV } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -428,7 +499,7 @@ public static NullableDateTimeOffsetAssertions Should([NotNull] this DateTimeOff #if NET6_0_OR_GREATER /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -438,7 +509,7 @@ public static DateOnlyAssertions Should(this DateOnly actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -448,7 +519,7 @@ public static NullableDateOnlyAssertions Should([NotNull] this DateOnly? actualV } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -458,7 +529,7 @@ public static TimeOnlyAssertions Should(this TimeOnly actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -470,7 +541,7 @@ public static NullableTimeOnlyAssertions Should([NotNull] this TimeOnly? actualV #endif /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -480,7 +551,7 @@ public static ComparableTypeAssertions Should([NotNull] this IComparable - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -490,7 +561,7 @@ public static NumericAssertions Should(this int actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -500,7 +571,7 @@ public static NullableNumericAssertions Should([NotNull] this int? actualVa } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -510,7 +581,7 @@ public static NumericAssertions Should(this uint actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -520,7 +591,7 @@ public static NullableNumericAssertions Should([NotNull] this uint? actual } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -530,7 +601,7 @@ public static NumericAssertions Should(this decimal actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -540,7 +611,7 @@ public static NullableNumericAssertions Should([NotNull] this decimal? } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -550,7 +621,7 @@ public static NumericAssertions Should(this byte actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -560,7 +631,7 @@ public static NullableNumericAssertions Should([NotNull] this byte? actual } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -570,7 +641,7 @@ public static NumericAssertions Should(this sbyte actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -580,7 +651,7 @@ public static NullableNumericAssertions Should([NotNull] this sbyte? actu } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -590,7 +661,7 @@ public static NumericAssertions Should(this short actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -600,7 +671,7 @@ public static NullableNumericAssertions Should([NotNull] this short? actu } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -610,7 +681,7 @@ public static NumericAssertions Should(this ushort actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -620,7 +691,7 @@ public static NullableNumericAssertions Should([NotNull] this ushort? ac } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -630,7 +701,7 @@ public static NumericAssertions Should(this long actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -640,7 +711,7 @@ public static NullableNumericAssertions Should([NotNull] this long? actual } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -650,7 +721,7 @@ public static NumericAssertions Should(this ulong actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -660,7 +731,7 @@ public static NullableNumericAssertions Should([NotNull] this ulong? actu } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -670,7 +741,7 @@ public static NumericAssertions Should(this float actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -680,7 +751,7 @@ public static NullableNumericAssertions Should([NotNull] this float? actu } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -690,7 +761,7 @@ public static NumericAssertions Should(this double actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -700,7 +771,7 @@ public static NullableNumericAssertions Should([NotNull] this double? ac } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -710,7 +781,7 @@ public static StringAssertions Should([NotNull] this string actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current . /// [Pure] @@ -720,7 +791,7 @@ public static SimpleTimeSpanAssertions Should(this TimeSpan actualValue) } /// - /// Returns an object that can be used to assert the + /// Returns a object that can be used to assert the /// current nullable . /// [Pure] @@ -731,7 +802,7 @@ public static NullableSimpleTimeSpanAssertions Should([NotNull] this TimeSpan? a /// /// Returns a object that can be used to assert the - /// current . + /// current . /// [Pure] public static TypeAssertions Should([NotNull] this Type subject) @@ -753,7 +824,7 @@ public static TypeSelectorAssertions Should(this TypeSelector typeSelector) } /// - /// Returns a object + /// Returns a object /// that can be used to assert the current . /// /// @@ -813,8 +884,8 @@ public static PropertyInfoSelectorAssertions Should(this PropertyInfoSelector pr } /// - /// Returns a object that can be used to assert the - /// current . + /// Returns an object that can be used to assert the + /// current . /// [Pure] public static ActionAssertions Should([NotNull] this Action action) @@ -824,7 +895,7 @@ public static ActionAssertions Should([NotNull] this Action action) /// /// Returns a object that can be used to assert the - /// current . + /// current . /// [Pure] public static NonGenericAsyncFunctionAssertions Should([NotNull] this Func action) @@ -844,7 +915,7 @@ public static GenericAsyncFunctionAssertions Should([NotNull] this Func /// Returns a object that can be used to assert the - /// current . + /// current . /// [Pure] public static FunctionAssertions Should([NotNull] this Func func) @@ -884,7 +955,7 @@ public static IMonitor Monitor(this T eventSource) /// is . public static IMonitor Monitor(this T eventSource, Action configureOptions) { - Guard.ThrowIfArgumentIsNull(configureOptions, nameof(configureOptions)); + Guard.ThrowIfArgumentIsNull(configureOptions); var options = new EventMonitorOptions(); configureOptions(options); diff --git a/Src/FluentAssertions/AsyncAssertionsExtensions.cs b/Src/FluentAssertions/AsyncAssertionsExtensions.cs index 4981a66dd0..fa5fb7d8ed 100644 --- a/Src/FluentAssertions/AsyncAssertionsExtensions.cs +++ b/Src/FluentAssertions/AsyncAssertionsExtensions.cs @@ -21,7 +21,7 @@ public static class AsyncAssertionsExtensions /// /// /// Please note that this assertion cannot identify whether the previous assertion was successful or not. - /// In case it was not successful and it is running within an active + /// In case it was not successful and it is running within an active /// there is no current result to compare with. /// So, this extension will compare with the default value. /// diff --git a/Src/FluentAssertions/AtLeast.cs b/Src/FluentAssertions/AtLeast.cs index 95750816f2..37fd679599 100644 --- a/Src/FluentAssertions/AtLeast.cs +++ b/Src/FluentAssertions/AtLeast.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions; +namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public static class AtLeast { public static OccurrenceConstraint Once() => new AtLeastTimesConstraint(1); diff --git a/Src/FluentAssertions/AtMost.cs b/Src/FluentAssertions/AtMost.cs index e11c892068..5268a80de2 100644 --- a/Src/FluentAssertions/AtMost.cs +++ b/Src/FluentAssertions/AtMost.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions; +namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public static class AtMost { public static OccurrenceConstraint Once() => new AtMostTimesConstraint(1); diff --git a/Src/FluentAssertions/CallerIdentification/IParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/IParsingStrategy.cs index 1076b460ae..8c5beea1d7 100644 --- a/Src/FluentAssertions/CallerIdentification/IParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/IParsingStrategy.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentAssertions.CallerIdentification; diff --git a/Src/FluentAssertions/CallerIdentification/MultiLineCommentParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/MultiLineCommentParsingStrategy.cs index 8630d3d5a8..115da14aef 100644 --- a/Src/FluentAssertions/CallerIdentification/MultiLineCommentParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/MultiLineCommentParsingStrategy.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentAssertions.CallerIdentification; diff --git a/Src/FluentAssertions/CallerIdentification/ParsingState.cs b/Src/FluentAssertions/CallerIdentification/ParsingState.cs index 18ac092068..607fc3b8b0 100644 --- a/Src/FluentAssertions/CallerIdentification/ParsingState.cs +++ b/Src/FluentAssertions/CallerIdentification/ParsingState.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.CallerIdentification; +namespace FluentAssertions.CallerIdentification; internal enum ParsingState { diff --git a/Src/FluentAssertions/CallerIdentification/QuotesParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/QuotesParsingStrategy.cs index 1cb9f41906..8eda4c9aa3 100644 --- a/Src/FluentAssertions/CallerIdentification/QuotesParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/QuotesParsingStrategy.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentAssertions.CallerIdentification; diff --git a/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs index c5480ef6b5..b01d7e3101 100644 --- a/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentAssertions.CallerIdentification; diff --git a/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs index e7a82a85dd..b45af424c4 100644 --- a/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentAssertions.CallerIdentification; diff --git a/Src/FluentAssertions/CallerIdentification/SingleLineCommentParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/SingleLineCommentParsingStrategy.cs index 371facc25f..4dbf5eed8d 100644 --- a/Src/FluentAssertions/CallerIdentification/SingleLineCommentParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/SingleLineCommentParsingStrategy.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentAssertions.CallerIdentification; diff --git a/Src/FluentAssertions/CallerIdentification/StatementParser.cs b/Src/FluentAssertions/CallerIdentification/StatementParser.cs index 2b9497653d..26dbaf0add 100644 --- a/Src/FluentAssertions/CallerIdentification/StatementParser.cs +++ b/Src/FluentAssertions/CallerIdentification/StatementParser.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Src/FluentAssertions/CallerIdentifier.cs b/Src/FluentAssertions/CallerIdentifier.cs index 4948e4ea0f..a13e0af809 100644 --- a/Src/FluentAssertions/CallerIdentifier.cs +++ b/Src/FluentAssertions/CallerIdentifier.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -16,6 +16,7 @@ namespace FluentAssertions; /// Tries to extract the name of the variable or invocation on which the assertion is executed. /// // REFACTOR: Should be internal and treated as an implementation detail of the AssertionScope +[StackTraceHidden] public static class CallerIdentifier { public static Action Logger { get; set; } = _ => { }; @@ -91,6 +92,14 @@ public static string[] DetermineCallerIdentities() return callers; } + /// + /// Represents a specific point in the call stack to facilitate skipping over internal FluentAssertions + /// frames when analyzing stack traces for caller identification. + /// + /// + /// Instances of this class are used to track and adjust starting points for stack trace searches, + /// ensuring that FluentAssertions internal frames are excluded from caller identification processes. + /// private sealed class StackFrameReference : IDisposable { public int SkipStackFrameCount { get; } @@ -125,11 +134,23 @@ public void Dispose() private static readonly AsyncLocal StartStackSearchAfterStackFrame = new(); + /// + /// Temporarily adjusts the stack trace search behavior to focus on the current scope during Fluent Assertions evaluations. + /// + /// An object that reverts the stack trace search behavior upon disposal. internal static IDisposable OverrideStackSearchUsingCurrentScope() { return new StackFrameReference(); } + /// + /// Determines whether there is only one Fluent Assertions scope present in the current call stack. + /// This is used to identify if a nested Fluent Assertions scope exists within the stack trace. + /// + /// + /// A boolean value indicating true if only one Fluent Assertions scope exists on the call stack; + /// otherwise, false. + /// internal static bool OnlyOneFluentAssertionScopeOnCallStack() { var allStackFrames = GetFrames(new StackTrace()); @@ -290,3 +311,4 @@ private static StackFrame[] GetFrames(StackTrace stack) .ToArray(); } } + diff --git a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs index afff6d0f95..f9599df2ee 100644 --- a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -36,7 +37,6 @@ public GenericCollectionAssertions(TCollection actualValue, AssertionChain asser #pragma warning disable CS0659, S1206 // Ignore not overriding Object.GetHashCode() #pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals - [DebuggerNonUserCode] public class GenericCollectionAssertions : ReferenceTypeAssertions where TCollection : IEnumerable @@ -1186,28 +1186,28 @@ public AndWhichConstraint ContainSingle( switch (actualItems.Count) { case 0: // Fail, Collection is empty - { - assertionChain - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:collection} to contain a single item{reason}, but the collection is empty."); + { + assertionChain + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:collection} to contain a single item{reason}, but the collection is empty."); - break; - } + break; + } case 1: // Success Condition - { - match = actualItems.Single(); - break; - } + { + match = actualItems.Single(); + break; + } default: // Fail, Collection contains more than a single item - { - assertionChain - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:collection} to contain a single item{reason}, but found {0}.", Subject); + { + assertionChain + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:collection} to contain a single item{reason}, but found {0}.", Subject); - break; - } + break; + } } } @@ -1304,7 +1304,7 @@ public AndConstraint EndWith(IEnumerable expectation, [StringSyn /// A collection of expected elements. /// /// - /// A equality comparison the is used to determine whether two objects should be treated as equal. + /// An equality comparison the is used to determine whether two objects should be treated as equal. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -1350,6 +1350,7 @@ public AndConstraint EndWith(T element, [StringSyntax("CompositeFor /// . Elements are compared using their method. /// /// A params array with the expected elements. + [EditorBrowsable(EditorBrowsableState.Never)] public AndConstraint Equal(params T[] elements) { AssertSubjectEquality(elements, ObjectExtensions.GetComparer(), string.Empty); @@ -1357,6 +1358,18 @@ public AndConstraint Equal(params T[] elements) return new AndConstraint((TAssertions)this); } + /// + /// Asserts that the current collection contains all the same elements in the same order as the collection identified by + /// . Elements are compared using their method. + /// + /// A params array with the expected elements. + public AndConstraint BeEqualTo(params T[] elements) + { + AssertSubjectEquality(elements, ObjectExtensions.GetComparer(), string.Empty); + + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that two collections contain the same items in the same order, where equality is determined using a /// . @@ -1365,7 +1378,7 @@ public AndConstraint Equal(params T[] elements) /// The collection to compare the subject with. /// /// - /// A equality comparison the is used to determine whether two objects should be treated as equal. + /// An equality comparison the is used to determine whether two objects should be treated as equal. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -1374,6 +1387,7 @@ public AndConstraint Equal(params T[] elements) /// /// Zero or more objects to format using the placeholders in . /// + [EditorBrowsable(EditorBrowsableState.Never)] public AndConstraint Equal( IEnumerable expectation, Func equalityComparison, [StringSyntax("CompositeFormat")] string because = "", @@ -1396,6 +1410,7 @@ public AndConstraint Equal( /// /// Zero or more objects to format using the placeholders in . /// + [EditorBrowsable(EditorBrowsableState.Never)] public AndConstraint Equal(IEnumerable expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { @@ -1404,6 +1419,50 @@ public AndConstraint Equal(IEnumerable expected, [StringSyntax(" return new AndConstraint((TAssertions)this); } + /// + /// Asserts that the current collection contains all the same elements in the same order as the collection identified by + /// . Elements are compared using their method. + /// + /// An with the expected elements. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeEqualTo(IEnumerable expected, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + return Equal(expected, because, becauseArgs); + } + + /// + /// Asserts that two collections contain the same items in the same order, where equality is determined using a + /// . + /// + /// + /// The collection to compare the subject with. + /// + /// + /// An equality comparison that is used to determine whether two objects should be treated as equal. + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeEqualTo( + IEnumerable expectation, Func equalityComparison, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + return Equal(expectation, equalityComparison, because, becauseArgs); + } + /// /// Asserts that the number of items in the collection matches the supplied amount. /// @@ -2738,6 +2797,7 @@ public AndConstraint NotContainNulls( /// Zero or more objects to format using the placeholders in . /// /// is . + [EditorBrowsable(EditorBrowsableState.Never)] public AndConstraint NotEqual(IEnumerable unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { @@ -2761,6 +2821,26 @@ public AndConstraint NotEqual(IEnumerable unexpected, [StringSyn return new AndConstraint((TAssertions)this); } + /// + /// Asserts that the current collection does not contain all the same elements in the same order as the collection + /// identified by . Elements are compared using their . + /// + /// An with the elements that are not expected. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + /// is . + public AndConstraint NotBeEqualTo(IEnumerable unexpected, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + return NotEqual(unexpected, because, becauseArgs); + } + /// /// Asserts that the number of items in the collection does not match the supplied amount. /// @@ -3276,7 +3356,7 @@ public AndConstraint StartWith(IEnumerable expectation, [StringS /// A collection of expected elements. /// /// - /// A equality comparison the is used to determine whether two objects should be treated as equal. + /// An equality comparison the is used to determine whether two objects should be treated as equal. /// /// /// A formatted phrase as is supported by explaining why the assertion diff --git a/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs b/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs index 68f3c66a4a..9607f5fd72 100644 --- a/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs index b5955cbe83..bb4a6433bc 100644 --- a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs +++ b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; diff --git a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolution.cs b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolution.cs index e4b28e29b1..230c312baf 100644 --- a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolution.cs +++ b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolution.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace FluentAssertions.Collections.MaximumMatching; diff --git a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs index 8bf25ee394..e87a2a58a2 100644 --- a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs +++ b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/Src/FluentAssertions/Collections/MaximumMatching/Predicate.cs b/Src/FluentAssertions/Collections/MaximumMatching/Predicate.cs index df5f2bd565..5b478b680a 100644 --- a/Src/FluentAssertions/Collections/MaximumMatching/Predicate.cs +++ b/Src/FluentAssertions/Collections/MaximumMatching/Predicate.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq.Expressions; using FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Collections/SpanAssertions.cs b/Src/FluentAssertions/Collections/SpanAssertions.cs new file mode 100644 index 0000000000..032cd437aa --- /dev/null +++ b/Src/FluentAssertions/Collections/SpanAssertions.cs @@ -0,0 +1,69 @@ +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Execution; + +namespace FluentAssertions.Collections; + +/// +/// Contains a number of methods to assert that a or matches an expected set of values. +/// +/// +/// Note: Assertion methods convert spans to arrays for comparison, which involves heap allocation. +/// This is necessary to integrate with the existing assertion infrastructure. +/// +[DebuggerNonUserCode] +public class SpanAssertions : GenericCollectionAssertions> +{ + /// + /// Initializes a new instance of the class. + /// + /// The values of the span under test. + /// The assertion chain that is used to build fluent continuations. + public SpanAssertions(T[] actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) + { + } + + /// + /// Asserts that the current span is equal to . + /// + /// The expected values to compare against. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + /// + /// An which can be used to chain assertions. + /// + public AndConstraint> BeEqualTo(Span expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return Equal(expected.ToArray(), because, becauseArgs); + } + + /// + /// Asserts that the current span is equal to . + /// + /// The expected values to compare against. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + /// + /// An which can be used to chain assertions. + /// + public AndConstraint> BeEqualTo(ReadOnlySpan expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return Equal(expected.ToArray(), because, becauseArgs); + } +} +#endif diff --git a/Src/FluentAssertions/Collections/SpanAssertionsExtensions.cs b/Src/FluentAssertions/Collections/SpanAssertionsExtensions.cs new file mode 100644 index 0000000000..b44c255c11 --- /dev/null +++ b/Src/FluentAssertions/Collections/SpanAssertionsExtensions.cs @@ -0,0 +1,54 @@ +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Collections; +using FluentAssertions.Common; +using FluentAssertions.Primitives; + +// ReSharper disable once CheckNamespace +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace FluentAssertions; + +/// +/// Provides string-specific assertion extensions for . +/// +[DebuggerNonUserCode] +public static class SpanAssertionsExtensions +{ + /// + /// Asserts that a or is exactly the same as , + /// including the casing and any leading or trailing whitespace. + /// + /// The on which the assertion is executed. + /// The expected string. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + /// + /// An which can be used to chain assertions. + /// + /// is . + public static AndConstraint> Be([NotNull] this SpanAssertions assertions, string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expected); + + var subject = new string(assertions.Subject!); + + var stringEqualityValidator = new StringValidator(assertions.CurrentAssertionChain, + new StringEqualityStrategy(StringComparer.Ordinal, "be"), + because, becauseArgs); + + stringEqualityValidator.Validate(subject, expected); + + return new AndConstraint>(assertions); + } +} + +#endif diff --git a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs index 20fcd05306..94daadec75 100644 --- a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; @@ -54,6 +55,7 @@ public StringCollectionAssertions(TCollection actualValue, AssertionChain assert /// /// An with the expected elements. [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] + [EditorBrowsable(EditorBrowsableState.Never)] public new AndConstraint Equal(params string[] expected) { return base.Equal(expected.AsEnumerable()); @@ -65,6 +67,7 @@ public StringCollectionAssertions(TCollection actualValue, AssertionChain assert /// the element order, use instead. /// /// An with the expected elements. + [EditorBrowsable(EditorBrowsableState.Never)] public AndConstraint Equal(IEnumerable expected) { return base.Equal(expected); @@ -135,7 +138,8 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()).AsCollection(); var context = - new EquivalencyValidationContext(Node.From>(() => CurrentAssertionChain.CallerIdentifier), options) + new EquivalencyValidationContext(Node.From>(() => CurrentAssertionChain.CallerIdentifier), + options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter diff --git a/Src/FluentAssertions/Collections/WhoseValueConstraint.cs b/Src/FluentAssertions/Collections/WhoseValueConstraint.cs index 50516dd040..4c15197ffd 100644 --- a/Src/FluentAssertions/Collections/WhoseValueConstraint.cs +++ b/Src/FluentAssertions/Collections/WhoseValueConstraint.cs @@ -2,6 +2,7 @@ namespace FluentAssertions.Collections; +[System.Diagnostics.StackTraceHidden] public class WhoseValueConstraint : AndConstraint where TCollection : IEnumerable> where TAssertions : GenericDictionaryAssertions diff --git a/Src/FluentAssertions/Common/CSharpAccessModifierExtensions.cs b/Src/FluentAssertions/Common/CSharpAccessModifierExtensions.cs index bdbd5008c8..60ff569047 100644 --- a/Src/FluentAssertions/Common/CSharpAccessModifierExtensions.cs +++ b/Src/FluentAssertions/Common/CSharpAccessModifierExtensions.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Reflection; namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class CSharpAccessModifierExtensions { internal static CSharpAccessModifier GetCSharpAccessModifier(this MethodBase methodBase) @@ -105,3 +106,4 @@ internal static CSharpAccessModifier GetCSharpAccessModifier(this Type type) return CSharpAccessModifier.InvalidForCSharp; } } + diff --git a/Src/FluentAssertions/Common/Clock.cs b/Src/FluentAssertions/Common/Clock.cs index b7bd423253..9cf2e24cf0 100644 --- a/Src/FluentAssertions/Common/Clock.cs +++ b/Src/FluentAssertions/Common/Clock.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -7,6 +7,7 @@ namespace FluentAssertions.Common; /// /// Default implementation for for production use. /// +[System.Diagnostics.StackTraceHidden] internal class Clock : IClock { public void Delay(TimeSpan timeToDelay) => Task.Delay(timeToDelay).GetAwaiter().GetResult(); diff --git a/Src/FluentAssertions/Common/DateTimeExtensions.cs b/Src/FluentAssertions/Common/DateTimeExtensions.cs index 74e00b3611..e48e1e5fe5 100644 --- a/Src/FluentAssertions/Common/DateTimeExtensions.cs +++ b/Src/FluentAssertions/Common/DateTimeExtensions.cs @@ -18,3 +18,4 @@ public static DateTimeOffset ToDateTimeOffset(this DateTime dateTime, TimeSpan o return new DateTimeOffset(DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified), offset); } } + diff --git a/Src/FluentAssertions/Common/EnumerableExtensions.cs b/Src/FluentAssertions/Common/EnumerableExtensions.cs index 8c9c1063f7..b6a9718045 100644 --- a/Src/FluentAssertions/Common/EnumerableExtensions.cs +++ b/Src/FluentAssertions/Common/EnumerableExtensions.cs @@ -4,6 +4,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class EnumerableExtensions { public static ICollection ConvertOrCastToCollection(this IEnumerable source) @@ -55,4 +56,19 @@ public static int IndexOfFirstDifferenceWith(this IEnumerable + /// Enumerates a sequence, returning each element along with its index. + /// + /// The type of the elements in the sequence. + /// The sequence to enumerate. + /// An enumerable of tuples, where each tuple contains the index and the element at that index in the sequence. + public static IEnumerable<(int Index, T Item)> Index(this IEnumerable items) + { + int index = 0; + foreach (var item in items) + { + yield return (index++, item); + } + } } diff --git a/Src/FluentAssertions/Common/ExceptionExtensions.cs b/Src/FluentAssertions/Common/ExceptionExtensions.cs index 1e3bbd3fe5..8479a559ea 100644 --- a/Src/FluentAssertions/Common/ExceptionExtensions.cs +++ b/Src/FluentAssertions/Common/ExceptionExtensions.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Reflection; using System.Runtime.ExceptionServices; namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class ExceptionExtensions { public static ExceptionDispatchInfo Unwrap(this TargetInvocationException exception) @@ -18,3 +19,4 @@ public static ExceptionDispatchInfo Unwrap(this TargetInvocationException except return ExceptionDispatchInfo.Capture(result); } } + diff --git a/Src/FluentAssertions/Common/ExpressionExtensions.cs b/Src/FluentAssertions/Common/ExpressionExtensions.cs index bca2089a11..1bf02dc0fe 100644 --- a/Src/FluentAssertions/Common/ExpressionExtensions.cs +++ b/Src/FluentAssertions/Common/ExpressionExtensions.cs @@ -7,6 +7,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class ExpressionExtensions { /// @@ -56,79 +57,79 @@ public static IEnumerable GetMemberPaths( #pragma warning restore IDE0010 { case ExpressionType.Lambda: - { - node = ((LambdaExpression)node).Body; - break; - } + { + node = ((LambdaExpression)node).Body; + break; + } case ExpressionType.Convert: case ExpressionType.ConvertChecked: - { - var unaryExpression = (UnaryExpression)node; - node = unaryExpression.Operand; - break; - } + { + var unaryExpression = (UnaryExpression)node; + node = unaryExpression.Operand; + break; + } case ExpressionType.MemberAccess: - { - var memberExpression = (MemberExpression)node; - node = memberExpression.Expression; + { + var memberExpression = (MemberExpression)node; + node = memberExpression.Expression; - break; - } + break; + } case ExpressionType.ArrayIndex: - { - var binaryExpression = (BinaryExpression)node; - node = binaryExpression.Left; + { + var binaryExpression = (BinaryExpression)node; + node = binaryExpression.Left; - break; - } + break; + } case ExpressionType.Parameter: - { - node = null; - break; - } + { + node = null; + break; + } case ExpressionType.Call: - { - var methodCallExpression = (MethodCallExpression)node; - - if (methodCallExpression is not { Method.Name: "get_Item", Arguments: [ConstantExpression] }) { - throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); - } + var methodCallExpression = (MethodCallExpression)node; + + if (methodCallExpression is not { Method.Name: "get_Item", Arguments: [ConstantExpression] }) + { + throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); + } - node = methodCallExpression.Object; - break; - } + node = methodCallExpression.Object; + break; + } default: - { - throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); - } + { + throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); + } } } } @@ -242,3 +243,4 @@ public static void ValidateMemberPath( private static string GetUnsupportedExpressionMessage(Expression expression) => $"Expression <{expression}> cannot be used to select a member."; } + diff --git a/Src/FluentAssertions/Common/Guard.cs b/Src/FluentAssertions/Common/Guard.cs index 2ca3fa22f9..b8ff3fd48d 100644 --- a/Src/FluentAssertions/Common/Guard.cs +++ b/Src/FluentAssertions/Common/Guard.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -6,6 +6,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class Guard { public static void ThrowIfArgumentIsNull([ValidatedNotNull][NoEnumeration] T obj, diff --git a/Src/FluentAssertions/Common/IClock.cs b/Src/FluentAssertions/Common/IClock.cs index ad953a8ad3..4e72fa8679 100644 --- a/Src/FluentAssertions/Common/IClock.cs +++ b/Src/FluentAssertions/Common/IClock.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; diff --git a/Src/FluentAssertions/Common/ITimer.cs b/Src/FluentAssertions/Common/ITimer.cs index c1bd9f293f..f210425614 100644 --- a/Src/FluentAssertions/Common/ITimer.cs +++ b/Src/FluentAssertions/Common/ITimer.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace FluentAssertions.Common; diff --git a/Src/FluentAssertions/Common/IntegerExtensions.cs b/Src/FluentAssertions/Common/IntegerExtensions.cs index 3e742bfeba..903a9dd867 100644 --- a/Src/FluentAssertions/Common/IntegerExtensions.cs +++ b/Src/FluentAssertions/Common/IntegerExtensions.cs @@ -1,8 +1,9 @@ -namespace FluentAssertions.Common; +namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class IntegerExtensions { public static string Times(this int count) => count == 1 ? "1 time" : $"{count} times"; - internal static bool IsConsecutiveTo(this int startNumber, int endNumber) => endNumber == (startNumber + 1); + internal static bool IsConsecutiveTo(this int startNumber, int endNumber) => endNumber == startNumber + 1; } diff --git a/Src/FluentAssertions/Common/Iterator.cs b/Src/FluentAssertions/Common/Iterator.cs index efb1dc3371..63626e001f 100644 --- a/Src/FluentAssertions/Common/Iterator.cs +++ b/Src/FluentAssertions/Common/Iterator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; @@ -8,13 +8,13 @@ namespace FluentAssertions.Common; /// A smarter enumerator that can provide information about the relative location (current, first, last) /// of the current item within the collection without unnecessarily iterating the collection. /// +[System.Diagnostics.StackTraceHidden] internal sealed class Iterator : IEnumerator { private const int InitialIndex = -1; private readonly IEnumerable enumerable; private readonly int? maxItems; private IEnumerator enumerator; - private T current; private T next; private bool hasNext; @@ -35,10 +35,10 @@ public void Reset() Index = InitialIndex; enumerator = enumerable.GetEnumerator(); - hasCurrent = false; hasNext = false; hasCompleted = false; - current = default; + Current = default; + hasCurrent = false; next = default; } @@ -59,12 +59,12 @@ public T Current throw new InvalidOperationException($"Please call {nameof(MoveNext)} first"); } - return current; + return field; } private set { - current = value; + field = value; hasCurrent = true; } } @@ -138,3 +138,4 @@ public void Dispose() enumerator.Dispose(); } } + diff --git a/Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs b/Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs index 1b2cbc65ce..fc807be666 100644 --- a/Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs +++ b/Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class KeyValuePairCollectionExtensions { public static IEnumerable GetKeys(this TCollection collection) @@ -81,3 +82,4 @@ public static TValue GetValue(this TCollection collec }; } } + diff --git a/Src/FluentAssertions/Common/MemberPath.cs b/Src/FluentAssertions/Common/MemberPath.cs index 58334bcbca..7121d92260 100644 --- a/Src/FluentAssertions/Common/MemberPath.cs +++ b/Src/FluentAssertions/Common/MemberPath.cs @@ -9,6 +9,7 @@ namespace FluentAssertions.Common; /// Encapsulates a dotted candidate to a (nested) member of a type as well as the /// declaring type of the deepest member. /// +[System.Diagnostics.StackTraceHidden] internal class MemberPath { private readonly string dottedPath; @@ -69,7 +70,11 @@ public bool IsSameAs(MemberPath candidate) return false; } - private bool IsParentOf(MemberPath candidate) + /// + /// Determines whether the current path is the prefix of + /// (i.e., the current path "owns" the candidate), treating numeric indices and wildcards as interchangeable. + /// + public bool IsParentOf(MemberPath candidate) { string[] candidateSegments = candidate.Segments; @@ -136,3 +141,4 @@ public override string ToString() return dottedPath; } } + diff --git a/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs b/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs index 07dbccad00..d8fe45c39c 100644 --- a/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs +++ b/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs @@ -1,4 +1,4 @@ -#if NET6_0_OR_GREATER || NETSTANDARD2_1 +#if NET6_0_OR_GREATER || NETSTANDARD2_1 using System; #endif using System.Collections.Generic; @@ -11,6 +11,7 @@ namespace FluentAssertions.Common; /// Sets the equal with any numeric index qualifier. /// All other comparisons are default string equality. /// +[System.Diagnostics.StackTraceHidden] internal class MemberPathSegmentEqualityComparer : IEqualityComparer { private const string AnyIndexQualifier = "*"; @@ -49,3 +50,4 @@ public int GetHashCode(string obj) #endif } } + diff --git a/Src/FluentAssertions/Common/MethodInfoExtensions.cs b/Src/FluentAssertions/Common/MethodInfoExtensions.cs index 98637454ed..2d1348be8a 100644 --- a/Src/FluentAssertions/Common/MethodInfoExtensions.cs +++ b/Src/FluentAssertions/Common/MethodInfoExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -7,6 +7,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class MethodInfoExtensions { /// @@ -66,3 +67,4 @@ private static (bool Success, MethodImplAttribute Attribute) RecreateMethodImplA return (false, null); } } + diff --git a/Src/FluentAssertions/Common/ObjectExtensions.cs b/Src/FluentAssertions/Common/ObjectExtensions.cs index e1bf0f0032..8db90f98d4 100644 --- a/Src/FluentAssertions/Common/ObjectExtensions.cs +++ b/Src/FluentAssertions/Common/ObjectExtensions.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class ObjectExtensions { public static Func GetComparer() @@ -87,3 +88,4 @@ public static string ToFormattedString(this object source) return Formatter.ToString(source); } } + diff --git a/Src/FluentAssertions/Common/PropertyInfoExtensions.cs b/Src/FluentAssertions/Common/PropertyInfoExtensions.cs index ec2f738dd2..587cea6828 100644 --- a/Src/FluentAssertions/Common/PropertyInfoExtensions.cs +++ b/Src/FluentAssertions/Common/PropertyInfoExtensions.cs @@ -1,7 +1,8 @@ -using System.Reflection; +using System.Reflection; namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class PropertyInfoExtensions { internal static bool IsVirtual(this PropertyInfo property) @@ -22,3 +23,4 @@ internal static bool IsAbstract(this PropertyInfo property) return methodInfo.IsAbstract; } } + diff --git a/Src/FluentAssertions/Common/StartTimer.cs b/Src/FluentAssertions/Common/StartTimer.cs index 63870db3bd..422bcb6ee7 100644 --- a/Src/FluentAssertions/Common/StartTimer.cs +++ b/Src/FluentAssertions/Common/StartTimer.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.Common; +namespace FluentAssertions.Common; /// /// Factory for starting a timer on demand. diff --git a/Src/FluentAssertions/Common/StopwatchTimer.cs b/Src/FluentAssertions/Common/StopwatchTimer.cs index 889a316e5f..dbda4bcac5 100644 --- a/Src/FluentAssertions/Common/StopwatchTimer.cs +++ b/Src/FluentAssertions/Common/StopwatchTimer.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Diagnostics; namespace FluentAssertions.Common; +[StackTraceHidden] internal sealed class StopwatchTimer : ITimer { private readonly Stopwatch stopwatch = Stopwatch.StartNew(); @@ -19,3 +20,4 @@ public void Dispose() } } } + diff --git a/Src/FluentAssertions/Common/StringExtensions.cs b/Src/FluentAssertions/Common/StringExtensions.cs index d24ab5eab2..f8e5c6b503 100644 --- a/Src/FluentAssertions/Common/StringExtensions.cs +++ b/Src/FluentAssertions/Common/StringExtensions.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; +#if NET6_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif using System.Text.RegularExpressions; using FluentAssertions.Formatting; namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class StringExtensions { /// @@ -178,4 +183,16 @@ public static string RenderAsIndentedBlock(this string message) return " \"" + string.Join(Environment.NewLine + " ", wrappedLines) + "\""; } + + /// + /// Simplifies setting the invariant culture when using interpolated strings. + /// +#if NET6_0_OR_GREATER + public static string Invariant(ref DefaultInterpolatedStringHandler formattable) + => string.Create(CultureInfo.InvariantCulture, ref formattable); +#else + public static string Invariant(FormattableString formattable) + => string.Create(CultureInfo.InvariantCulture, formattable); +#endif } + diff --git a/Src/FluentAssertions/Common/TimeOnlyExtensions.cs b/Src/FluentAssertions/Common/TimeOnlyExtensions.cs index e327319193..cee301084c 100644 --- a/Src/FluentAssertions/Common/TimeOnlyExtensions.cs +++ b/Src/FluentAssertions/Common/TimeOnlyExtensions.cs @@ -3,6 +3,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class TimeOnlyExtensions { /// @@ -27,3 +28,4 @@ public static bool IsCloseTo(this TimeOnly subject, TimeOnly other, TimeSpan pre } #endif + diff --git a/Src/FluentAssertions/Common/TypeExtensions.cs b/Src/FluentAssertions/Common/TypeExtensions.cs index 4cd89baa9c..1a2c86354e 100644 --- a/Src/FluentAssertions/Common/TypeExtensions.cs +++ b/Src/FluentAssertions/Common/TypeExtensions.cs @@ -11,6 +11,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class TypeExtensions { private const BindingFlags PublicInstanceMembersFlag = @@ -482,3 +483,4 @@ public static Type NullableOrActualType(this Type type) return type; } } + diff --git a/Src/FluentAssertions/Common/TypeReflector.cs b/Src/FluentAssertions/Common/TypeReflector.cs index d630c2f0a3..33b5fd9879 100644 --- a/Src/FluentAssertions/Common/TypeReflector.cs +++ b/Src/FluentAssertions/Common/TypeReflector.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,6 +6,7 @@ namespace FluentAssertions.Common; +[System.Diagnostics.StackTraceHidden] internal static class TypeReflector { public static IEnumerable GetAllTypesFromAppDomain(Func predicate) @@ -56,3 +57,4 @@ private static IEnumerable GetExportedTypes(Assembly assembly) } } } + diff --git a/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs b/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs index cc938cc6a2..67eb0cfada 100644 --- a/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs +++ b/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs @@ -1,9 +1,9 @@ -using FluentAssertions.Configuration; +using FluentAssertions.Configuration; namespace FluentAssertions.Common; /// -/// Defines the modes in which custom implementations of +/// Defines the modes in which custom implementations of /// are detected as configured through . /// public enum ValueFormatterDetectionMode diff --git a/Src/FluentAssertions/Configuration/GlobalConfiguration.cs b/Src/FluentAssertions/Configuration/GlobalConfiguration.cs index ebe6577e69..5dba8e4ecd 100644 --- a/Src/FluentAssertions/Configuration/GlobalConfiguration.cs +++ b/Src/FluentAssertions/Configuration/GlobalConfiguration.cs @@ -1,9 +1,8 @@ namespace FluentAssertions.Configuration; +[System.Diagnostics.StackTraceHidden] public class GlobalConfiguration { - private TestFramework? testFramework; - /// /// Provides access to the formatting defaults for all assertions. /// @@ -22,10 +21,10 @@ public class GlobalConfiguration /// public TestFramework? TestFramework { - get => testFramework; + get; set { - testFramework = value; + field = value; AssertionEngine.TestFramework = null; } } diff --git a/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs b/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs index 059efa534a..7b5081ea33 100644 --- a/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs +++ b/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs @@ -1,10 +1,12 @@ using System; + using FluentAssertions.Common; using FluentAssertions.Equivalency; using JetBrains.Annotations; namespace FluentAssertions.Configuration; +[System.Diagnostics.StackTraceHidden] public class GlobalEquivalencyOptions { private EquivalencyOptions defaults = new(); @@ -49,3 +51,4 @@ public EquivalencyOptions CloneDefaults() return new EquivalencyOptions(defaults); } } + diff --git a/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs b/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs index 4676e9c890..f15e6ea1ef 100644 --- a/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs +++ b/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs @@ -4,16 +4,15 @@ namespace FluentAssertions.Configuration; +[System.Diagnostics.StackTraceHidden] public class GlobalFormattingOptions : FormattingOptions { - private string valueFormatterAssembly; - public string ValueFormatterAssembly { - get => valueFormatterAssembly; + get; set { - valueFormatterAssembly = value; + field = value; ValueFormatterDetectionMode = ValueFormatterDetectionMode.Specific; } } @@ -34,3 +33,4 @@ public string ValueFormatterAssembly }; } } + diff --git a/Src/FluentAssertions/CustomAssertionAttribute.cs b/Src/FluentAssertions/CustomAssertionAttribute.cs index 87dfa929b1..198b7a96c4 100644 --- a/Src/FluentAssertions/CustomAssertionAttribute.cs +++ b/Src/FluentAssertions/CustomAssertionAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace FluentAssertions; @@ -8,4 +8,6 @@ namespace FluentAssertions; /// [AttributeUsage(AttributeTargets.Method)] #pragma warning disable CA1813 // Avoid unsealed attributes. This type has shipped. +[System.Diagnostics.StackTraceHidden] public class CustomAssertionAttribute : Attribute; + diff --git a/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs b/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs index 9e0cc90410..6b332c1d81 100644 --- a/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs +++ b/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs @@ -7,4 +7,6 @@ namespace FluentAssertions; /// internally, or directly uses AssertionChain. /// [AttributeUsage(AttributeTargets.Assembly)] +[System.Diagnostics.StackTraceHidden] public sealed class CustomAssertionsAssemblyAttribute : Attribute; + diff --git a/Src/FluentAssertions/Disposable.cs b/Src/FluentAssertions/Disposable.cs index ac98c8167a..3cd8ec4337 100644 --- a/Src/FluentAssertions/Disposable.cs +++ b/Src/FluentAssertions/Disposable.cs @@ -1,7 +1,8 @@ -using System; +using System; namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] internal sealed class Disposable : IDisposable { private readonly Action action; @@ -16,3 +17,4 @@ public void Dispose() action(); } } + diff --git a/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs b/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs index d7cfd1e1e5..aff493b87b 100644 --- a/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs +++ b/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs @@ -2,6 +2,7 @@ namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] internal static class AssertionChainExtensions { /// @@ -17,3 +18,4 @@ public static AssertionChain For(this AssertionChain chain, IEquivalencyValidati .BecauseOf(context.Reason); } } + diff --git a/Src/FluentAssertions/Equivalency/Comparands.cs b/Src/FluentAssertions/Equivalency/Comparands.cs index 4e763acf9b..2f483e8476 100644 --- a/Src/FluentAssertions/Equivalency/Comparands.cs +++ b/Src/FluentAssertions/Equivalency/Comparands.cs @@ -1,20 +1,20 @@ -using System; +using System; + +using System.Globalization; using FluentAssertions.Common; -using static System.FormattableString; namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] public class Comparands { - private Type compileTimeType; - public Comparands() { } public Comparands(object subject, object expectation, Type compileTimeType) { - this.compileTimeType = compileTimeType; + CompileTimeType = compileTimeType; Subject = subject; Expectation = expectation; } @@ -36,11 +36,11 @@ public Type CompileTimeType { get { - return compileTimeType != typeof(object) || Expectation is null ? compileTimeType : RuntimeType; + return field != typeof(object) || Expectation is null ? field : RuntimeType; } // SMELL: Do we really need this? Can we replace it by making Comparands generic or take a constructor parameter? - set => compileTimeType = value; + set; } /// @@ -74,6 +74,7 @@ public Type GetExpectedType(IEquivalencyOptions options) public override string ToString() { - return Invariant($"{{Subject={Subject}, Expectation={Expectation}}}"); + return string.Create(CultureInfo.InvariantCulture, $"{{Subject={Subject}, Expectation={Expectation}}}"); } } + diff --git a/Src/FluentAssertions/Equivalency/ConversionSelector.cs b/Src/FluentAssertions/Equivalency/ConversionSelector.cs index c2985fcae1..f0f8248a34 100644 --- a/Src/FluentAssertions/Equivalency/ConversionSelector.cs +++ b/Src/FluentAssertions/Equivalency/ConversionSelector.cs @@ -11,6 +11,7 @@ namespace FluentAssertions.Equivalency; /// /// Collects the members that need to be converted by the . /// +[System.Diagnostics.StackTraceHidden] public class ConversionSelector { private sealed class ConversionSelectorRule @@ -89,7 +90,7 @@ public override string ToString() { if (inclusions.Count == 0 && exclusions.Count == 0) { - return "Without automatic conversion."; + return "Without automatic conversion"; } var descriptionBuilder = new StringBuilder(); @@ -112,3 +113,4 @@ public ConversionSelector Clone() return new ConversionSelector(new List(inclusions), new List(exclusions)); } } + diff --git a/Src/FluentAssertions/Equivalency/Digit.cs b/Src/FluentAssertions/Equivalency/Digit.cs index 123b552896..56a41b9d8e 100644 --- a/Src/FluentAssertions/Equivalency/Digit.cs +++ b/Src/FluentAssertions/Equivalency/Digit.cs @@ -2,6 +2,7 @@ namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] internal class Digit { private readonly int length; @@ -49,3 +50,4 @@ public bool Increment() return success; } } + diff --git a/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs b/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs index 1d1c62864a..57bcadc959 100644 --- a/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs +++ b/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] internal sealed class EqualityStrategyProvider { private readonly List referenceTypes = []; @@ -17,8 +18,6 @@ internal sealed class EqualityStrategyProvider [CanBeNull] private readonly Func defaultStrategy; - private bool? compareRecordsByValue; - public EqualityStrategyProvider() { } @@ -30,10 +29,10 @@ public EqualityStrategyProvider(Func defaultStrategy) public bool? CompareRecordsByValue { - get => compareRecordsByValue; + get; set { - compareRecordsByValue = value; + field = value; typeCache.Clear(); } } @@ -65,9 +64,9 @@ public EqualityStrategy GetEqualityStrategy(Type type) return EqualityStrategy.ForceEquals; } - if ((compareRecordsByValue != null || defaultStrategy is null) && typeKey.IsRecord()) + if ((CompareRecordsByValue != null || defaultStrategy is null) && typeKey.IsRecord()) { - return compareRecordsByValue is true ? EqualityStrategy.ForceEquals : EqualityStrategy.ForceMembers; + return CompareRecordsByValue == true ? EqualityStrategy.ForceEquals : EqualityStrategy.ForceMembers; } if (defaultStrategy is not null) @@ -107,7 +106,7 @@ public override string ToString() { var builder = new StringBuilder(); - if (compareRecordsByValue is true) + if (CompareRecordsByValue == true) { builder.AppendLine("- Compare records by value"); } @@ -129,3 +128,4 @@ public override string ToString() return builder.ToString(); } } + diff --git a/Src/FluentAssertions/Equivalency/EquivalencyOptions.cs b/Src/FluentAssertions/Equivalency/EquivalencyOptions.cs index 039a2195c1..857e94d96d 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyOptions.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyOptions.cs @@ -12,6 +12,7 @@ namespace FluentAssertions.Equivalency; /// /// Represents the run-time type-specific behavior of a structural equivalency assertion. /// +[System.Diagnostics.StackTraceHidden] public class EquivalencyOptions : SelfReferenceEquivalencyOptions> { @@ -94,7 +95,7 @@ public EquivalencyOptions WithoutStrictOrderingFor( } /// - /// Creates a new set of options based on the current instance which acts on a a collection of the . + /// Creates a new set of options based on the current instance which acts on a collection of the . /// public EquivalencyOptions> AsCollection() { @@ -208,3 +209,4 @@ public EquivalencyOptions() PreferringDeclaredMemberTypes(); } } + diff --git a/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs b/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs index f81b3d48c2..edf514bed3 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs @@ -1,5 +1,3 @@ -#region - using System; using System.Collections; using System.Collections.Generic; @@ -7,14 +5,13 @@ using FluentAssertions.Equivalency.Inlining; using FluentAssertions.Equivalency.Steps; -#endregion - namespace FluentAssertions.Equivalency; /// /// Represents a mutable collection of equivalency steps that can be reordered and/or amended with additional /// custom equivalency steps. /// +[System.Diagnostics.StackTraceHidden] public class EquivalencyPlan : IEnumerable { private List steps = GetDefaultSteps(); @@ -166,3 +163,4 @@ private static List GetDefaultSteps() ]; } } + diff --git a/Src/FluentAssertions/Equivalency/EquivalencyStep.cs b/Src/FluentAssertions/Equivalency/EquivalencyStep.cs index 58f689f771..497e7677f3 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyStep.cs @@ -1,8 +1,9 @@ -namespace FluentAssertions.Equivalency; +namespace FluentAssertions.Equivalency; /// /// Convenient implementation of that will only invoke /// +[System.Diagnostics.StackTraceHidden] public abstract class EquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs index d6cee4a0cd..c00e6ae223 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs @@ -1,13 +1,18 @@ +#if !NET6_0_OR_GREATER +using System; +#endif + +using System.Globalization; using FluentAssertions.Equivalency.Execution; using FluentAssertions.Equivalency.Tracing; using FluentAssertions.Execution; -using static System.FormattableString; namespace FluentAssertions.Equivalency; /// /// Provides information on a particular property during an assertion for structural equality of two object graphs. /// +[System.Diagnostics.StackTraceHidden] public class EquivalencyValidationContext : IEquivalencyValidationContext { private Tracer tracer; @@ -27,7 +32,7 @@ public EquivalencyValidationContext(INode root, IEquivalencyOptions options) public IEquivalencyOptions Options { get; } - private CyclicReferenceDetector CyclicReferenceDetector { get; set; } + internal CyclicReferenceDetector CyclicReferenceDetector { get; set; } public IEquivalencyValidationContext AsNestedMember(IMember expectationMember) { @@ -98,6 +103,7 @@ internal void ResetTracing() public override string ToString() { - return Invariant($"{{Path=\"{CurrentNode.Subject.PathAndName}\"}}"); + return string.Create(CultureInfo.InvariantCulture, $"{{Path=\"{CurrentNode.Subject.PathAndName}\"}}"); } } + diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs index 6a796efdec..25f40f194b 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs @@ -7,6 +7,7 @@ namespace FluentAssertions.Equivalency; /// /// Is responsible for validating the equivalency of a subject with another object. /// +[System.Diagnostics.StackTraceHidden] internal class EquivalencyValidator : IValidateChildNodeEquivalency { private const int MaxDepth = 10; diff --git a/Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs b/Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs index 445537aa1e..a5ba14b3ec 100644 --- a/Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs +++ b/Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs @@ -11,6 +11,7 @@ namespace FluentAssertions.Equivalency.Execution; /// /// Ensures that all the rules remove the collection index from the path before processing it further. /// +[System.Diagnostics.StackTraceHidden] internal class CollectionMemberOptionsDecorator : IEquivalencyOptions, IContainTypingRules { private readonly IEquivalencyOptions inner; @@ -91,5 +92,14 @@ public EqualityStrategy GetEqualityStrategy(Type type) /// public bool IgnoreJsonPropertyCasing => inner.IgnoreJsonPropertyCasing; + public bool EnableFullDump => inner.EnableFullDump; + + /// + public bool TreatNullCollectionsAsEmpty => inner.TreatNullCollectionsAsEmpty; + + /// + public bool TreatNullStringsAsEmpty => inner.TreatNullStringsAsEmpty; + public ITraceWriter TraceWriter => inner.TraceWriter; } + diff --git a/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs b/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs index 4ca13ce49d..d8d21327bb 100644 --- a/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs +++ b/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs @@ -7,6 +7,7 @@ namespace FluentAssertions.Equivalency.Execution; /// Keeps track of objects and their location within an object graph so that cyclic references can be detected /// and handled upon. /// +[System.Diagnostics.StackTraceHidden] internal class CyclicReferenceDetector : ICloneable2 { #region Private Definitions @@ -45,3 +46,4 @@ public object Clone() }; } } + diff --git a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs index 345e572a05..0733af802c 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs @@ -2,6 +2,7 @@ namespace FluentAssertions.Equivalency.Execution; +[System.Diagnostics.StackTraceHidden] internal class ObjectInfo : IObjectInfo { public ObjectInfo(Comparands comparands, INode currentNode) @@ -23,3 +24,4 @@ public ObjectInfo(Comparands comparands, INode currentNode) public Type RuntimeType { get; } } + diff --git a/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs index 4d68cb292d..58a782bd1a 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs @@ -1,14 +1,15 @@ using System; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using FluentAssertions.Common; -using static System.FormattableString; namespace FluentAssertions.Equivalency.Execution; /// /// Represents an object tracked by the including it's location within an object graph. /// +[System.Diagnostics.StackTraceHidden] internal class ObjectReference { private readonly object @object; @@ -76,7 +77,7 @@ public override int GetHashCode() public override string ToString() { - return Invariant($"{{\"{path}\", {@object}}}"); + return string.Create(CultureInfo.InvariantCulture, $"{{\"{path}\", {@object}}}"); } /// @@ -90,3 +91,4 @@ public override string ToString() /// public bool CompareByMembers { get; } } + diff --git a/Src/FluentAssertions/Equivalency/Field.cs b/Src/FluentAssertions/Equivalency/Field.cs index fc3d00a2fd..4fd1af6c72 100644 --- a/Src/FluentAssertions/Equivalency/Field.cs +++ b/Src/FluentAssertions/Equivalency/Field.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Equivalency; /// /// A specialized type of that represents a field of an object in a structural equivalency assertion. /// +[System.Diagnostics.StackTraceHidden] internal class Field : Node, IMember { private readonly FieldInfo fieldInfo; @@ -18,7 +19,7 @@ public Field(FieldInfo fieldInfo, INode parent) this.fieldInfo = fieldInfo; DeclaringType = fieldInfo.DeclaringType; ReflectedType = fieldInfo.ReflectedType; - Subject = new Pathway(parent.Subject.PathAndName, fieldInfo.Name, pathAndName => $"field {parent.GetSubjectId().Combine(pathAndName)}"); + Subject = new Pathway(parent.Subject.PathAndName, fieldInfo.Name, pathAndName => $"field {parent.GetSubjectId().Combine(pathAndName)}"); Expectation = new Pathway(parent.Expectation.PathAndName, fieldInfo.Name, pathAndName => $"field {pathAndName}"); GetSubjectId = parent.GetSubjectId; Type = fieldInfo.FieldType; @@ -33,7 +34,7 @@ public object GetValue(object obj) return fieldInfo.GetValue(obj); } - public Type DeclaringType { get; set; } + public Type DeclaringType { get; } public CSharpAccessModifier GetterAccessibility => fieldInfo.GetCSharpAccessModifier(); @@ -42,3 +43,4 @@ public object GetValue(object obj) public bool IsBrowsable => isBrowsable ??= fieldInfo.GetCustomAttribute() is not { State: EditorBrowsableState.Never }; } + diff --git a/Src/FluentAssertions/Equivalency/IAssertionContext.cs b/Src/FluentAssertions/Equivalency/IAssertionContext.cs index 1c6d662a82..4b993796ad 100644 --- a/Src/FluentAssertions/Equivalency/IAssertionContext.cs +++ b/Src/FluentAssertions/Equivalency/IAssertionContext.cs @@ -18,7 +18,7 @@ public interface IAssertionContext TSubject Subject { get; } /// - /// Gets the value of the expectation object that was matched with the subject using a . + /// Gets the value of the expectation object that was matched with the subject using an . /// TSubject Expectation { get; } diff --git a/Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs b/Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs index 61a77d6efe..03d17a0734 100644 --- a/Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs +++ b/Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs @@ -132,4 +132,19 @@ public interface IEquivalencyOptions /// When set to true, the comparison will ignore the casing of JSON property names when comparing objects to JSON trees. /// bool IgnoreJsonPropertyCasing { get; } + + /// + /// Gets a value indicating whether the full dump of the subject should be included in the failure message. + /// + bool EnableFullDump { get; } + + /// + /// Gets a value indicating whether null collections should be treated as equivalent to empty collections. + /// + bool TreatNullCollectionsAsEmpty { get; } + + /// + /// Gets a value indicating whether null strings should be treated as equivalent to empty strings. + /// + bool TreatNullStringsAsEmpty { get; } } diff --git a/Src/FluentAssertions/Equivalency/INode.cs b/Src/FluentAssertions/Equivalency/INode.cs index 102760f49f..9434efdee3 100644 --- a/Src/FluentAssertions/Equivalency/INode.cs +++ b/Src/FluentAssertions/Equivalency/INode.cs @@ -30,7 +30,7 @@ public interface INode /// /// Gets the path from the root of the subject upto and including the current node. /// - Pathway Subject { get; internal set; } + Pathway Subject { get; } /// /// Gets the path from the root of the expectation upto and including the current node. diff --git a/Src/FluentAssertions/Equivalency/IObjectInfo.cs b/Src/FluentAssertions/Equivalency/IObjectInfo.cs index a02a04a1a0..5a10a3da8e 100644 --- a/Src/FluentAssertions/Equivalency/IObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/IObjectInfo.cs @@ -30,7 +30,7 @@ public interface IObjectInfo /// /// Gets the compile-time type of the current object. If the current object is not the root object and the type is not , - /// then it returns the same as the property does. + /// then it returns the same as the property does. /// Type CompileTimeType { get; } diff --git a/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs b/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs index d314e64b53..2eff6916d0 100644 --- a/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs +++ b/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs @@ -9,6 +9,7 @@ namespace FluentAssertions.Equivalency.Inlining; /// of the assertion APIs provided by Fluent Assertions. /// /// The expected type of the subject to which the assertion is applied. +[System.Diagnostics.StackTraceHidden] internal class ActionBasedInlineAssertion(Action assertion) : IInlineEquivalencyAssertion { /// @@ -24,3 +25,4 @@ public void Execute(AssertionChain assertionChain, Comparands comparands) } } } + diff --git a/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs b/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs index eef612dbbd..1f48475dc4 100644 --- a/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs +++ b/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Equivalency.Inlining; /// Represents a condition-based inline equivalency assertion that evaluates a specified condition against a subject during object equivalency checks. /// /// The expected type of the subject being asserted. +[System.Diagnostics.StackTraceHidden] internal class ConditionBasedInlineAssertion(Expression> condition) : IInlineEquivalencyAssertion { /// @@ -22,3 +23,4 @@ public void Execute(AssertionChain assertionChain, Comparands comparands) .FailWith("Expected {context:subject} to meet condition {0}, but it did not.", condition); } } + diff --git a/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs index b4c5b62f97..f9d5fe9ee7 100644 --- a/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs @@ -13,6 +13,7 @@ namespace FluentAssertions.Equivalency.Inlining; /// This step allows users to define custom equivalency behaviors inline during assertion /// execution. /// +[System.Diagnostics.StackTraceHidden] public class InlineEquivalencyStep : IEquivalencyStep { /// @@ -31,3 +32,4 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } } + diff --git a/Src/FluentAssertions/Equivalency/JsonProperty.cs b/Src/FluentAssertions/Equivalency/JsonProperty.cs index c50668f4b0..58b679667d 100644 --- a/Src/FluentAssertions/Equivalency/JsonProperty.cs +++ b/Src/FluentAssertions/Equivalency/JsonProperty.cs @@ -12,6 +12,7 @@ namespace FluentAssertions.Equivalency; /// information about a specific JSON property, its parent object, and its counterpart in an expectation object /// when performing comparisons. /// +[System.Diagnostics.StackTraceHidden] internal class JsonProperty(JsonNode property, JsonObject parent, INode expectationParent) : IMember { // SMELL: A lot of properties are required by the IMember interface, but they are not used. In the future @@ -145,3 +146,4 @@ public static JsonProperty Find(IMember expectedMember, INode parent, object sub } #endif + diff --git a/Src/FluentAssertions/Equivalency/MemberFactory.cs b/Src/FluentAssertions/Equivalency/MemberFactory.cs index 6fed0af560..b34c371c83 100644 --- a/Src/FluentAssertions/Equivalency/MemberFactory.cs +++ b/Src/FluentAssertions/Equivalency/MemberFactory.cs @@ -1,9 +1,11 @@ using System; + using System.Reflection; using FluentAssertions.Common; namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] public static class MemberFactory { public static IMember Create(MemberInfo memberInfo, INode parent) @@ -29,3 +31,4 @@ internal static IMember Find(object target, string memberName, INode parent) return field is not null ? new Field(field, parent) : null; } } + diff --git a/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs b/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs index 2e5417bf16..f234134479 100644 --- a/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs +++ b/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs @@ -6,6 +6,7 @@ namespace FluentAssertions.Equivalency; /// /// Provides contextual information to an . /// +[System.Diagnostics.StackTraceHidden] public class MemberSelectionContext { private readonly Type compileTimeType; @@ -42,3 +43,4 @@ public Type Type } } } + diff --git a/Src/FluentAssertions/Equivalency/MemberVisibility.cs b/Src/FluentAssertions/Equivalency/MemberVisibility.cs index a0eddebf20..f41b857b57 100644 --- a/Src/FluentAssertions/Equivalency/MemberVisibility.cs +++ b/Src/FluentAssertions/Equivalency/MemberVisibility.cs @@ -1,4 +1,4 @@ -using System; +using System; #pragma warning disable CA1714 namespace FluentAssertions.Equivalency; diff --git a/Src/FluentAssertions/Equivalency/MemberVisibilityExtensions.cs b/Src/FluentAssertions/Equivalency/MemberVisibilityExtensions.cs index 01abeeba12..1f14f26269 100644 --- a/Src/FluentAssertions/Equivalency/MemberVisibilityExtensions.cs +++ b/Src/FluentAssertions/Equivalency/MemberVisibilityExtensions.cs @@ -4,6 +4,7 @@ namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] internal static class MemberVisibilityExtensions { private static readonly ConcurrentDictionary Cache = new(); @@ -41,3 +42,4 @@ public static MemberKind ToMemberKind(this MemberVisibility visibility) }); } } + diff --git a/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs index 4962f36860..70d6cb84df 100644 --- a/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using FluentAssertions.Execution; @@ -8,6 +8,7 @@ namespace FluentAssertions.Equivalency; /// Supports recursively comparing two multi-dimensional arrays for equivalency using strict order for the array items /// themselves. /// +[System.Diagnostics.StackTraceHidden] internal class MultiDimensionalArrayEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -105,3 +106,4 @@ private static bool HaveSameRank(object subject, Array expectation, AssertionCha return assertionChain.Succeeded; } } + diff --git a/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs b/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs index 1708c3e7a7..1dc2a26169 100644 --- a/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs +++ b/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq.Expressions; using FluentAssertions.Common; @@ -6,6 +6,7 @@ namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] public class NestedExclusionOptionBuilder { /// @@ -49,3 +50,4 @@ public NestedExclusionOptionBuilder For( return new NestedExclusionOptionBuilder(capturedOptions, currentPathSelectionRule); } } + diff --git a/Src/FluentAssertions/Equivalency/Node.cs b/Src/FluentAssertions/Equivalency/Node.cs index b8b08bc94a..6de5450342 100644 --- a/Src/FluentAssertions/Equivalency/Node.cs +++ b/Src/FluentAssertions/Equivalency/Node.cs @@ -1,41 +1,36 @@ using System; using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using FluentAssertions.Common; namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] internal class Node : INode { private static readonly Regex MatchFirstIndex = new(@"^\[[0-9]+\]$"); - private GetSubjectId subjectIdProvider; - private string cachedSubjectId; - private Pathway subject; public GetSubjectId GetSubjectId { - get => () => cachedSubjectId ??= subjectIdProvider(); - protected init => subjectIdProvider = value; + get => () => cachedSubjectId ??= field(); + protected init => field = value; } - public Type Type { get; protected set; } + public Type Type { get; init; } - public Type ParentType { get; protected set; } + public Type ParentType { get; init; } public Pathway Subject { - get => subject; - set + get; + protected set { - subject = value; - - if (Expectation is null) - { - Expectation = value; - } + field = value; + Expectation ??= value; } } @@ -53,7 +48,7 @@ public bool IsRoot private bool IsFirstIndex => MatchFirstIndex.IsMatch(Subject.PathAndName); - public bool RootIsCollection { get; protected set; } + public bool RootIsCollection { get; init; } public void AdjustForRemappedSubject(IMember subjectMember) { @@ -78,7 +73,7 @@ public static INode From(GetSubjectId getSubjectId) { return new Node { - subjectIdProvider = () => getSubjectId() ?? "root", + GetSubjectId = () => getSubjectId() ?? "root", Subject = new Pathway(string.Empty, string.Empty, _ => getSubjectId()), Type = typeof(T), ParentType = null, @@ -142,6 +137,7 @@ public override bool Equals(object obj) private bool Equals(Node other) => (Type, Subject.Name, Subject.Path) == (other.Type, other.Subject.Name, other.Subject.Path); + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] public override int GetHashCode() { unchecked @@ -156,3 +152,4 @@ public override int GetHashCode() public override string ToString() => Subject.Description; } + diff --git a/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs b/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs index 645bba4ad2..20a1581893 100644 --- a/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs +++ b/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs @@ -7,6 +7,7 @@ namespace FluentAssertions.Equivalency; /// /// Collection of s. /// +[System.Diagnostics.StackTraceHidden] public class OrderingRuleCollection : IEnumerable { private readonly List rules = []; @@ -30,7 +31,7 @@ public OrderingRuleCollection(IEnumerable orderingRules) /// Returns an enumerator that iterates through the collection. /// /// - /// A that can be used to iterate through the collection. + /// An that can be used to iterate through the collection. /// /// 1 public IEnumerator GetEnumerator() @@ -42,7 +43,7 @@ public IEnumerator GetEnumerator() /// Returns an enumerator that iterates through a collection. /// /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() @@ -70,3 +71,4 @@ public bool IsOrderingStrictFor(IObjectInfo objectInfo) return results.Contains(OrderStrictness.Strict) && !results.Contains(OrderStrictness.NotStrict); } } + diff --git a/Src/FluentAssertions/Equivalency/Pathway.cs b/Src/FluentAssertions/Equivalency/Pathway.cs index 8bd3f24689..5eccbe4299 100644 --- a/Src/FluentAssertions/Equivalency/Pathway.cs +++ b/Src/FluentAssertions/Equivalency/Pathway.cs @@ -9,8 +9,6 @@ public record Pathway { public delegate string GetDescription(string pathAndName); - private string path = string.Empty; - private string name = string.Empty; private string pathAndName; private readonly GetDescription getDescription; @@ -38,10 +36,10 @@ public Pathway(Pathway parent, string name, GetDescription getDescription) /// public string Path { - get => path; + get; private init { - path = value; + field = value; pathAndName = null; } } @@ -51,10 +49,10 @@ private init /// public string Name { - get => name; + get => field; private init { - name = value; + field = value; pathAndName = null; } } @@ -62,7 +60,7 @@ private init /// /// Gets the path and name of the field or property separated by dots. /// - public string PathAndName => pathAndName ??= path.Combine(name); + public string PathAndName => pathAndName ??= Path.Combine(Name); /// /// Gets the display representation of this path. diff --git a/Src/FluentAssertions/Equivalency/Property.cs b/Src/FluentAssertions/Equivalency/Property.cs index 33cce0a884..31a2fc3ccf 100644 --- a/Src/FluentAssertions/Equivalency/Property.cs +++ b/Src/FluentAssertions/Equivalency/Property.cs @@ -9,6 +9,7 @@ namespace FluentAssertions.Equivalency; /// A specialized type of that represents a property of an object in a structural equivalency assertion. /// #pragma warning disable CA1716 +[System.Diagnostics.StackTraceHidden] internal class Property : Node, IMember { private readonly PropertyInfo propertyInfo; @@ -24,7 +25,7 @@ public Property(Type reflectedType, PropertyInfo propertyInfo, INode parent) ReflectedType = reflectedType; this.propertyInfo = propertyInfo; DeclaringType = propertyInfo.DeclaringType; - Subject = new Pathway(parent.Subject.PathAndName, propertyInfo.Name, pathAndName => $"property {parent.GetSubjectId().Combine(pathAndName)}"); + Subject = new Pathway(parent.Subject.PathAndName, propertyInfo.Name, pathAndName => $"property {parent.GetSubjectId().Combine(pathAndName)}"); Expectation = new Pathway(parent.Expectation.PathAndName, propertyInfo.Name, pathAndName => $"property {pathAndName}"); Type = propertyInfo.PropertyType; ParentType = propertyInfo.DeclaringType; @@ -45,14 +46,7 @@ public object GetValue(object obj) public CSharpAccessModifier SetterAccessibility => propertyInfo.GetSetMethod(nonPublic: true).GetCSharpAccessModifier(); - public bool IsBrowsable - { - get - { - isBrowsable ??= - propertyInfo.GetCustomAttribute() is not { State: EditorBrowsableState.Never }; - - return isBrowsable.Value; - } - } + public bool IsBrowsable => + isBrowsable ??= propertyInfo.GetCustomAttribute() is not { State: EditorBrowsableState.Never }; } + diff --git a/Src/FluentAssertions/Equivalency/Selection/CollectionMemberSelectionRuleDecorator.cs b/Src/FluentAssertions/Equivalency/Selection/CollectionMemberSelectionRuleDecorator.cs index cfcdb8d5da..8fab431258 100644 --- a/Src/FluentAssertions/Equivalency/Selection/CollectionMemberSelectionRuleDecorator.cs +++ b/Src/FluentAssertions/Equivalency/Selection/CollectionMemberSelectionRuleDecorator.cs @@ -2,15 +2,8 @@ namespace FluentAssertions.Equivalency.Selection; -internal class CollectionMemberSelectionRuleDecorator : IMemberSelectionRule +internal class CollectionMemberSelectionRuleDecorator(IMemberSelectionRule selectionRule) : IPathBasedSelectionRule { - private readonly IMemberSelectionRule selectionRule; - - public CollectionMemberSelectionRuleDecorator(IMemberSelectionRule selectionRule) - { - this.selectionRule = selectionRule; - } - public bool IncludesMembers => selectionRule.IncludesMembers; public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, @@ -19,6 +12,12 @@ public IEnumerable SelectMembers(INode currentNode, IEnumerable /// Selection rule that removes a particular property from the structural comparison. /// -internal class ExcludeMemberByPathSelectionRule : SelectMemberByPathSelectionRule +internal class ExcludeMemberByPathSelectionRule(MemberPath pathToExclude) : SelectMemberByPathSelectionRule { - private MemberPath memberToExclude; - - public ExcludeMemberByPathSelectionRule(MemberPath pathToExclude) - { - memberToExclude = pathToExclude; - } + private MemberPath memberToExclude = pathToExclude; protected override void AddOrRemoveMembersFrom(List selectedMembers, INode parent, string parentPath, MemberSelectionContext context) @@ -27,6 +22,8 @@ public void AppendPath(MemberPath nextPath) memberToExclude = memberToExclude.AsParentCollectionOf(nextPath); } + protected override MemberPath MemberPath => memberToExclude; + public MemberPath CurrentPath => memberToExclude; public override string ToString() diff --git a/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPredicateSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPredicateSelectionRule.cs index cc1fd9cd45..e497250682 100644 --- a/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPredicateSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPredicateSelectionRule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; diff --git a/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByTypeSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByTypeSelectionRule.cs new file mode 100644 index 0000000000..adade7e849 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByTypeSelectionRule.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions.Common; + +namespace FluentAssertions.Equivalency.Selection; + +/// +/// Selection rule that removes members of a particular type from the structural comparison. +/// +internal class ExcludeMemberByTypeSelectionRule : IMemberSelectionRule +{ + private readonly Type targetType; + + public ExcludeMemberByTypeSelectionRule(Type targetType) + { + Guard.ThrowIfArgumentIsNull(targetType); + this.targetType = targetType; + } + + public bool IncludesMembers => false; + + public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, + MemberSelectionContext context) + { + return selectedMembers.Where(p => !ShouldExclude(p.Type)).ToArray(); + } + + private bool ShouldExclude(Type memberType) + { + if (targetType.IsGenericTypeDefinition) + { + // Check if memberType derives from the target open generic type + // e.g., class Derived : OpenGeneric or class Derived : OpenGeneric + Type baseType = memberType; + do + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == targetType) + { + return true; + } + + baseType = baseType.BaseType; + } + while (baseType is not null); + + return false; + } + + return targetType.IsAssignableFrom(memberType); + } + + /// + /// 2 + public override string ToString() + { + if (targetType.IsGenericTypeDefinition) + { + return $"Exclude members whose type derives from/is a closed generic type of {targetType}"; + } + + if (targetType.IsSealed) + { + return $"Exclude members of type {targetType}"; + } + + return $"Exclude members whose type is/derives from {targetType}"; + } +} diff --git a/Src/FluentAssertions/Equivalency/Selection/ExcludeNonBrowsableMembersRule.cs b/Src/FluentAssertions/Equivalency/Selection/ExcludeNonBrowsableMembersRule.cs index 79ffb0df6c..9828300d5b 100644 --- a/Src/FluentAssertions/Equivalency/Selection/ExcludeNonBrowsableMembersRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/ExcludeNonBrowsableMembersRule.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace FluentAssertions.Equivalency.Selection; diff --git a/Src/FluentAssertions/Equivalency/Selection/IPathBasedSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/IPathBasedSelectionRule.cs new file mode 100644 index 0000000000..f7ded83c08 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Selection/IPathBasedSelectionRule.cs @@ -0,0 +1,16 @@ +namespace FluentAssertions.Equivalency.Selection; + +/// +/// Represents a selection rule whose effect is determined by a configured member path. +/// This allows callers to ask whether the rule targets any members within the subtree rooted at +/// a given , even when the rule is wrapped by decorators such as the +/// collection-member options decorator. +/// +internal interface IPathBasedSelectionRule : IMemberSelectionRule +{ + /// + /// Returns true when this rule targets at least one member of + /// or one of its descendants. + /// + bool SelectsMembersOf(INode currentNode); +} diff --git a/Src/FluentAssertions/Equivalency/Selection/IncludeMemberByPathSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/IncludeMemberByPathSelectionRule.cs index 4ed440b5a5..79775410b3 100644 --- a/Src/FluentAssertions/Equivalency/Selection/IncludeMemberByPathSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/IncludeMemberByPathSelectionRule.cs @@ -8,17 +8,12 @@ namespace FluentAssertions.Equivalency.Selection; /// /// Selection rule that includes a particular property in the structural comparison. /// -internal class IncludeMemberByPathSelectionRule : SelectMemberByPathSelectionRule +internal class IncludeMemberByPathSelectionRule(MemberPath pathToInclude) : SelectMemberByPathSelectionRule { - private readonly MemberPath memberToInclude; - - public IncludeMemberByPathSelectionRule(MemberPath pathToInclude) - { - memberToInclude = pathToInclude; - } - public override bool IncludesMembers => true; + protected override MemberPath MemberPath => pathToInclude; + protected override void AddOrRemoveMembersFrom(List selectedMembers, INode parent, string parentPath, MemberSelectionContext context) { @@ -26,7 +21,7 @@ protected override void AddOrRemoveMembersFrom(List selectedMembers, IN { var memberPath = new MemberPath(context.Type, memberInfo.DeclaringType, parentPath.Combine(memberInfo.Name)); - if (memberToInclude.IsSameAs(memberPath) || memberToInclude.IsParentOrChildOf(memberPath)) + if (pathToInclude.IsSameAs(memberPath) || pathToInclude.IsParentOrChildOf(memberPath)) { selectedMembers.Add(MemberFactory.Create(memberInfo, parent)); } @@ -35,6 +30,6 @@ protected override void AddOrRemoveMembersFrom(List selectedMembers, IN public override string ToString() { - return "Include member root." + memberToInclude; + return "Include member root." + pathToInclude; } } diff --git a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs index ac37b31834..618dcd70c5 100644 --- a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs @@ -1,36 +1,81 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using FluentAssertions.Common; namespace FluentAssertions.Equivalency.Selection; -internal abstract class SelectMemberByPathSelectionRule : IMemberSelectionRule +internal abstract class SelectMemberByPathSelectionRule : IPathBasedSelectionRule { + private static readonly Regex LeadingCollectionIndexRegex = new(@"^\[[0-9]+]\.?"); + public virtual bool IncludesMembers => false; + protected abstract MemberPath MemberPath { get; } + public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - var currentPath = RemoveRootIndexQualifier(currentNode.Expectation.PathAndName); var members = selectedMembers.ToList(); - AddOrRemoveMembersFrom(members, currentNode, currentPath, context); + AddOrRemoveMembersFrom(members, currentNode, GetPathRelativeToSelectionRoot(currentNode), context); return members; } + /// + /// Returns true if this rule would select members within the subtree rooted at + /// , meaning the rule path targets any member at or below the current node. + /// + public bool SelectsMembersOf(INode currentNode) + { + if (currentNode.IsRoot) + { + return !MemberPath.ToString().IsNullOrEmpty(); + } + + string currentPath = GetPathRelativeToSelectionRoot(currentNode); + + if (string.IsNullOrEmpty(currentPath)) + { + return !MemberPath.ToString().IsNullOrEmpty(); + } + + // Compare normalized path segments rather than raw strings so collection rules like "Items[].Name" + // still match the concrete node path "Items[0].Name", and so paths built through + // MemberPath.AsParentCollectionOf are interpreted consistently. + return new MemberPath(currentPath).IsParentOf(MemberPath); + } + protected abstract void AddOrRemoveMembersFrom(List selectedMembers, INode parent, string parentPath, MemberSelectionContext context); - private static string RemoveRootIndexQualifier(string path) + /// + /// Returns the path used for member-selection matching at . + /// For items inside a root collection, Fluent Assertions exposes paths such as [0].Name, but + /// selection rules are expressed relative to the collection item type (for example Name). + /// This method removes that root-item index so both sides use the same coordinate system. + /// + private static string GetPathRelativeToSelectionRoot(INode currentNode) { - Match match = new Regex(@"^\[[0-9]+]").Match(path); - - if (match.Success) + if (currentNode.IsRoot) { - path = path.Substring(match.Length); + return string.Empty; } - return path; + string expectationPath = currentNode.Expectation.PathAndName; + return RemoveLeadingCollectionIndex(expectationPath); + } + + /// + /// Removes only the leading root-collection index from , such as turning + /// [0] into an empty path and [0].Name into Name. + /// Nested collection indices are intentionally left in place; they are handled later by + /// comparison, which treats [] and concrete indices as equivalent. + /// + private static string RemoveLeadingCollectionIndex(string path) + { + Match match = LeadingCollectionIndexRegex.Match(path); + return match.Success ? path.Substring(match.Length) : path; } } diff --git a/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs b/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs index 14fea3f009..00f6348f14 100644 --- a/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs +++ b/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs @@ -21,6 +21,7 @@ namespace FluentAssertions.Equivalency; /// /// Represents the run-time behavior of a structural equivalency assertion. /// +[StackTraceHidden] public abstract class SelfReferenceEquivalencyOptions : IEquivalencyOptions, IContainTypingRules where TSelf : SelfReferenceEquivalencyOptions { @@ -92,12 +93,15 @@ protected SelfReferenceEquivalencyOptions(IEquivalencyOptions defaults) includedFields = defaults.IncludedFields; ignoreNonBrowsableOnSubject = defaults.IgnoreNonBrowsableOnSubject; excludeNonBrowsableOnExpectation = defaults.ExcludeNonBrowsableOnExpectation; + EnableFullDump = defaults.EnableFullDump; IgnoreLeadingWhitespace = defaults.IgnoreLeadingWhitespace; IgnoreTrailingWhitespace = defaults.IgnoreTrailingWhitespace; IgnoreCase = defaults.IgnoreCase; IgnoreNewlineStyle = defaults.IgnoreNewlineStyle; IncludeFullStringsInDifference = defaults.IncludeFullStringsInDifference; IgnoreJsonPropertyCasing = defaults.IgnoreJsonPropertyCasing; + TreatNullCollectionsAsEmpty = defaults.TreatNullCollectionsAsEmpty; + TreatNullStringsAsEmpty = defaults.TreatNullStringsAsEmpty; ConversionSelector = defaults.ConversionSelector.Clone(); @@ -211,6 +215,13 @@ EqualityStrategy IEquivalencyOptions.GetEqualityStrategy(Type type) public bool IgnoreJsonPropertyCasing { get; set; } + /// + public bool EnableFullDump { get; private set; } + + public bool TreatNullCollectionsAsEmpty { get; private set; } + + public bool TreatNullStringsAsEmpty { get; private set; } + public ITraceWriter TraceWriter { get; private set; } /// @@ -408,6 +419,58 @@ public TSelf Excluding(Expression> predicate) return (TSelf)this; } + /// + /// Excludes all members whose type is or derives from from the structural equality check. + /// + /// + /// The type of members to exclude. All members whose type is or derives from this type will be excluded. + /// For sealed types, only exact type matches are excluded. For open generic types, use the overload. + /// + /// + /// + /// For non-sealed types, this excludes members of the specified type and all derived types. + /// For example, Excluding<BaseClass>() will exclude members of type BaseClass and DerivedClass. + /// + /// + /// For sealed types (like ), only exact type matches are excluded. + /// + /// + public TSelf Excluding() + { + return Excluding(typeof(TMember)); + } + + /// + /// Excludes all members whose type is or derives from the specified from the structural equality check. + /// + /// + /// The type of members to exclude. All members whose type is or derives from this type will be excluded. + /// For sealed types, only exact type matches are excluded. For open generic types (e.g., typeof(Nullable<>)), + /// all closed generics and types deriving from them will be excluded. + /// + /// is . + /// + /// + /// For non-sealed types, this excludes members of the specified type and all derived types. + /// For example, Excluding(typeof(BaseClass)) will exclude members of type BaseClass and DerivedClass. + /// + /// + /// For sealed types (like ), only exact type matches are excluded. + /// + /// + /// For open generic types, this excludes all closed generics and types deriving from them. For example, + /// Excluding(typeof(Nullable<>)) excludes all nullable value types like int?, double?, etc. + /// Similarly, Excluding(typeof(List<>)) excludes List<int>, List<string>, + /// and any types deriving from those closed generics. + /// + /// + public TSelf Excluding(Type type) + { + Guard.ThrowIfArgumentIsNull(type); + AddSelectionRule(new ExcludeMemberByTypeSelectionRule(type)); + return (TSelf)this; + } + /// /// Includes the specified member in the equality check. /// @@ -757,6 +820,15 @@ public TSelf WithTracing(ITraceWriter writer = null) return (TSelf)this; } + /// + /// Configures the equivalency options to include the full representation of the subject in assertion failure messages. + /// + public TSelf WithFullDump() + { + EnableFullDump = true; + return (TSelf)this; + } + /// /// Instructs the equivalency comparison to try to convert the values of /// matching properties before running any of the other steps. @@ -838,6 +910,26 @@ public TSelf IncludingFullStringsInDifference() return (TSelf)this; } + /// + /// Instructs the comparison to treat null collections as equivalent to empty collections, + /// regardless of whether the subject or the expectation is null. + /// + public TSelf ComparingNullCollectionsAsEmpty() + { + TreatNullCollectionsAsEmpty = true; + return (TSelf)this; + } + + /// + /// Instructs the comparison to treat null strings as equivalent to empty strings, + /// regardless of whether the subject or the expectation is null. + /// + public TSelf ComparingNullStringsAsEmpty() + { + TreatNullStringsAsEmpty = true; + return (TSelf)this; + } + #if NET6_0_OR_GREATER /// /// Tells the comparison to ignore the casing when trying to match a property to a JSON property. @@ -851,7 +943,7 @@ public TSelf IgnoringJsonPropertyCasing() /// /// Returns the comparer for strings, which is either an explicitly specified comparer via - /// or an ordinal + /// or an ordinal /// comparer depending on . /// internal IEqualityComparer GetStringComparerOrDefault() @@ -909,30 +1001,30 @@ public override string ToString() foreach (IMemberSelectionRule rule in selectionRules) { - builder.Append("- ").AppendLine(rule.ToString()); + builder.Append("- ").Append(rule).AppendLine(); } foreach (ITypingRule rule in typingRules) { - builder.Append("- ").AppendLine(rule.ToString()); + builder.Append("- ").Append(rule).AppendLine(); } foreach (IMemberMatchingRule rule in matchingRules) { - builder.Append("- ").AppendLine(rule.ToString()); + builder.Append("- ").Append(rule).AppendLine(); } foreach (IEquivalencyStep step in userEquivalencySteps) { - builder.Append("- ").AppendLine(step.ToString()); + builder.Append("- ").Append(step).AppendLine(); } foreach (IOrderingRule rule in OrderingRules) { - builder.Append("- ").AppendLine(rule.ToString()); + builder.Append("- ").Append(rule).AppendLine(); } - builder.Append("- ").AppendLine(ConversionSelector.ToString()); + builder.Append("- ").Append(ConversionSelector).AppendLine(); return builder.ToString(); } @@ -1014,3 +1106,4 @@ private TSelf AddEquivalencyStep(IEquivalencyStep equivalencyStep) #endregion } + diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs index f8a9c02f33..4af7e61781 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs @@ -2,6 +2,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] internal sealed class AssertionContext : IAssertionContext { private AssertionContext(INode currentNode, TSubject subject, TSubject expectation, @@ -34,3 +35,4 @@ internal static AssertionContext CreateFrom(Comparands comparands, IEq context.Reason.Arguments); } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs index c636079e68..17e5ddda05 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs @@ -6,8 +6,9 @@ namespace FluentAssertions.Equivalency.Steps; /// -/// Represents a collection of assertion results obtained through a . +/// Represents a collection of assertion results obtained through an . /// +[System.Diagnostics.StackTraceHidden] internal class AssertionResultSet { private readonly Dictionary set = []; @@ -68,3 +69,4 @@ private KeyValuePair[] GetBestResultSets() /// public bool ContainsSuccessfulSet() => set.Values.Any(v => v.Length == 0); } + diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs index cb6d2fcc03..31f1b83743 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs @@ -1,4 +1,5 @@ using System; + using System.Linq.Expressions; using FluentAssertions.Common; using FluentAssertions.Equivalency.Execution; @@ -6,6 +7,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class AssertionRuleEquivalencyStep : IEquivalencyStep { private readonly Func predicate; @@ -115,3 +117,4 @@ public override string ToString() return "Invoke Action<" + typeof(TSubject).Name + "> when " + description; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs b/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs index 9d45787682..0cb2d09a4e 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using FluentAssertions.Common; -using static System.FormattableString; namespace FluentAssertions.Equivalency.Steps; @@ -11,6 +10,7 @@ namespace FluentAssertions.Equivalency.Steps; /// /// Whether or not the conversion is attempted depends on the . /// +[System.Diagnostics.StackTraceHidden] public class AutoConversionStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -37,14 +37,15 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon if (TryChangeType(comparands.Subject, expectationType, out object convertedSubject)) { context.Tracer.WriteLine(member => - Invariant($"Converted subject {comparands.Subject} at {member.Subject} to {expectationType}")); + string.Create(CultureInfo.InvariantCulture, $"Converted subject {comparands.Subject} at {member.Subject} to {expectationType}")); comparands.Subject = convertedSubject; } else { context.Tracer.WriteLine(member => - Invariant($"Subject {comparands.Subject} at {member.Subject} could not be converted to {expectationType}")); + string.Create(CultureInfo.InvariantCulture, + $"Subject {comparands.Subject} at {member.Subject} could not be converted to {expectationType}")); } return EquivalencyResult.ContinueWithNext; @@ -85,3 +86,4 @@ public override string ToString() return string.Empty; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs index 6b04dbb728..b694f9a916 100644 --- a/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs @@ -10,6 +10,7 @@ namespace FluentAssertions.Equivalency.Steps; /// Specific equivalency step for handling date and time types where the types are different and the failure message /// should make that clear. /// +[System.Diagnostics.StackTraceHidden] public class DateAndTimeEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -51,3 +52,4 @@ private static bool IsOfDateOrTimeType(Type type) => } #pragma warning restore AV1000 + diff --git a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs index 67db86940f..f53a550a04 100644 --- a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs @@ -1,11 +1,15 @@ +#if !NET6_0_OR_GREATER +using System; +#endif using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using FluentAssertions.Common; using FluentAssertions.Execution; -using static System.FormattableString; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class DictionaryEquivalencyStep : EquivalencyStep { [SuppressMessage("ReSharper", "PossibleNullReferenceException")] @@ -18,21 +22,23 @@ protected override EquivalencyResult OnHandle(Comparands comparands, var assertionChain = AssertionChain.GetOrCreate().For(context); - if (PreconditionsAreMet(expectation, subject, assertionChain) && expectation is not null) + if (PreconditionsAreMet(expectation, subject, assertionChain)) { foreach (object key in expectation.Keys) { if (context.Options.IsRecursive) { context.Tracer.WriteLine(member => - Invariant($"Recursing into dictionary item {key} at {member.Expectation}")); + string.Create(CultureInfo.InvariantCulture, + $"Recursing into dictionary item {key} at {member.Expectation}")); - nestedValidator.AssertEquivalencyOf(new Comparands(subject[key], expectation[key], typeof(object)), context.AsDictionaryItem(key)); + nestedValidator.AssertEquivalencyOf(new Comparands(subject[key], expectation[key], typeof(object)), + context.AsDictionaryItem(key)); } else { context.Tracer.WriteLine(member => - Invariant( + string.Create(CultureInfo.InvariantCulture, $"Comparing dictionary item {key} at {member.Expectation} between subject and expectation")); assertionChain.WithCallerPostfix($"[{key.ToFormattedString()}]").ReuseOnce(); @@ -46,21 +52,21 @@ protected override EquivalencyResult OnHandle(Comparands comparands, private static bool PreconditionsAreMet(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) { - return AssertIsDictionary(subject, assertionChain) - && AssertEitherIsNotNull(expectation, subject, assertionChain) - && AssertSameLength(expectation, subject, assertionChain); + return AssertSubjectIsNotNull(subject, assertionChain) + && AssertExpectationIsNotNull(expectation, subject, assertionChain) + && AssertSameLength(expectation, subject, assertionChain); } - private static bool AssertEitherIsNotNull(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) + private static bool AssertExpectationIsNotNull(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) { assertionChain - .ForCondition((expectation is null && subject is null) || expectation is not null) + .ForCondition(expectation is not null) .FailWith("Expected {context:subject} to be {0}{reason}, but found {1}.", null, subject); return assertionChain.Succeeded; } - private static bool AssertIsDictionary(IDictionary subject, AssertionChain assertionChain) + private static bool AssertSubjectIsNotNull(IDictionary subject, AssertionChain assertionChain) { assertionChain .ForCondition(subject is not null) @@ -72,9 +78,9 @@ private static bool AssertIsDictionary(IDictionary subject, AssertionChain asser private static bool AssertSameLength(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) { assertionChain - .ForCondition(expectation is null || subject.Keys.Count == expectation.Keys.Count) + .ForCondition(subject.Keys.Count == expectation.Keys.Count) .FailWith("Expected {context:subject} to be a dictionary with {0} item(s), but it only contains {1} item(s).", - expectation?.Keys.Count, subject?.Keys.Count); + expectation.Keys.Count, subject.Keys.Count); return assertionChain.Succeeded; } diff --git a/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs b/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs index e0ca76da14..8e253e604b 100644 --- a/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs +++ b/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -10,6 +10,7 @@ namespace FluentAssertions.Equivalency.Steps; /// /// Provides Reflection-backed meta-data information about a type implementing the interface. /// +[System.Diagnostics.StackTraceHidden] internal sealed class DictionaryInterfaceInfo { // ReSharper disable once PossibleNullReferenceException @@ -135,3 +136,4 @@ private static Dictionary ConvertToDictionaryInternal $"IDictionary<{Key}, {Value}>"; } + diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs b/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs index 0cf246e82d..18a0b80d5d 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class EnumEqualityStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -39,21 +40,21 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon switch (context.Options.EnumEquivalencyHandling) { case EnumEquivalencyHandling.ByValue: - { - HandleByValue(assertionChain, comparands, context.Reason); - break; - } + { + HandleByValue(assertionChain, comparands, context.Reason); + break; + } case EnumEquivalencyHandling.ByName: - { - HandleByName(assertionChain, comparands, context.Reason); - break; - } + { + HandleByName(assertionChain, comparands, context.Reason); + break; + } default: - { - throw new InvalidOperationException($"Do not know how to handle {context.Options.EnumEquivalencyHandling}"); - } + { + throw new InvalidOperationException($"Do not know how to handle {context.Options.EnumEquivalencyHandling}"); + } } } @@ -119,3 +120,4 @@ private static string GetDisplayNameForEnumComparison(object o, decimal? v) return o is not null ? Convert.ToDecimal(o, CultureInfo.InvariantCulture) : null; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs index 1b6906ba58..f88ac4078a 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs @@ -1,10 +1,12 @@ using System; + using System.Collections; using System.Linq; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class EnumerableEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -17,7 +19,13 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon var assertionChain = AssertionChain.GetOrCreate().For(context); - if (AssertSubjectIsCollection(assertionChain, comparands.Subject)) + bool treatNullAsEmpty = context.Options.TreatNullCollectionsAsEmpty; + + bool subjectIsUsable = treatNullAsEmpty + ? comparands.Subject is null || AssertSubjectIsCollection(assertionChain, comparands.Subject) + : AssertSubjectIsCollection(assertionChain, comparands.Subject); + + if (subjectIsUsable) { var validator = new EnumerableEquivalencyValidator(assertionChain, valueChildNodes, context) { @@ -25,7 +33,9 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon OrderingRules = context.Options.OrderingRules }; - validator.Execute(ToArray(comparands.Subject), ToArray(comparands.Expectation)); + validator.Execute( + treatNullAsEmpty && comparands.Subject is null ? [] : ToArray(comparands.Subject), + treatNullAsEmpty && comparands.Expectation is null ? [] : ToArray(comparands.Expectation)); } return EquivalencyResult.EquivalencyProven; @@ -77,3 +87,4 @@ private static bool IsIgnorableArrayLikeType(object value) (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ArraySegment<>)); } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs index 11e01ebb00..8e11be3ed3 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs @@ -1,216 +1,160 @@ using System.Collections.Generic; using System.Linq; +using System.Text; using FluentAssertions.Equivalency.Execution; using FluentAssertions.Equivalency.Tracing; using FluentAssertions.Execution; -using static System.FormattableString; +using FluentAssertions.Formatting; +using static FluentAssertions.Common.StringExtensions; namespace FluentAssertions.Equivalency.Steps; /// /// Executes a single equivalency assertion on two collections, optionally recursive and with or without strict ordering. /// -internal class EnumerableEquivalencyValidator +[System.Diagnostics.StackTraceHidden] +internal class EnumerableEquivalencyValidator( + AssertionChain assertionChain, + IValidateChildNodeEquivalency parent, + IEquivalencyValidationContext context) { - private const int FailedItemsFastFailThreshold = 10; + private readonly Tracer tracer = context.Tracer; - #region Private Definitions + public required bool Recursive { get; init; } - private readonly AssertionChain assertionChain; - private readonly IValidateChildNodeEquivalency parent; - private readonly IEquivalencyValidationContext context; + public required OrderingRuleCollection OrderingRules { get; init; } - #endregion - - public EnumerableEquivalencyValidator(AssertionChain assertionChain, IValidateChildNodeEquivalency parent, - IEquivalencyValidationContext context) - { - this.assertionChain = assertionChain; - this.parent = parent; - this.context = context; - Recursive = false; - } - - public bool Recursive { get; init; } - - public OrderingRuleCollection OrderingRules { get; init; } - - public void Execute(object[] subject, T[] expectation) + public void Execute(object[] subjects, T[] expectations) { - if (AssertIsNotNull(expectation, subject) && AssertCollectionsHaveSameCount(subject, expectation)) + if (AssertIsNotNull(expectations, subjects)) { if (Recursive) { - using var _ = context.Tracer.WriteBlock(member => - Invariant($"Structurally comparing {subject} and expectation {expectation} at {member.Expectation}")); + using var _ = tracer.WriteBlock(member => + Invariant($"Structurally comparing {subjects} and expectation {expectations} at {member.Expectation}")); - AssertElementGraphEquivalency(subject, expectation, context.CurrentNode); + ExecuteRecursiveAssertion(subjects, expectations); } else { - using var _ = context.Tracer.WriteBlock(member => + using var _ = tracer.WriteBlock(member => Invariant( - $"Comparing subject {subject} and expectation {expectation} at {member.Expectation} using simple value equality")); + $"Comparing subject {subjects} and expectation {expectations} at {member.Expectation} using simple value equality")); - subject.Should().BeEquivalentTo(expectation); + subjects.Should().BeEquivalentTo(expectations); } } } - private bool AssertIsNotNull(object expectation, object[] subject) + private bool AssertIsNotNull(T[] expectations, object[] subjects) { assertionChain - .ForCondition(expectation is not null) - .FailWith("Expected {context:subject} to be , but found {0}.", [subject]); + .ForCondition(expectations is not null) + .FailWith("Expected {context:subject} to be , but found {0}.", [subjects]); return assertionChain.Succeeded; } - private bool AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) + private void ExecuteRecursiveAssertion(object[] subjects, T[] expectation) { - assertionChain - .AssertEitherCollectionIsNotEmpty(subject, expectation) - .Then - .AssertCollectionHasEnoughItems(subject, expectation) - .Then - .AssertCollectionHasNotTooManyItems(subject, expectation); + List> remainingSubjects = subjects + .Select((item, index) => new IndexedItem(item, index)) + .ToList(); - return assertionChain.Succeeded; - } - - private void AssertElementGraphEquivalency(object[] subjects, T[] expectations, INode currentNode) - { - unmatchedSubjectIndexes = Enumerable.Range(0, subjects.Length).ToList(); + List remainingExpectations = new(expectation); - if (OrderingRules.IsOrderingStrictFor(new ObjectInfo(new Comparands(subjects, expectations, typeof(T[])), currentNode))) + bool isOrderingStrict = IsOrderingStrictFor(subjects, remainingExpectations, context.CurrentNode); + if (isOrderingStrict) { - AssertElementGraphEquivalencyWithStrictOrdering(subjects, expectations); + new StrictlyOrderedEquivalencyStrategy(parent, context) + .FindAndRemoveMatches(remainingSubjects, remainingExpectations); } else { - AssertElementGraphEquivalencyWithLooseOrdering(subjects, expectations); + new LooselyOrderedEquivalencyStrategy(assertionChain, parent, context) + .FindAndRemoveMatches(remainingSubjects, remainingExpectations); } + + ReportRemainingOrMissingItems(remainingSubjects, remainingExpectations, subjects, expectation.Length, + isOrderingStrict ? "in order" : "in any order"); } - private void AssertElementGraphEquivalencyWithStrictOrdering(object[] subjects, T[] expectations) + private bool IsOrderingStrictFor(object[] subjects, List expectations, INode currentNode) { - int failedCount = 0; + return OrderingRules.IsOrderingStrictFor(new ObjectInfo(new Comparands(subjects, expectations, typeof(T[])), + currentNode)); + } + +#pragma warning disable CA1305 - foreach (int index in Enumerable.Range(0, expectations.Length)) + private void ReportRemainingOrMissingItems(List> remainingSubjects, List remainingExpectations, + object[] allSubjects, + int expectationLength, string orderingDescription) + { + if (remainingSubjects.Count > 0 || remainingExpectations.Count > 0) { - T expectation = expectations[index]; + StringBuilder message = new(); + string phrase = expectationLength switch + { + 0 => "be an empty collection", + 1 => "contain exactly one item", + _ => $"contain exactly {expectationLength} items {orderingDescription}" + }; - using var _ = context.Tracer.WriteBlock(member => - Invariant( - $"Strictly comparing expectation {expectation} at {member.Expectation} to item with index {index} in {subjects}")); + message.Append( + $"Expected {{context:collection}} to {phrase}{{reason}}, but "); - bool succeeded = StrictlyMatchAgainst(subjects, expectation, index); - if (!succeeded) + if (remainingExpectations.Count > 0) { - failedCount++; - if (failedCount >= FailedItemsFastFailThreshold) + message.Append("it misses {0}"); + if (remainingSubjects.Count > 0) { - context.Tracer.WriteLine(member => - $"Aborting strict order comparison of collections after {FailedItemsFastFailThreshold} items failed at {member.Expectation}"); - - break; + message.Append(" and "); } } - } - } - - private void AssertElementGraphEquivalencyWithLooseOrdering(object[] subjects, T[] expectations) - { - int failedCount = 0; - - foreach (int index in Enumerable.Range(0, expectations.Length)) - { - T expectation = expectations[index]; - - using var _ = context.Tracer.WriteBlock(member => - Invariant( - $"Finding the best match of {expectation} within all items in {subjects} at {member.Expectation}[{index}]")); - bool succeeded = LooselyMatchAgainst(subjects, expectation, index); - - if (!succeeded) + if (remainingSubjects.Count > 0) { - failedCount++; - - if (failedCount >= FailedItemsFastFailThreshold) + if (remainingExpectations.Count == 0) { - context.Tracer.WriteLine(member => - $"Fail failing loose order comparison of collection after {FailedItemsFastFailThreshold} items failed at {member.Expectation}"); - - break; + // Subjects are truly extra (the subject collection has more items than expected). + // Show each extraneous item with its original index to help pinpoint the divergence. + if (remainingSubjects.Count == 1) + { + message.Append($"found one extraneous item at index {remainingSubjects[0].Index}: {{1}}"); + } + else + { + string formattedItems = string.Join(", ", + remainingSubjects.Select(s => + $"{Formatter.ToString(s.Item).EscapePlaceholders()} (at index {s.Index})")); + + message.Append("found extraneous items "); + message.Append(formattedItems); + } + } + else + { + // Both missing and extra subjects exist (same-size collections with pairwise mismatches). + // Fall back to the compact list format without per-item indices. + phrase = remainingSubjects.Count > 1 ? "extraneous items" : "one extraneous item"; + message.Append($"found {phrase} {{1}}"); } } - } - } - - private List unmatchedSubjectIndexes; - - private bool LooselyMatchAgainst(IList subjects, T expectation, int expectationIndex) - { - var results = new AssertionResultSet(); - int index = 0; - - GetTraceMessage getMessage = member => - $"Comparing subject at {member.Subject}[{index}] with the expectation at {member.Expectation}[{expectationIndex}]"; - - int indexToBeRemoved = -1; - for (var metaIndex = 0; metaIndex < unmatchedSubjectIndexes.Count; metaIndex++) - { - index = unmatchedSubjectIndexes[metaIndex]; - object subject = subjects[index]; - - using var _ = context.Tracer.WriteBlock(getMessage); - string[] failures = TryToMatch(subject, expectation, expectationIndex); - - results.AddSet(index, failures); - - if (results.ContainsSuccessfulSet()) + if (context.Options.EnableFullDump) { - context.Tracer.WriteLine(_ => "It's a match"); - indexToBeRemoved = metaIndex; - break; + message.AppendLine(); + message.AppendLine(); + message.AppendLine("Full dump of {context:subject}: {2}"); } - context.Tracer.WriteLine(_ => $"Contained {failures.Length} failures"); - } - - if (indexToBeRemoved != -1) - { - unmatchedSubjectIndexes.RemoveAt(indexToBeRemoved); - } + object subjectsArg = remainingSubjects.Count == 1 + ? remainingSubjects[0].Item + : remainingSubjects.Select(s => s.Item).ToList(); - foreach (string failure in results.GetTheFailuresForTheSetWithTheFewestFailures(expectationIndex)) - { - assertionChain.AddPreFormattedFailure(failure); + assertionChain.FailWith(message.ToString(), remainingExpectations, subjectsArg, allSubjects); } - - return indexToBeRemoved != -1; - } - - private string[] TryToMatch(object subject, T expectation, int expectationIndex) - { - using var scope = new AssertionScope(); - - parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), context.AsCollectionItem(expectationIndex)); - - return scope.Discard(); - } - - private bool StrictlyMatchAgainst(object[] subjects, T expectation, int expectationIndex) - { - using var scope = new AssertionScope(); - object subject = subjects[expectationIndex]; - IEquivalencyValidationContext equivalencyValidationContext = context.AsCollectionItem(expectationIndex); - - parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), equivalencyValidationContext); - - bool failed = scope.HasFailures(); - return !failed; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs deleted file mode 100644 index f3f3d00e21..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -internal static class EnumerableEquivalencyValidatorExtensions -{ - public static Continuation AssertEitherCollectionIsNotEmpty(this AssertionChain assertionChain, - ICollection subject, - ICollection expectation) - { - return assertionChain - .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count, - chain => chain - .ForCondition(subject.Count > 0 || expectation.Count == 0) - .FailWith(", but found an empty collection.") - .Then - .ForCondition(subject.Count == 0 || expectation.Count > 0) - .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s).", - subject, - subject.Count)); - } - - public static Continuation AssertCollectionHasEnoughItems(this AssertionChain assertionChain, ICollection subject, - ICollection expectation) - { - return assertionChain - .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count, - chain => chain - .ForCondition(subject.Count >= expectation.Count) - .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s) less than{Environment.NewLine}{{2}}.", - subject, - expectation.Count - subject.Count, - expectation)); - } - - public static Continuation AssertCollectionHasNotTooManyItems(this AssertionChain assertionChain, - ICollection subject, - ICollection expectation) - { - return assertionChain - .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count, - chain => chain - .ForCondition(subject.Count <= expectation.Count) - .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s) more than{Environment.NewLine}{{2}}.", - subject, - subject.Count - expectation.Count, - expectation)); - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableExtensions.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableExtensions.cs new file mode 100644 index 0000000000..b2faeb9339 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableExtensions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace FluentAssertions.Equivalency.Steps; + +[System.Diagnostics.StackTraceHidden] +internal static class EnumerableExtensions +{ + /// + /// Converts an enumerable collection of into an . + /// + public static IndexedItemCollection ToIndexedList(this IEnumerable> source) => new(source); +} diff --git a/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs index 6bd954d16b..48febbfd49 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs @@ -1,16 +1,19 @@ -using System; using System.Collections.Generic; + +using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class EqualityComparerEquivalencyStep : IEquivalencyStep { private readonly IEqualityComparer comparer; public EqualityComparerEquivalencyStep(IEqualityComparer comparer) { - this.comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + Guard.ThrowIfArgumentIsNull(comparer); + this.comparer = comparer; } public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -48,3 +51,4 @@ public override string ToString() return $"Use {comparer} for objects of type {typeof(T)}"; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/EquivalencyValidationContextExtensions.cs b/Src/FluentAssertions/Equivalency/Steps/EquivalencyValidationContextExtensions.cs index 207044c478..febfd913c4 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EquivalencyValidationContextExtensions.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EquivalencyValidationContextExtensions.cs @@ -1,10 +1,28 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] internal static class EquivalencyValidationContextExtensions { + // Pre-cached string representations of common collection indices to avoid repeated allocations. + private static readonly string[] CachedIndexStrings = InitializeCachedIndexStrings(1024); + + private static string[] InitializeCachedIndexStrings(int count) + { + var result = new string[count]; + for (int i = 0; i < count; i++) + { + result[i] = i.ToString(CultureInfo.InvariantCulture); + } + + return result; + } + public static IEquivalencyValidationContext AsCollectionItem(this IEquivalencyValidationContext context, int index) => - context.AsCollectionItem(index.ToString(CultureInfo.InvariantCulture)); + context.AsCollectionItem(index < CachedIndexStrings.Length + ? CachedIndexStrings[index] + : index.ToString(CultureInfo.InvariantCulture)); } + diff --git a/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs index 1d398fdcd2..f490d467d9 100644 --- a/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -8,6 +9,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class GenericDictionaryEquivalencyStep : IEquivalencyStep { #pragma warning disable SA1110 // Allow opening parenthesis on new line to reduce line length @@ -225,3 +227,4 @@ public KeyDifference(List missingKeys, List additiona public List AdditionalKeys { get; } } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs index f7e4e7297f..83935c1ec4 100644 --- a/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs @@ -1,5 +1,6 @@ using System; using System.Collections; + using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -8,6 +9,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class GenericEnumerableEquivalencyStep : IEquivalencyStep { #pragma warning disable SA1110 // Allow opening parenthesis on new line to reduce line length @@ -20,7 +22,12 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon { Type expectedType = comparands.GetExpectedType(context.Options); - if (comparands.Expectation is null || !IsGenericCollection(expectedType)) + if (!IsGenericCollection(expectedType)) + { + return EquivalencyResult.ContinueWithNext; + } + + if (comparands.Expectation is null && !context.Options.TreatNullCollectionsAsEmpty) { return EquivalencyResult.ContinueWithNext; } @@ -35,7 +42,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon "to use for asserting the equivalency of the collection. ", interfaceTypes.Select(type => "IEnumerable<" + type.GetGenericArguments().Single() + ">"))); - if (AssertSubjectIsCollection(assertionChain, comparands.Subject)) + if (AssertSubjectIsCollection(assertionChain, comparands.Subject, context.Options.TreatNullCollectionsAsEmpty)) { var validator = new EnumerableEquivalencyValidator(assertionChain, valueChildNodes, context) { @@ -45,12 +52,13 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon Type typeOfEnumeration = GetTypeOfEnumeration(expectedType); - var subjectAsArray = EnumerableEquivalencyStep.ToArray(comparands.Subject); + var subjectAsArray = comparands.Subject is null ? [] : EnumerableEquivalencyStep.ToArray(comparands.Subject); + object expectation = comparands.Expectation ?? Array.CreateInstance(typeOfEnumeration, 0); try { HandleMethod.MakeGenericMethod(typeOfEnumeration) - .Invoke(null, [validator, subjectAsArray, comparands.Expectation]); + .Invoke(null, [validator, subjectAsArray, expectation]); } catch (TargetInvocationException e) { @@ -64,8 +72,13 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private static void HandleImpl(EnumerableEquivalencyValidator validator, object[] subject, IEnumerable expectation) => validator.Execute(subject, ToArray(expectation)); - private static bool AssertSubjectIsCollection(AssertionChain assertionChain, object subject) + private static bool AssertSubjectIsCollection(AssertionChain assertionChain, object subject, bool treatNullAsEmpty) { + if (treatNullAsEmpty && subject is null) + { + return true; + } + assertionChain .ForCondition(subject is not null) .FailWith("Expected {context:subject} not to be {0}.", new object[] { null }); @@ -125,3 +138,4 @@ private static T[] ToArray(IEnumerable value) } } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/IndexedItemCollection{T}.cs b/Src/FluentAssertions/Equivalency/Steps/IndexedItemCollection{T}.cs new file mode 100644 index 0000000000..246611af1d --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/IndexedItemCollection{T}.cs @@ -0,0 +1,52 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace FluentAssertions.Equivalency.Steps; + +/// +/// Represents a collection of indexed items of type that implements . +/// +/// The type of elements in the collection. +[System.Diagnostics.StackTraceHidden] +internal class IndexedItemCollection : IReadOnlyList> +{ + private readonly List> items = new(); + + public IndexedItemCollection(IEnumerable source) + { + items = source.Select((item, index) => new IndexedItem(item, index)).ToList(); + } + + public IndexedItemCollection(IEnumerable> source) + { + items.AddRange(source); + } + + /// + public IEnumerator> GetEnumerator() => items.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)items).GetEnumerator(); + + public int Count => items.Count; + + /// + public IndexedItem this[int index] => items[index]; + + public void Remove(IndexedItem item) + { + items.Remove(item); + } + + /// + /// Removes items from the specified list of expectations that do not have a corresponding match + /// in the current collection. + /// + /// The list of expectations to be filtered by removing unmatched items. + public void RemoveMatchedItemFrom(List expectations) + { + var existingItems = new HashSet(items.Select(x => x.Item)); + expectations.RemoveAll(expectation => !existingItems.Contains(expectation)); + } +} diff --git a/Src/FluentAssertions/Equivalency/Steps/IndexedItem{T}.cs b/Src/FluentAssertions/Equivalency/Steps/IndexedItem{T}.cs new file mode 100644 index 0000000000..4f7b46082b --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/IndexedItem{T}.cs @@ -0,0 +1,13 @@ +namespace FluentAssertions.Equivalency.Steps; + +/// +/// Encapsulates an item of type associated with an index. +/// +/// The type of the item being indexed. +[System.Diagnostics.StackTraceHidden] +internal class IndexedItem(T item, int index) +{ + public T Item { get; } = item; + + public int Index { get; } = index; +} diff --git a/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs b/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs index 8ae2289f0f..e7bd20ec19 100644 --- a/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs @@ -6,6 +6,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class JsonConversionStep : IEquivalencyStep { /// @@ -63,3 +64,4 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon } #endif + diff --git a/Src/FluentAssertions/Equivalency/Steps/LooselyOrderedEquivalencyStrategy.cs b/Src/FluentAssertions/Equivalency/Steps/LooselyOrderedEquivalencyStrategy.cs new file mode 100644 index 0000000000..06b37dfbbd --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/LooselyOrderedEquivalencyStrategy.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using FluentAssertions.Common; +using FluentAssertions.Equivalency.Execution; +using FluentAssertions.Equivalency.Tracing; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Steps; + +[System.Diagnostics.StackTraceHidden] +internal class LooselyOrderedEquivalencyStrategy( + AssertionChain assertionChain, + IValidateChildNodeEquivalency parent, + IEquivalencyValidationContext context) +{ + private const int MaximumFailuresToReport = 10; + + private readonly Tracer tracer = context.Tracer; + + // Populated during Phase 1 + 2 using skip-formatting dry-runs (no string allocation). + private Dictionary<(object Subject, object Expectation, int ExpectationIndex), int> countCache = new(); + + // Populated lazily during Phase 3 for the ~n selected pairs with full formatting. + private Dictionary<(object Subject, object Expectation, int ExpectationIndex), string[]> fullFailuresCache = new(); + + public void FindAndRemoveMatches(List> subjects, List expectations) + { + countCache = new(new ReferentialComparer()); + fullFailuresCache = new(new ReferentialComparer()); + + var expectationsWithIndexes = new IndexedItemCollection(expectations); + + // First find the exact matches and remove them from the collection + FindAndRemoveExactMatches(subjects, expectationsWithIndexes); + + // Reorder the remaining expectations based on their similarity to the remaining unmatched subjects. + expectationsWithIndexes = SortExpectationsByMinDistance(subjects, expectationsWithIndexes); + + // If there are still unmatched expectations, find the closest matches + FindAndRemoveClosestMatches(subjects, expectationsWithIndexes); + + // Remove the items that are no longer needed from the original list of expectations + expectationsWithIndexes.RemoveMatchedItemFrom(expectations); + } + + private void FindAndRemoveExactMatches(List> subjects, IndexedItemCollection expectationsWithIndexes) + { + int expectationIndex = 0; + while (expectationsWithIndexes.Count > expectationIndex) + { + IndexedItem expectation = expectationsWithIndexes[expectationIndex]; + if (StrictlyMatchAgainst(subjects, expectation.Item, expectation.Index)) + { + // It's a full match, so we don't have to look at it again + expectationsWithIndexes.Remove(expectation); + } + else + { + // Continue with the next item, but we leave the unmatched items. + expectationIndex++; + } + } + } + + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] + private bool StrictlyMatchAgainst(List> remainingSubjects, TExpectation expectation, int expectationIndex) + { + foreach ((int index, IndexedItem subject) in remainingSubjects.Index()) + { + using var _ = tracer.WriteBlock(member => + $"Comparing subject at {member.Subject}[{index}] with the expectation at {member.Expectation}[{expectationIndex}]"); + + int failures = TryToMatchCount(expectation, subject.Item, expectationIndex); + if (failures == 0) + { + tracer.WriteLine(_ => "It's a match"); + remainingSubjects.RemoveAt(index); + return true; + } + + tracer.WriteLine(_ => $"Contained {failures} failures"); + } + + return false; + } + + /// + /// This is an optimization for loosely-ordered collection comparison. By processing expectations + /// that are most similar to remaining subjects first, the algorithm is more likely + /// to find the correct pairings earlier, leading to better error messages when mismatches occur. + /// + private IndexedItemCollection SortExpectationsByMinDistance(List> remainingSubjects, + IndexedItemCollection expectationsWithIndexes) + { + if (remainingSubjects.Count > 0) + { + return expectationsWithIndexes + .Select(e => new + { + Expectation = e, + MinDistance = remainingSubjects.Min(a => TryToMatchCount(e.Item, a.Item, e.Index)) + }) + .OrderBy(x => x.MinDistance) + .Select(x => x.Expectation) + .ToIndexedList(); + } + else + { + return expectationsWithIndexes; + } + } + + private void FindAndRemoveClosestMatches(List> remainingSubjects, + IndexedItemCollection expectationsWithIndexes) + { + int nrFailures = 0; + if (expectationsWithIndexes.Count > 0 && remainingSubjects.Count > 0) + { + IReadOnlyList<(IndexedItem, IndexedItem, string[])> bestMatches = + FindClosestMismatches(remainingSubjects, expectationsWithIndexes); + + foreach (var (expectation, subject, failures) in bestMatches) + { + foreach (string failure in failures) + { + if (nrFailures < MaximumFailuresToReport) + { + assertionChain.AddPreFormattedFailure(failure); + nrFailures++; + } + } + + remainingSubjects.Remove(subject); + expectationsWithIndexes.Remove(expectation); + } + } + } + + private IReadOnlyList<(IndexedItem Expectation, IndexedItem Actual, string[] Failures)> FindClosestMismatches( + List> remainingSubjects, IndexedItemCollection expectationsWithIndexes) + { + // For small collections, use exact permutation search to find the globally optimal assignment. + // factorial(8) = 40,320 which is well within reason. + const int maxSizeForExactSearch = 8; + + return remainingSubjects.Count <= maxSizeForExactSearch ? + FindClosestMismatchesByPermutation(remainingSubjects, expectationsWithIndexes) : + FindClosestMismatchesByGreedyAssignment(remainingSubjects, expectationsWithIndexes); + } + + /// + /// Finds the best assignment by exhaustively trying all permutations. Only suitable for small collections. + /// Uses failure counts for scoring (no string formatting) and only fetches full failure strings for the + /// winning assignment. + /// + private IReadOnlyList<(IndexedItem Expectation, IndexedItem Actual, string[] Failures)> FindClosestMismatchesByPermutation( + List> remainingSubjects, IndexedItemCollection expectationsWithIndexes) + { + var bestScore = int.MaxValue; + IReadOnlyList> bestAssignment = null; + + foreach (IReadOnlyList> assignment in remainingSubjects.Permute()) + { + int score = 0; + bool tooHigh = false; + + for (int index = 0; index < expectationsWithIndexes.Count && index < assignment.Count; index++) + { + IndexedItem expectationWithIndex = expectationsWithIndexes[index]; + score += TryToMatchCount(expectationWithIndex.Item, assignment[index].Item, expectationWithIndex.Index); + + if (score >= bestScore) + { + tooHigh = true; + break; + } + } + + if (!tooHigh && score < bestScore) + { + bestScore = score; + bestAssignment = assignment; + } + } + + if (bestAssignment is null) + { + return Array.Empty<(IndexedItem, IndexedItem, string[])>(); + } + + // Fetch full failure strings only for the winning assignment. + int pairCount = Math.Min(expectationsWithIndexes.Count, bestAssignment.Count); + var result = new List<(IndexedItem, IndexedItem, string[])>(pairCount); + + for (int index = 0; index < pairCount; index++) + { + IndexedItem expectationWithIndex = expectationsWithIndexes[index]; + string[] failures = TryToMatch(expectationWithIndex.Item, bestAssignment[index].Item, expectationWithIndex.Index); + result.Add((expectationWithIndex, bestAssignment[index], failures)); + } + + return result; + } + + /// + /// Finds a near-optimal assignment using a greedy strategy. Suitable for large collections where the exact + /// permutation search would be prohibitively expensive. All distances are already cached from Phase 1, so + /// this is O(n² log n) rather than O(n! × n). + /// + private IReadOnlyList<(IndexedItem Expectation, IndexedItem Actual, string[] Failures)> FindClosestMismatchesByGreedyAssignment( + List> remainingSubjects, IndexedItemCollection expectationsWithIndexes) + { + int subjectCount = remainingSubjects.Count; + int expectationCount = expectationsWithIndexes.Count; + int pairCount = expectationCount * subjectCount; + + // Use failure counts (no string allocation) to build the pair list for sorting/assignment. + var allPairs = new List<(int ExpectationIndex, int SubjectIndex, int Count)>(pairCount); + + for (int expectationIndex = 0; expectationIndex < expectationCount; expectationIndex++) + { + IndexedItem exp = expectationsWithIndexes[expectationIndex]; + + for (int subjectIndex = 0; subjectIndex < subjectCount; subjectIndex++) + { + int count = TryToMatchCount(exp.Item, remainingSubjects[subjectIndex].Item, exp.Index); + allPairs.Add((expectationIndex, subjectIndex, count)); + } + } + + // Sort by distance ascending. When distances are equal, use expectation index then subject index as + // tiebreakers so that assignments are deterministic and follow natural ordering. + allPairs.Sort(static (a, b) => + { + int relativeOrder = a.Count.CompareTo(b.Count); + if (relativeOrder != 0) + { + return relativeOrder; + } + + relativeOrder = a.ExpectationIndex.CompareTo(b.ExpectationIndex); + return relativeOrder != 0 ? relativeOrder : a.SubjectIndex.CompareTo(b.SubjectIndex); + }); + + var assignedExpectationIndexes = new bool[expectationCount]; + var assignedSubjectIndexes = new bool[subjectCount]; + int totalToAssign = Math.Min(expectationCount, subjectCount); + + var result = new List<(IndexedItem, IndexedItem, string[])>(totalToAssign); + + // First checks candidate matches from best to worst, then picks the first unused expectation/subject pair it finds, + // computes detailed failures only for that chosen pair, and then repeats until all possible matches are assigned. + foreach (var (expectationIndex, subjectIndex, _) in allPairs) + { + if (!assignedExpectationIndexes[expectationIndex] && !assignedSubjectIndexes[subjectIndex]) + { + // Fetch full failure strings only for the selected pairs (~n total). + string[] failures = TryToMatch(expectationsWithIndexes[expectationIndex].Item, + remainingSubjects[subjectIndex].Item, expectationsWithIndexes[expectationIndex].Index); + + result.Add((expectationsWithIndexes[expectationIndex], remainingSubjects[subjectIndex], failures)); + assignedExpectationIndexes[expectationIndex] = true; + assignedSubjectIndexes[subjectIndex] = true; + + if (result.Count == totalToAssign) + { + break; + } + } + } + + return result; + } + + /// + /// Performs a dry-run comparison using a scope that skips formatting. Returns the number of failures + /// without allocating any string messages. Used in Phase 1 and Phase 2 to avoid eager + /// FailureMessageFormatter + Regex.Replace costs for pairs that will ultimately be discarded. + /// + private int TryToMatchCount(TExpectation expectation, object subject, int expectationIndex) + { + var cacheKey = (subject, (object)expectation, expectationIndex); + + if (countCache.TryGetValue(cacheKey, out int cachedCount)) + { + return cachedCount; + } + + using var scope = new AssertionScope { UseDryRun = true }; + + var itemContext = (EquivalencyValidationContext)context.AsCollectionItem(expectationIndex); + itemContext.CyclicReferenceDetector = (CyclicReferenceDetector)((EquivalencyValidationContext)context).CyclicReferenceDetector.Clone(); + + parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(TExpectation)), itemContext); + + int count = scope.GetFailureCount(); + scope.Discard(); + countCache[cacheKey] = count; + + return count; + } + + private string[] TryToMatch(TExpectation expectation, object subject, int expectationIndex) + { + var cacheKey = (subject, (object)expectation, expectationIndex); + + if (fullFailuresCache.TryGetValue(cacheKey, out string[] cachedResult)) + { + return cachedResult; + } + + using var scope = new AssertionScope(); + + var itemContext = (EquivalencyValidationContext)context.AsCollectionItem(expectationIndex); + itemContext.CyclicReferenceDetector = (CyclicReferenceDetector)((EquivalencyValidationContext)context).CyclicReferenceDetector.Clone(); + + parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(TExpectation)), itemContext); + + string[] failures = scope.Discard(); + fullFailuresCache[cacheKey] = failures; + + return failures; + } +} diff --git a/Src/FluentAssertions/Equivalency/Steps/ReadOnlyCollectionExtensions.cs b/Src/FluentAssertions/Equivalency/Steps/ReadOnlyCollectionExtensions.cs new file mode 100644 index 0000000000..8b2ca781c1 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/ReadOnlyCollectionExtensions.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FluentAssertions.Equivalency.Steps; + +[System.Diagnostics.StackTraceHidden] +internal static class ReadOnlyCollectionExtensions +{ + /// + /// Generates all possible permutations of the given collection. + /// + /// The type of the elements in the collection. + /// The collection of items for which permutations are to be generated. + /// A sequence of all possible permutations, where each permutation is represented as a read-only list of elements. + public static IEnumerable> Permute(this IReadOnlyList items) + { + int[] indices = Enumerable.Range(0, items.Count).ToArray(); + + do + { + var result = new T[indices.Length]; + for (int index = 0; index < indices.Length; index++) + { + result[index] = items[indices[index]]; + } + + yield return result; + } + while (NextPermutation(indices)); + } + + private static bool NextPermutation(int[] indices) + { + int index = indices.Length - 2; + while (index >= 0 && indices[index] >= indices[index + 1]) + { + index--; + } + + if (index < 0) + { + return false; + } + + int j = indices.Length - 1; + while (indices[j] <= indices[index]) + { + j--; + } + + (indices[index], indices[j]) = (indices[j], indices[index]); + Array.Reverse(indices, index + 1, indices.Length - index - 1); + return true; + } + +} diff --git a/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs index c41e899078..b1770b6283 100644 --- a/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions.Equivalency.Steps; +namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class ReferenceEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, diff --git a/Src/FluentAssertions/Equivalency/Steps/ReferentialComparer.cs b/Src/FluentAssertions/Equivalency/Steps/ReferentialComparer.cs new file mode 100644 index 0000000000..6b8241bab8 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/ReferentialComparer.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace FluentAssertions.Equivalency.Steps; + +/// +/// Provides a mechanism for comparing tuples that consist of a subject, an expectation, +/// and an expectation index. The comparison is based on object references and the expectation index. +/// +[System.Diagnostics.StackTraceHidden] +internal sealed class ReferentialComparer : IEqualityComparer<(object Subject, object Expectation, int ExpectationIndex)> +{ + public bool Equals((object Subject, object Expectation, int ExpectationIndex) x, + (object Subject, object Expectation, int ExpectationIndex) y) + { + return ReferenceEquals(x.Subject, y.Subject) + && ReferenceEquals(x.Expectation, y.Expectation) + && x.ExpectationIndex == y.ExpectationIndex; + } + + public int GetHashCode((object Subject, object Expectation, int ExpectationIndex) obj) + { + int hashCode = RuntimeHelpers.GetHashCode(obj.Subject); + hashCode = (hashCode * 397) + RuntimeHelpers.GetHashCode(obj.Expectation); + hashCode = (hashCode * 397) + obj.ExpectationIndex; + return hashCode; + } +} diff --git a/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs index 114a0aa726..f928070aa1 100644 --- a/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs @@ -1,9 +1,10 @@ -namespace FluentAssertions.Equivalency.Steps; +namespace FluentAssertions.Equivalency.Steps; /// /// Represents a composite equivalency step that passes the execution to all user-supplied steps that can handle the /// current context. /// +[System.Diagnostics.StackTraceHidden] public class RunAllUserStepsEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, diff --git a/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs index af3598ec1d..3920ab5932 100644 --- a/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs @@ -2,6 +2,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class SimpleEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -21,3 +22,4 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/StrictlyOrderedEquivalencyStrategy.cs b/Src/FluentAssertions/Equivalency/Steps/StrictlyOrderedEquivalencyStrategy.cs new file mode 100644 index 0000000000..809a41bad8 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/StrictlyOrderedEquivalencyStrategy.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using FluentAssertions.Equivalency.Tracing; +using FluentAssertions.Execution; +using static FluentAssertions.Common.StringExtensions; + +namespace FluentAssertions.Equivalency.Steps; + +[System.Diagnostics.StackTraceHidden] +internal class StrictlyOrderedEquivalencyStrategy( + IValidateChildNodeEquivalency parent, + IEquivalencyValidationContext context) +{ + private const int FailedItemsFastFailThreshold = 10; + private readonly Tracer tracer = context.Tracer; + + public void FindAndRemoveMatches(List> subjects, List expectations) + { + int failedCount = 0; + + int index = 0; + for (; index < expectations.Count && index < subjects.Count; index++) + { + TExpectation expectation = expectations[index]; + + int tempIndex = index; + using var _ = tracer.WriteBlock(member => + Invariant($"Strictly comparing expectation {expectation} at {member.Expectation} to item with index {tempIndex} in {subjects}")); + + bool succeeded = StrictlyMatchAgainst(subjects, expectation, index); + if (!succeeded) + { + failedCount++; + if (failedCount >= FailedItemsFastFailThreshold) + { + tracer.WriteLine(member => + $"Aborting strict order comparison of collections after {FailedItemsFastFailThreshold} items failed at {member.Expectation}"); + + break; + } + } + } + + subjects.RemoveRange(0, index); + expectations.RemoveRange(0, index); + } + + private bool StrictlyMatchAgainst(List> subjects, T expectation, int expectationIndex) + { + using var scope = new AssertionScope(); + + object subject = subjects[expectationIndex].Item; + IEquivalencyValidationContext equivalencyValidationContext = context.AsCollectionItem(expectationIndex); + + parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), equivalencyValidationContext); + + return !scope.HasFailures(); + } +} diff --git a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs index 80c8b32452..bf283246d3 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs @@ -3,6 +3,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class StringEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -17,7 +18,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon var assertionChain = AssertionChain.GetOrCreate().For(context); - if (!ValidateAgainstNulls(assertionChain, comparands, context.CurrentNode)) + if (!ValidateAgainstNulls(assertionChain, comparands, context.CurrentNode, context.Options.TreatNullStringsAsEmpty)) { return EquivalencyResult.EquivalencyProven; } @@ -73,7 +74,8 @@ private static Func, EquivalencyOptions> Crea return o; }; - private static bool ValidateAgainstNulls(AssertionChain assertionChain, Comparands comparands, INode currentNode) + private static bool ValidateAgainstNulls(AssertionChain assertionChain, Comparands comparands, INode currentNode, + bool treatNullAsEmpty) { object expected = comparands.Expectation; object subject = comparands.Subject; @@ -82,6 +84,16 @@ private static bool ValidateAgainstNulls(AssertionChain assertionChain, Comparan if (onlyOneNull) { + if (treatNullAsEmpty) + { + string nonNullValue = (string)(expected ?? subject); + + if (nonNullValue.Length == 0) + { + return false; + } + } + assertionChain.FailWith( "Expected {0} to be {1}{reason}, but found {2}.", currentNode.Subject.Description.AsNonFormattable(), expected, subject); @@ -100,9 +112,10 @@ private static bool ValidateSubjectIsString(AssertionChain assertionChain, Compa } assertionChain.FailWith( - "Expected {0} to be {1}, but found {2}.", currentNode.AsNonFormattable(), - comparands.RuntimeType, comparands.Subject.GetType()); + "Expected {0} to be of type {1}, but found {2} of type {3}.", currentNode.AsNonFormattable(), + comparands.RuntimeType, comparands.Subject, comparands.Subject.GetType()); return assertionChain.Succeeded; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs index 188b4f7d79..2481aca98d 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class StructuralEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -108,3 +109,4 @@ private static IEnumerable GetMembersFromExpectation(INode currentNode, return members; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs index e63c850e43..04fdf8183c 100644 --- a/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs @@ -13,6 +13,7 @@ namespace FluentAssertions.Equivalency.Steps; /// This differs from the default equivalency assertion which states that two objects are equivalent if they have the /// same properties and values, regardless of their type. /// +[System.Diagnostics.StackTraceHidden] public class TypeEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, @@ -57,3 +58,4 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs index 2af82fa77d..1ddaa1228a 100644 --- a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions.Equivalency.Selection; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; @@ -6,36 +9,170 @@ namespace FluentAssertions.Equivalency.Steps; /// /// Ensures that types that are marked as value types are treated as such. /// +[System.Diagnostics.StackTraceHidden] public class ValueTypeEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IValidateChildNodeEquivalency valueChildNodes) { - Type expectationType = comparands.GetExpectedType(context.Options); - EqualityStrategy strategy = context.Options.GetEqualityStrategy(expectationType); + var options = context.Options; + Type expectationType = comparands.GetExpectedType(options); + EqualityStrategy strategy = options.GetEqualityStrategy(expectationType); - bool canHandle = strategy is EqualityStrategy.Equals or EqualityStrategy.ForceEquals; + if (strategy is not (EqualityStrategy.Equals or EqualityStrategy.ForceEquals)) + { + return EquivalencyResult.ContinueWithNext; + } - if (canHandle) + if (ReportConflictIfAny(context, comparands, options, expectationType, strategy)) { - context.Tracer.WriteLine(member => - { - string strategyName = strategy == EqualityStrategy.Equals - ? $"{expectationType} overrides Equals" - : "we are forced to use Equals"; + return EquivalencyResult.EquivalencyProven; + } + + ApplyValueSemantics(comparands, context, expectationType, strategy); + return EquivalencyResult.EquivalencyProven; + } - return $"Treating {member.Expectation.Description} as a value type because {strategyName}."; - }); + /// + /// Checks for any selection rule that targets members of the current node, which would be silently + /// ignored under value-semantic comparison. Reports the conflict and returns true if one is found. + /// + private static bool ReportConflictIfAny( + IEquivalencyValidationContext context, + Comparands comparands, + IEquivalencyOptions options, + Type expectationType, + EqualityStrategy strategy) + { + // Path-based rules can be checked cheaply by comparing path strings (also handles deep paths like o.Child.Text). + IMemberSelectionRule conflictingRule = options.SelectionRules + .OfType() + .FirstOrDefault(rule => rule.SelectsMembersOf(context.CurrentNode)); - AssertionChain.GetOrCreate() - .For(context) - .ReuseOnce(); + // Non-path rules (based on predicates) require evaluating them against the actual member list + conflictingRule ??= FindConflictingNonPathRule(context.CurrentNode, comparands, options); - comparands.Subject.Should().Be(comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments); + if (conflictingRule is not null) + { + ReportConflict(context, expectationType, conflictingRule, strategy); + } - return EquivalencyResult.EquivalencyProven; + return conflictingRule is not null; + } + + /// + /// Finds the first non-path selection rule that would modify the member list of , + /// indicating it would be silently ignored due to value-semantic comparison. + /// + private static IMemberSelectionRule FindConflictingNonPathRule( + INode currentNode, Comparands comparands, IEquivalencyOptions options) + { + var selectionContext = new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, options); + IList allMembers = GetAllMembers(currentNode, selectionContext); + + // If the type has no accessible members, no selection rule can meaningfully conflict. + if (allMembers.Count == 0) + { + return null; } - return EquivalencyResult.ContinueWithNext; + return options.SelectionRules + .Where(rule => !IsInfrastructureRule(rule) && rule is not IPathBasedSelectionRule) + .FirstOrDefault(rule => RuleAffectsMembers(rule, allMembers, GetFilteredMembers(rule, currentNode, allMembers, selectionContext))); + } + + /// + /// Applies in isolation to determine whether it modifies the member list. + /// Inclusion rules are run from an empty starting set; exclusion rules are run on the full member list. + /// + private static IList GetFilteredMembers( + IMemberSelectionRule rule, INode currentNode, IList allMembers, MemberSelectionContext context) + { + IEnumerable seed = rule.IncludesMembers ? [] : allMembers; + return rule.SelectMembers(currentNode, seed, context).ToList(); + } + + /// Gets all properties and fields of the current node's type, respecting the configured visibility. + private static IList GetAllMembers(INode currentNode, MemberSelectionContext context) + { + IEnumerable members = new AllPropertiesSelectionRule().SelectMembers(currentNode, [], context); + return new AllFieldsSelectionRule().SelectMembers(currentNode, members, context).ToList(); + } + + /// + /// Returns true if actually affects the member set of the current value type. + /// For inclusion rules, a conflict exists only when the rule selects at least one member of this type, + /// meaning the user intended to compare this value type member-by-member. + /// For exclusion rules, a conflict exists when the rule removes at least one member from the full set. + /// + private static bool RuleAffectsMembers(IMemberSelectionRule rule, IList allMembers, IList filteredMembers) => + rule.IncludesMembers + ? filteredMembers.Count > 0 + : MemberSetChanged(allMembers, filteredMembers); + + /// Returns true if represents a different set of members than . + private static bool MemberSetChanged(IList before, IList after) => + before.Count != after.Count || before.Except(after).Any(); + + /// Returns true for rules that are part of the default selection pipeline, not user-configured. + private static bool IsInfrastructureRule(IMemberSelectionRule rule) => + rule is AllPropertiesSelectionRule or AllFieldsSelectionRule or ExcludeNonBrowsableMembersRule; + + /// Reports an assertion failure for a selection rule that conflicts with value semantics. + private static void ReportConflict( + IEquivalencyValidationContext context, + Type expectationType, + IMemberSelectionRule conflictingRule, + EqualityStrategy strategy) + { + Reason reason = context.Reason; + bool isGeneric = expectationType.IsGenericType; + + string cause = strategy == EqualityStrategy.ForceEquals + ? $"{expectationType} is compared by value (because ComparingByValue was configured), " + : $"{expectationType} is compared by value (because it overrides Equals), "; + + string suggestion = strategy == EqualityStrategy.ForceEquals + ? "Either remove the ComparingByValue configuration, " + : isGeneric + ? "Either call the ComparingByMembers(Type) overload to force member-wise comparison, " + : $"Either call ComparingByMembers<{expectationType.Name}>() to force member-wise comparison, "; + + string message = + cause + + $"so the {conflictingRule} selection rule does not apply. " + + suggestion + + "or remove the selection rule." + + reason.FormattedMessage; + + AssertionChain.GetOrCreate().For(context).FailWith(message, reason.Arguments); + } + + /// + /// Traces the value-semantic comparison strategy and performs the equality assertion. + /// + private static void ApplyValueSemantics( + Comparands comparands, + IEquivalencyValidationContext context, + Type expectationType, + EqualityStrategy strategy) + { + context.Tracer.WriteLine(member => + { + string strategyName = strategy == EqualityStrategy.Equals + ? $"{expectationType} overrides Equals" + : "we are forced to use Equals"; + + return $"Treating {member.Expectation.Description} as a value type because {strategyName}."; + }); + + var reason = context.Reason; + + AssertionChain.GetOrCreate() + .For(context) + .ReuseOnce(); + + comparands.Subject.Should().Be(comparands.Expectation, reason.FormattedMessage, reason.Arguments); } } + diff --git a/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs index c403ed74d9..5cd7af5f93 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs @@ -1,8 +1,9 @@ -using System.Xml.Linq; +using System.Xml.Linq; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class XAttributeEquivalencyStep : EquivalencyStep { protected override EquivalencyResult OnHandle(Comparands comparands, diff --git a/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs index b88921f45b..727f6ef730 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs @@ -1,8 +1,9 @@ -using System.Xml.Linq; +using System.Xml.Linq; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class XDocumentEquivalencyStep : EquivalencyStep { protected override EquivalencyResult OnHandle(Comparands comparands, diff --git a/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs index 9ed4e2d503..3450ec7716 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs @@ -1,8 +1,9 @@ -using System.Xml.Linq; +using System.Xml.Linq; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; +[System.Diagnostics.StackTraceHidden] public class XElementEquivalencyStep : EquivalencyStep { protected override EquivalencyResult OnHandle(Comparands comparands, diff --git a/Src/FluentAssertions/Equivalency/SubjectInfoExtensions.cs b/Src/FluentAssertions/Equivalency/SubjectInfoExtensions.cs index 6cdc9e0349..abc1ce3696 100644 --- a/Src/FluentAssertions/Equivalency/SubjectInfoExtensions.cs +++ b/Src/FluentAssertions/Equivalency/SubjectInfoExtensions.cs @@ -1,7 +1,8 @@ -using FluentAssertions.Common; +using FluentAssertions.Common; namespace FluentAssertions.Equivalency; +[System.Diagnostics.StackTraceHidden] public static class SubjectInfoExtensions { /// @@ -48,3 +49,4 @@ public static bool WhichGetterDoesNotHave(this IMemberInfo memberInfo, CSharpAcc return memberInfo.GetterAccessibility != accessModifier; } } + diff --git a/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs b/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs index 1df987b6ae..6c3b2d8b51 100644 --- a/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs +++ b/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs @@ -1,5 +1,3 @@ namespace FluentAssertions.Equivalency.Tracing; -internal sealed class InternalTraceWriter : StringBuilderTraceWriter -{ -} +internal sealed class InternalTraceWriter : StringBuilderTraceWriter; diff --git a/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs b/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs index a0752de0e3..28119cfba2 100644 --- a/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs +++ b/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs @@ -30,7 +30,7 @@ private void WriteLine(string trace) { foreach (string traceLine in trace.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)) { - builder.Append(new string(' ', depth * 2)).AppendLine(traceLine); + builder.Append(' ', depth * 2).AppendLine(traceLine); } } diff --git a/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs b/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs index 8f81205c2c..5140bd56c4 100644 --- a/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs +++ b/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs @@ -3,6 +3,7 @@ namespace FluentAssertions.Equivalency.Typing; /// /// An implementation of that applies strict typing to all objects. /// +[System.Diagnostics.StackTraceHidden] internal class AlwaysBeStrictTypingRule : ITypingRule { /// diff --git a/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs b/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs index 3aea5b3db2..f6bfe6daa2 100644 --- a/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs +++ b/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Equivalency.Typing; /// An implementation of that uses a predicate to determine /// whether strict typing should be applied during equivalency comparison. /// +[System.Diagnostics.StackTraceHidden] internal class PredicateBasedTypingRule : ITypingRule { private readonly Func predicate; @@ -37,3 +38,4 @@ public override string ToString() return $"Use strict typing when {description}"; } } + diff --git a/Src/FluentAssertions/EventRaisingExtensions.cs b/Src/FluentAssertions/EventRaisingExtensions.cs index 15798cba20..17c04f2b23 100644 --- a/Src/FluentAssertions/EventRaisingExtensions.cs +++ b/Src/FluentAssertions/EventRaisingExtensions.cs @@ -177,3 +177,4 @@ internal static IEventRecording WithPropertyChangeFor(this IEventRecording event return new FilteredEventRecording(eventRecording, eventsForPropertyName); } } + diff --git a/Src/FluentAssertions/Events/EventAssertions.cs b/Src/FluentAssertions/Events/EventAssertions.cs index 36210c41eb..00c1bacc11 100644 --- a/Src/FluentAssertions/Events/EventAssertions.cs +++ b/Src/FluentAssertions/Events/EventAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -10,7 +10,7 @@ namespace FluentAssertions.Events; /// -/// Provides convenient assertion methods on a that can be +/// Provides convenient assertion methods on an that can be /// used to assert that certain events have been raised. /// public class EventAssertions : ReferenceTypeAssertions> @@ -174,3 +174,4 @@ public void NotRaisePropertyChangeFor(Expression> propertyExpres protected override string Identifier => "subject"; } + diff --git a/Src/FluentAssertions/Events/EventHandlerFactory.cs b/Src/FluentAssertions/Events/EventHandlerFactory.cs index bf05ddf5af..54dc1e4d93 100644 --- a/Src/FluentAssertions/Events/EventHandlerFactory.cs +++ b/Src/FluentAssertions/Events/EventHandlerFactory.cs @@ -7,6 +7,7 @@ namespace FluentAssertions.Events; /// /// Static methods that aid in generic event subscription /// +[System.Diagnostics.StackTraceHidden] internal static class EventHandlerFactory { /// @@ -146,3 +147,4 @@ private static MethodInfo DelegateInvokeMethod(Type d) return invoke; } } + diff --git a/Src/FluentAssertions/Events/EventMetadata.cs b/Src/FluentAssertions/Events/EventMetadata.cs index e569b862c8..3fc8f5c97f 100644 --- a/Src/FluentAssertions/Events/EventMetadata.cs +++ b/Src/FluentAssertions/Events/EventMetadata.cs @@ -1,10 +1,11 @@ -using System; +using System; namespace FluentAssertions.Events; /// /// Provides the metadata of a monitored event. /// +[System.Diagnostics.StackTraceHidden] public class EventMetadata { /// @@ -23,3 +24,4 @@ public EventMetadata(string eventName, Type handlerType) HandlerType = handlerType; } } + diff --git a/Src/FluentAssertions/Events/EventMonitor.cs b/Src/FluentAssertions/Events/EventMonitor.cs index 20ae36d1ea..23ecf5afb0 100644 --- a/Src/FluentAssertions/Events/EventMonitor.cs +++ b/Src/FluentAssertions/Events/EventMonitor.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_0 using System; using System.Collections.Concurrent; @@ -13,6 +13,7 @@ namespace FluentAssertions.Events; /// /// Tracks the events an object raises. /// +[System.Diagnostics.StackTraceHidden] internal sealed class EventMonitor : IMonitor { private readonly WeakReference subject; @@ -123,7 +124,7 @@ public void Dispose() recorderMap.Clear(); } - private void DisposeSafeIfRequested(IDisposable recorder) + private void DisposeSafeIfRequested(EventRecorder recorder) { try { @@ -137,7 +138,7 @@ private void DisposeSafeIfRequested(IDisposable recorder) private void AttachEventHandler(EventInfo eventInfo, Func utcNow) { - if (!recorderMap.TryGetValue(eventInfo.Name, out _)) + if (!recorderMap.ContainsKey(eventInfo.Name)) { var recorder = new EventRecorder(subject.Target, eventInfo.Name, utcNow, threadSafeSequenceGenerator); @@ -165,3 +166,4 @@ private void AttachEventHandler(EventInfo eventInfo, EventRecorder recorder) } #endif + diff --git a/Src/FluentAssertions/Events/EventMonitorOptions.cs b/Src/FluentAssertions/Events/EventMonitorOptions.cs index c6cbf76d25..4d04765e88 100644 --- a/Src/FluentAssertions/Events/EventMonitorOptions.cs +++ b/Src/FluentAssertions/Events/EventMonitorOptions.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Events; /// /// Settings for the EventMonitor. /// +[System.Diagnostics.StackTraceHidden] public class EventMonitorOptions { /// @@ -54,3 +55,4 @@ internal EventMonitorOptions ConfigureTimestampProvider(Func timestamp return this; } } + diff --git a/Src/FluentAssertions/Events/EventRecorder.cs b/Src/FluentAssertions/Events/EventRecorder.cs index 768449083e..33ec8de284 100644 --- a/Src/FluentAssertions/Events/EventRecorder.cs +++ b/Src/FluentAssertions/Events/EventRecorder.cs @@ -12,6 +12,7 @@ namespace FluentAssertions.Events; /// Records activity for a single event. /// [DebuggerNonUserCode] +[StackTraceHidden] internal sealed class EventRecorder : IEventRecording, IDisposable { private readonly Func utcNow; diff --git a/Src/FluentAssertions/Events/FilteredEventRecording.cs b/Src/FluentAssertions/Events/FilteredEventRecording.cs index 018229d66f..a99ae9970a 100644 --- a/Src/FluentAssertions/Events/FilteredEventRecording.cs +++ b/Src/FluentAssertions/Events/FilteredEventRecording.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace FluentAssertions.Events; +[System.Diagnostics.StackTraceHidden] internal class FilteredEventRecording : IEventRecording { private readonly OccurredEvent[] occurredEvents; @@ -42,3 +43,4 @@ public IEnumerator GetEnumerator() } } } + diff --git a/Src/FluentAssertions/Events/IMonitor.cs b/Src/FluentAssertions/Events/IMonitor.cs index a1e831d2d5..cb47f8d38c 100644 --- a/Src/FluentAssertions/Events/IMonitor.cs +++ b/Src/FluentAssertions/Events/IMonitor.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace FluentAssertions.Events; diff --git a/Src/FluentAssertions/Events/OccurredEvent.cs b/Src/FluentAssertions/Events/OccurredEvent.cs index 6f062b7050..74e8865cd6 100644 --- a/Src/FluentAssertions/Events/OccurredEvent.cs +++ b/Src/FluentAssertions/Events/OccurredEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; @@ -7,6 +7,7 @@ namespace FluentAssertions.Events; /// /// Represents an occurrence of a particular event. /// +[System.Diagnostics.StackTraceHidden] public class OccurredEvent { /// @@ -44,3 +45,4 @@ internal bool IsAffectingPropertyName(string propertyName) .Any(e => string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == propertyName); } } + diff --git a/Src/FluentAssertions/Events/RecordedEvent.cs b/Src/FluentAssertions/Events/RecordedEvent.cs index 1d44a724a6..b486192e28 100644 --- a/Src/FluentAssertions/Events/RecordedEvent.cs +++ b/Src/FluentAssertions/Events/RecordedEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace FluentAssertions.Events; @@ -7,6 +7,7 @@ namespace FluentAssertions.Events; /// This class is used to store data about an intercepted event /// [DebuggerNonUserCode] +[StackTraceHidden] internal class RecordedEvent { /// diff --git a/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs b/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs index 3faccd0e53..d72e869554 100644 --- a/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs +++ b/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Events; /// /// Generates a sequence in a thread-safe manner. /// +[System.Diagnostics.StackTraceHidden] internal sealed class ThreadSafeSequenceGenerator { private int sequence = -1; @@ -17,3 +18,4 @@ public int Increment() return Interlocked.Increment(ref sequence); } } + diff --git a/Src/FluentAssertions/Exactly.cs b/Src/FluentAssertions/Exactly.cs index 6b1c424666..1319edc240 100644 --- a/Src/FluentAssertions/Exactly.cs +++ b/Src/FluentAssertions/Exactly.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions; +namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public static class Exactly { public static OccurrenceConstraint Once() => new ExactlyTimesConstraint(1); diff --git a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs index 2de8149e47..31773d9dc8 100644 --- a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs +++ b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs @@ -1,4 +1,5 @@ using System; + using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Threading.Tasks; @@ -224,3 +225,4 @@ public static async Task> WithParameterName, /// you can run multiple assertions which failure messages will be collected until the scope is disposed. /// +[System.Diagnostics.StackTraceHidden] public sealed class AssertionChain { private readonly Func getCurrentScope; @@ -31,9 +32,10 @@ public sealed class AssertionChain /// /// Ensures that the next call to will reuse the current instance. /// - public void ReuseOnce() + public AssertionChain ReuseOnce() { Instance.Value = this; + return this; } /// @@ -246,18 +248,28 @@ private Continuation FailWith(Func getFailureReason) { if (PreviousAssertionSucceeded) { - PreviousAssertionSucceeded = succeeded is true; + PreviousAssertionSucceeded = succeeded == true; - if (succeeded is not true) + if (succeeded != true) { - string failure = getFailureReason(); + AssertionScope scope = getCurrentScope(); - if (expectation is not null) + if (scope.UseDryRun) { - failure = expectation() + failure; + // Dry-run mode: skip all string formatting and only count the failure. + scope.RegisterFailure(); } + else + { + string failure = getFailureReason(); + + if (expectation is not null) + { + failure = expectation() + failure; + } - getCurrentScope().AddPreFormattedFailure(failure.Capitalize().RemoveTrailingWhitespaceFromLines()); + scope.AddPreFormattedFailure(failure.Capitalize().RemoveTrailingWhitespaceFromLines()); + } } } @@ -287,7 +299,17 @@ public void OverrideCallerIdentifier(Func getCallerIdentifier) public AssertionChain WithCallerPostfix(string postfix) { identifierBuilder.UsePostfix(postfix); + return this; + } + /// + /// Adds a specified prefix to the caller identifier used in the current assertion chain. + /// + /// The prefix to prepend to the caller identifier. + /// An updated instance of with the specified prefix applied. + public AssertionChain WithCallerPrefix(string prefix) + { + identifierBuilder.UsePrefix(prefix); return this; } @@ -295,9 +317,10 @@ public AssertionChain WithCallerPostfix(string postfix) /// Marks the next assertion as being part of a chained call to Should where it needs to find the next /// caller identifier. /// - internal void AdvanceToNextIdentifier() + internal AssertionChain AdvanceToNextIdentifier() { identifierBuilder.AdvanceToNextSubject(); + return this; } /// diff --git a/Src/FluentAssertions/Execution/AssertionFailedException.cs b/Src/FluentAssertions/Execution/AssertionFailedException.cs index d21f2f6ab8..dda9396090 100644 --- a/Src/FluentAssertions/Execution/AssertionFailedException.cs +++ b/Src/FluentAssertions/Execution/AssertionFailedException.cs @@ -7,7 +7,9 @@ namespace FluentAssertions.Execution; /// /// The mandatory exception message #pragma warning disable CA1032, RCS1194 // AssertionFailedException should never be constructed with an empty message +[System.Diagnostics.StackTraceHidden] public class AssertionFailedException(string message) : Exception(message), IAssertionException #pragma warning restore CA1032, RCS1194 { } + diff --git a/Src/FluentAssertions/Execution/AssertionScope.cs b/Src/FluentAssertions/Execution/AssertionScope.cs index 53c56c0c79..2060d94649 100644 --- a/Src/FluentAssertions/Execution/AssertionScope.cs +++ b/Src/FluentAssertions/Execution/AssertionScope.cs @@ -15,6 +15,7 @@ namespace FluentAssertions.Execution; /// such as when using or . /// // Remove all assertion logic from this class since it is superseded by the Assertion class +[System.Diagnostics.StackTraceHidden] public sealed class AssertionScope : IDisposable { private readonly IAssertionStrategy assertionStrategy; @@ -25,6 +26,9 @@ public sealed class AssertionScope : IDisposable private AssertionScope parent; + // Tracks failure count when SkipFormattingForFailures is enabled (no string storage needed). + private int failureCount; + /// /// Starts an unnamed scope within which multiple assertions can be executed /// and which will not throw until the scope is disposed. @@ -69,11 +73,11 @@ public AssertionScope(Func name) /// is . private AssertionScope(Func name, IAssertionStrategy assertionStrategy) { + Guard.ThrowIfArgumentIsNull(assertionStrategy); parent = CurrentScope.Value; CurrentScope.Value = this; - this.assertionStrategy = assertionStrategy - ?? throw new ArgumentNullException(nameof(assertionStrategy)); + this.assertionStrategy = assertionStrategy; if (parent is not null) { @@ -132,6 +136,25 @@ public static AssertionScope Current /// public FormattingOptions FormattingOptions { get; } = AssertionConfiguration.Current.Formatting.Clone(); + /// + /// When set to true, will skip building failure message + /// strings and only track the number of failures. Use this for dry-run comparisons (e.g. collection + /// matching) where the actual formatted messages are not needed and would be discarded anyway. + /// + internal bool UseDryRun { get; set; } + + /// + /// Returns the number of failures recorded while is enabled. + /// Only counts direct failures tracked via . + /// + internal int GetFailureCount() => failureCount + assertionStrategy.FailureMessages.Count(); + + /// + /// Increments the failure counter without storing or formatting a failure message. + /// Only called from when is active. + /// + internal void RegisterFailure() => failureCount++; + /// /// Adds a pre-formatted failure message to the current scope. /// diff --git a/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs b/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs index 4c017f1806..574f652aa2 100644 --- a/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs @@ -6,6 +6,7 @@ namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal class CollectingAssertionStrategy : IAssertionStrategy { private readonly List failureMessages = []; @@ -48,7 +49,7 @@ public void ThrowIfAny(IDictionary context) } /// - /// Instructs the strategy to handle a assertion failure. + /// Instructs the strategy to handle an assertion failure. /// public void HandleFailure(string message) { diff --git a/Src/FluentAssertions/Execution/ContextDataDictionary.cs b/Src/FluentAssertions/Execution/ContextDataDictionary.cs index 7bc42fdd97..3e112e493e 100644 --- a/Src/FluentAssertions/Execution/ContextDataDictionary.cs +++ b/Src/FluentAssertions/Execution/ContextDataDictionary.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Execution; /// /// Represents a collection of data items that are associated with an . /// +[System.Diagnostics.StackTraceHidden] internal class ContextDataDictionary { private readonly List items = []; @@ -78,3 +79,4 @@ public DataItem Clone() } } } + diff --git a/Src/FluentAssertions/Execution/Continuation.cs b/Src/FluentAssertions/Execution/Continuation.cs index b2f65e62a3..ee6d772807 100644 --- a/Src/FluentAssertions/Execution/Continuation.cs +++ b/Src/FluentAssertions/Execution/Continuation.cs @@ -1,8 +1,9 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; /// /// Enables chaining multiple assertions on an . /// +[System.Diagnostics.StackTraceHidden] public class Continuation { internal Continuation(AssertionChain parent) diff --git a/Src/FluentAssertions/Execution/ContinuationOfGiven.cs b/Src/FluentAssertions/Execution/ContinuationOfGiven.cs index ef42263f3f..f3240248d1 100644 --- a/Src/FluentAssertions/Execution/ContinuationOfGiven.cs +++ b/Src/FluentAssertions/Execution/ContinuationOfGiven.cs @@ -1,8 +1,9 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; /// -/// Enables chaining multiple assertions from a call. +/// Enables chaining multiple assertions from an call. /// +[System.Diagnostics.StackTraceHidden] public class ContinuationOfGiven { internal ContinuationOfGiven(GivenSelector parent) @@ -17,3 +18,4 @@ internal ContinuationOfGiven(GivenSelector parent) public bool Succeeded => Then.Succeeded; } + diff --git a/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs b/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs index 2a0a5d8e03..b6421e59a0 100644 --- a/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs @@ -4,6 +4,7 @@ namespace FluentAssertions.Execution; [ExcludeFromCodeCoverage] +[System.Diagnostics.StackTraceHidden] internal class DefaultAssertionStrategy : IAssertionStrategy { /// @@ -12,7 +13,7 @@ internal class DefaultAssertionStrategy : IAssertionStrategy public IEnumerable FailureMessages => []; /// - /// Instructs the strategy to handle a assertion failure. + /// Instructs the strategy to handle an assertion failure. /// public void HandleFailure(string message) { diff --git a/Src/FluentAssertions/Execution/FailReason.cs b/Src/FluentAssertions/Execution/FailReason.cs index 27bab552db..ea6fffb22f 100644 --- a/Src/FluentAssertions/Execution/FailReason.cs +++ b/Src/FluentAssertions/Execution/FailReason.cs @@ -19,6 +19,7 @@ namespace FluentAssertions.Execution; /// Note that only 10 args are supported in combination with a {reason}. /// /// +[System.Diagnostics.StackTraceHidden] public class FailReason { /// diff --git a/Src/FluentAssertions/Execution/FailureMessageFormatter.cs b/Src/FluentAssertions/Execution/FailureMessageFormatter.cs index 9fc73c1682..905e835f93 100644 --- a/Src/FluentAssertions/Execution/FailureMessageFormatter.cs +++ b/Src/FluentAssertions/Execution/FailureMessageFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -10,6 +10,7 @@ namespace FluentAssertions.Execution; /// /// Encapsulates expanding the various placeholders supported in a failure message. /// +[System.Diagnostics.StackTraceHidden] internal class FailureMessageFormatter(FormattingOptions formattingOptions) { private static readonly char[] Blanks = ['\r', '\n', ' ', '\t']; @@ -152,3 +153,4 @@ private string FormatArgumentPlaceholders(string failureMessage, object[] failur } } } + diff --git a/Src/FluentAssertions/Execution/FallbackTestFramework.cs b/Src/FluentAssertions/Execution/FallbackTestFramework.cs index a5c3715734..eb4fadfc94 100644 --- a/Src/FluentAssertions/Execution/FallbackTestFramework.cs +++ b/Src/FluentAssertions/Execution/FallbackTestFramework.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Execution; /// /// Throws a generic exception in case no other test harness is detected. /// +[System.Diagnostics.StackTraceHidden] internal class FallbackTestFramework : ITestFramework { /// diff --git a/Src/FluentAssertions/Execution/GivenSelector.cs b/Src/FluentAssertions/Execution/GivenSelector.cs index 0c2e42798f..65ed49c8d2 100644 --- a/Src/FluentAssertions/Execution/GivenSelector.cs +++ b/Src/FluentAssertions/Execution/GivenSelector.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using FluentAssertions.Common; @@ -8,6 +8,7 @@ namespace FluentAssertions.Execution; /// Represents a chaining object returned from to continue the assertion using /// an object returned by a selector. /// +[System.Diagnostics.StackTraceHidden] public class GivenSelector { private readonly AssertionChain assertionChain; @@ -74,3 +75,4 @@ public ContinuationOfGiven FailWith(string message, params object[] args) return new ContinuationOfGiven(this); } } + diff --git a/Src/FluentAssertions/Execution/GivenSelectorExtensions.cs b/Src/FluentAssertions/Execution/GivenSelectorExtensions.cs index 2d32816641..a7733f5d02 100644 --- a/Src/FluentAssertions/Execution/GivenSelectorExtensions.cs +++ b/Src/FluentAssertions/Execution/GivenSelectorExtensions.cs @@ -1,10 +1,12 @@ -using System; +using System; + using System.Collections; using System.Collections.Generic; using FluentAssertions.Common; namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal static class GivenSelectorExtensions { public static ContinuationOfGiven> AssertCollectionIsNotNull( @@ -104,3 +106,4 @@ public CollectionWithIndex(ICollection items, int index) IEnumerator IEnumerable.GetEnumerator() => Items.GetEnumerator(); } } + diff --git a/Src/FluentAssertions/Execution/IAssertionStrategy.cs b/Src/FluentAssertions/Execution/IAssertionStrategy.cs index 84612c05cb..8dbd04e0d2 100644 --- a/Src/FluentAssertions/Execution/IAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/IAssertionStrategy.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace FluentAssertions.Execution; /// -/// Defines a strategy for handling failures in a . +/// Defines a strategy for handling failures in an . /// public interface IAssertionStrategy { @@ -13,7 +13,7 @@ public interface IAssertionStrategy IEnumerable FailureMessages { get; } /// - /// Instructs the strategy to handle a assertion failure. + /// Instructs the strategy to handle an assertion failure. /// void HandleFailure(string message); diff --git a/Src/FluentAssertions/Execution/ITestFramework.cs b/Src/FluentAssertions/Execution/ITestFramework.cs index 9aa877ea26..114a8849ea 100644 --- a/Src/FluentAssertions/Execution/ITestFramework.cs +++ b/Src/FluentAssertions/Execution/ITestFramework.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Execution/LateBoundTestFramework.cs b/Src/FluentAssertions/Execution/LateBoundTestFramework.cs index 010150d46a..ed2422c813 100644 --- a/Src/FluentAssertions/Execution/LateBoundTestFramework.cs +++ b/Src/FluentAssertions/Execution/LateBoundTestFramework.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal abstract class LateBoundTestFramework : ITestFramework { private Func exceptionFactory = diff --git a/Src/FluentAssertions/Execution/MSTestFrameworkV2.cs b/Src/FluentAssertions/Execution/MSTestFrameworkV2.cs index b299b995ab..8a424f3aad 100644 --- a/Src/FluentAssertions/Execution/MSTestFrameworkV2.cs +++ b/Src/FluentAssertions/Execution/MSTestFrameworkV2.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal class MSTestFrameworkV2 : LateBoundTestFramework { protected override string ExceptionFullName => "Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException"; diff --git a/Src/FluentAssertions/Execution/MSTestFrameworkV4.cs b/Src/FluentAssertions/Execution/MSTestFrameworkV4.cs index c40fcda2c3..249f0cc075 100644 --- a/Src/FluentAssertions/Execution/MSTestFrameworkV4.cs +++ b/Src/FluentAssertions/Execution/MSTestFrameworkV4.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal sealed class MSTestFrameworkV4 : LateBoundTestFramework { protected override string ExceptionFullName => "Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException"; diff --git a/Src/FluentAssertions/Execution/MSpecFramework.cs b/Src/FluentAssertions/Execution/MSpecFramework.cs index ffbd583395..35532767c8 100644 --- a/Src/FluentAssertions/Execution/MSpecFramework.cs +++ b/Src/FluentAssertions/Execution/MSpecFramework.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal class MSpecFramework : LateBoundTestFramework { protected internal override string AssemblyName => "Machine.Specifications"; diff --git a/Src/FluentAssertions/Execution/NUnitTestFramework.cs b/Src/FluentAssertions/Execution/NUnitTestFramework.cs index 700e4a2ce3..e4624b5cb9 100644 --- a/Src/FluentAssertions/Execution/NUnitTestFramework.cs +++ b/Src/FluentAssertions/Execution/NUnitTestFramework.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal class NUnitTestFramework : LateBoundTestFramework { protected internal override string AssemblyName => "nunit.framework"; diff --git a/Src/FluentAssertions/Execution/Reason.cs b/Src/FluentAssertions/Execution/Reason.cs index ca6996b66f..11468f3d6a 100644 --- a/Src/FluentAssertions/Execution/Reason.cs +++ b/Src/FluentAssertions/Execution/Reason.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Execution; /// /// Represents the reason for a structural equivalency assertion. /// +[System.Diagnostics.StackTraceHidden] public class Reason { public Reason([StringSyntax("CompositeFormat")] string formattedMessage, object[] arguments) @@ -20,7 +21,8 @@ public Reason([StringSyntax("CompositeFormat")] string formattedMessage, object[ public string FormattedMessage { get; set; } /// - /// Zero or more objects to format using the placeholders in . + /// Zero or more objects to format using the placeholders in . /// public object[] Arguments { get; set; } } + diff --git a/Src/FluentAssertions/Execution/StringExtensions.cs b/Src/FluentAssertions/Execution/StringExtensions.cs index ab1b13eb16..6f3b226d4d 100644 --- a/Src/FluentAssertions/Execution/StringExtensions.cs +++ b/Src/FluentAssertions/Execution/StringExtensions.cs @@ -1,5 +1,6 @@ namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal static class StringExtensions { /// diff --git a/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs b/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs index 8d8371000d..cbeae9f658 100644 --- a/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs +++ b/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs @@ -12,6 +12,7 @@ namespace FluentAssertions.Execution; /// (nested) instances of and modifications made by the /// . /// +[System.Diagnostics.StackTraceHidden] internal class SubjectIdentificationBuilder { private readonly Func getScopeName; @@ -58,6 +59,18 @@ public void OverrideSubjectIdentifier(Func getSubject) this.getSubject = getSubject; } + /// + /// Prepends the given prefix to the current subject and combines that with next subject. + /// + /// The prefix to prepend to the current subject. + public void UsePrefix(string prefix) + { + var localIndex = identifierIndex; + getSubject = () => (prefix + GetIdentifier(localIndex)).Combine(GetIdentifier(localIndex + 1)); + + HasOverriddenIdentifier = true; + } + /// /// Appends the given postfix to the current subject and combines that with next subject. /// @@ -100,3 +113,4 @@ private string GetIdentifier(int index) return identifiersExtractedFromTheCode.Value.Length > index ? identifiersExtractedFromTheCode.Value[index] : null; } } + diff --git a/Src/FluentAssertions/Execution/TUnitFramework.cs b/Src/FluentAssertions/Execution/TUnitFramework.cs index 94e8337ee6..90c0d694c0 100644 --- a/Src/FluentAssertions/Execution/TUnitFramework.cs +++ b/Src/FluentAssertions/Execution/TUnitFramework.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; +[System.Diagnostics.StackTraceHidden] internal class TUnitFramework : LateBoundTestFramework { public TUnitFramework() diff --git a/Src/FluentAssertions/Execution/TestFrameworkFactory.cs b/Src/FluentAssertions/Execution/TestFrameworkFactory.cs index ac298b9045..dcc44d4918 100644 --- a/Src/FluentAssertions/Execution/TestFrameworkFactory.cs +++ b/Src/FluentAssertions/Execution/TestFrameworkFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FluentAssertions.Configuration; @@ -9,6 +9,7 @@ namespace FluentAssertions.Execution; /// Determines the test framework, either by scanning the current app domain for known test framework assemblies or by /// passing the framework name directly. /// +[System.Diagnostics.StackTraceHidden] internal static class TestFrameworkFactory { private static readonly Dictionary Frameworks = new() @@ -73,3 +74,4 @@ private static ITestFramework AttemptToDetectUsingDynamicScanning() return Frameworks.Values.FirstOrDefault(framework => framework.IsAvailable); } } + diff --git a/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs b/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs index 0696cde026..bdf8e7756e 100644 --- a/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs +++ b/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs @@ -5,7 +5,9 @@ namespace FluentAssertions.Execution; /// /// Wrapper to tell the not to apply any value formatters on this string. /// +[System.Diagnostics.StackTraceHidden] internal class WithoutFormattingWrapper(string value) { public override string ToString() => value; } + diff --git a/Src/FluentAssertions/Execution/XUnitTestFramework.cs b/Src/FluentAssertions/Execution/XUnitTestFramework.cs index 8ef0c1d7dc..f25b3b5252 100644 --- a/Src/FluentAssertions/Execution/XUnitTestFramework.cs +++ b/Src/FluentAssertions/Execution/XUnitTestFramework.cs @@ -3,6 +3,7 @@ namespace FluentAssertions.Execution; /// /// Implements the xUnit (version 2 and 3) test framework adapter. /// +[System.Diagnostics.StackTraceHidden] internal class XUnitTestFramework : LateBoundTestFramework { private readonly string assemblyName; diff --git a/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs b/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs index c6f5d4f4e2..60b8f02705 100644 --- a/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs +++ b/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; namespace FluentAssertions.Extensibility; @@ -7,6 +7,7 @@ namespace FluentAssertions.Extensibility; /// Can be added to an assembly so it gets a change to initialize Fluent Assertions before the first assertion happens. /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +[System.Diagnostics.StackTraceHidden] public sealed class AssertionEngineInitializerAttribute : Attribute { private readonly string methodName; @@ -28,3 +29,4 @@ internal void Initialize() type?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)?.Invoke(obj: null, parameters: null); } } + diff --git a/Src/FluentAssertions/Extensions/FluentTimeSpanExtensions.cs b/Src/FluentAssertions/Extensions/FluentTimeSpanExtensions.cs index 865b72cf7b..4afd886b08 100644 --- a/Src/FluentAssertions/Extensions/FluentTimeSpanExtensions.cs +++ b/Src/FluentAssertions/Extensions/FluentTimeSpanExtensions.cs @@ -248,3 +248,4 @@ public static TimeSpan And(this TimeSpan sourceTime, TimeSpan offset) return sourceTime.Add(offset); } } + diff --git a/Src/FluentAssertions/Extensions/OccurrenceConstraintExtensions.cs b/Src/FluentAssertions/Extensions/OccurrenceConstraintExtensions.cs index beeaa525bf..1df6053b10 100644 --- a/Src/FluentAssertions/Extensions/OccurrenceConstraintExtensions.cs +++ b/Src/FluentAssertions/Extensions/OccurrenceConstraintExtensions.cs @@ -34,3 +34,4 @@ public static OccurrenceConstraint TimesOrMore(this int times) return AtLeast.Times(times); } } + diff --git a/Src/FluentAssertions/FluentActions.cs b/Src/FluentAssertions/FluentActions.cs index 4f78f889ef..f7621a3243 100644 --- a/Src/FluentAssertions/FluentActions.cs +++ b/Src/FluentAssertions/FluentActions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -10,6 +10,7 @@ namespace FluentAssertions; /// /// Contains static methods to help with exception assertions on actions. /// +[StackTraceHidden] [DebuggerNonUserCode] public static class FluentActions { diff --git a/Src/FluentAssertions/FluentAssertions.csproj b/Src/FluentAssertions/FluentAssertions.csproj index 2d853b4ddd..327d5ffbb9 100644 --- a/Src/FluentAssertions/FluentAssertions.csproj +++ b/Src/FluentAssertions/FluentAssertions.csproj @@ -47,15 +47,19 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all + + true + + - + diff --git a/Src/FluentAssertions/Formatting/AggregateExceptionValueFormatter.cs b/Src/FluentAssertions/Formatting/AggregateExceptionValueFormatter.cs index 5c51953044..65f3dcfb6e 100644 --- a/Src/FluentAssertions/Formatting/AggregateExceptionValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/AggregateExceptionValueFormatter.cs @@ -1,5 +1,5 @@ using System; -using static System.FormattableString; +using System.Globalization; namespace FluentAssertions.Formatting; @@ -29,7 +29,7 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting } else { - formattedGraph.AddLine(Invariant($"{exception.InnerExceptions.Count} (aggregated) exceptions:")); + formattedGraph.AddLine(string.Create(CultureInfo.InvariantCulture, $"{exception.InnerExceptions.Count} (aggregated) exceptions:")); foreach (Exception innerException in exception.InnerExceptions) { diff --git a/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs b/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs index 1c9085dab7..140dd62ebc 100644 --- a/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using FluentAssertions.Common; diff --git a/Src/FluentAssertions/Formatting/DecimalValueFormatter.cs b/Src/FluentAssertions/Formatting/DecimalValueFormatter.cs index a7a6d86020..476e913ab0 100644 --- a/Src/FluentAssertions/Formatting/DecimalValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DecimalValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs b/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs index 01ff94aab3..aa9b376c4b 100644 --- a/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs @@ -45,7 +45,7 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting /// /// Selects which members of to format. /// - /// The of the object being formatted. + /// The of the object being formatted. /// The members of that will be included when formatting this object. /// The default is all non-private members. protected virtual MemberInfo[] GetMembers(Type type) @@ -115,9 +115,9 @@ private static void WriteMemberValues(object obj, MemberInfo[] members, Formatte /// /// Selects the name to display for . /// - /// The of the object being formatted. + /// The of the object being formatted. /// The name to be displayed for . - /// The default is . + /// The default is . protected virtual string TypeDisplayName(Type type) => type.FullName; private static void WriteMemberValueTextFor(object value, MemberInfo member, FormattedObjectGraph formattedGraph, diff --git a/Src/FluentAssertions/Formatting/DictionaryValueFormatter.cs b/Src/FluentAssertions/Formatting/DictionaryValueFormatter.cs index ffaf470a52..c40fbce002 100644 --- a/Src/FluentAssertions/Formatting/DictionaryValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DictionaryValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; diff --git a/Src/FluentAssertions/Formatting/DoubleValueFormatter.cs b/Src/FluentAssertions/Formatting/DoubleValueFormatter.cs index 0166c44651..6fa1890fda 100644 --- a/Src/FluentAssertions/Formatting/DoubleValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DoubleValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/EnumValueFormatter.cs b/Src/FluentAssertions/Formatting/EnumValueFormatter.cs index 516e3f2a88..09ccbd1e50 100644 --- a/Src/FluentAssertions/Formatting/EnumValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/EnumValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/EnumerableExtensions.cs b/Src/FluentAssertions/Formatting/EnumerableExtensions.cs index 621ebebd62..b6feed56c8 100644 --- a/Src/FluentAssertions/Formatting/EnumerableExtensions.cs +++ b/Src/FluentAssertions/Formatting/EnumerableExtensions.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs b/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs index fe15e11746..42c98a2ad3 100644 --- a/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; diff --git a/Src/FluentAssertions/Formatting/ExceptionValueFormatter.cs b/Src/FluentAssertions/Formatting/ExceptionValueFormatter.cs index a4975af707..971224a535 100644 --- a/Src/FluentAssertions/Formatting/ExceptionValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/ExceptionValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/ExpressionValueFormatter.cs b/Src/FluentAssertions/Formatting/ExpressionValueFormatter.cs index c22dbd46bc..c76ec824aa 100644 --- a/Src/FluentAssertions/Formatting/ExpressionValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/ExpressionValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq.Expressions; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/FormatChild.cs b/Src/FluentAssertions/Formatting/FormatChild.cs index d182debfa4..bf5de58fa1 100644 --- a/Src/FluentAssertions/Formatting/FormatChild.cs +++ b/Src/FluentAssertions/Formatting/FormatChild.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.Formatting; +namespace FluentAssertions.Formatting; /// /// Represents a method that can be used to format child values from inside an . diff --git a/Src/FluentAssertions/Formatting/Formatter.cs b/Src/FluentAssertions/Formatting/Formatter.cs index a2c4dbc150..811a2fbf6f 100644 --- a/Src/FluentAssertions/Formatting/Formatter.cs +++ b/Src/FluentAssertions/Formatting/Formatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -168,7 +168,7 @@ private static void Format(object value, FormattedObjectGraph output, Formatting } /// - /// Removes a custom formatter that was previously added through . + /// Removes a custom formatter that was previously added through . /// /// /// This method is not thread-safe and should not be invoked from within a unit test. diff --git a/Src/FluentAssertions/Formatting/FormattingContext.cs b/Src/FluentAssertions/Formatting/FormattingContext.cs index 76dee54f8f..f444b665fd 100644 --- a/Src/FluentAssertions/Formatting/FormattingContext.cs +++ b/Src/FluentAssertions/Formatting/FormattingContext.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.Formatting; +namespace FluentAssertions.Formatting; /// /// Provides information about the current formatting action. diff --git a/Src/FluentAssertions/Formatting/FormattingOptions.cs b/Src/FluentAssertions/Formatting/FormattingOptions.cs index 8cb16870f8..8327b13e7b 100644 --- a/Src/FluentAssertions/Formatting/FormattingOptions.cs +++ b/Src/FluentAssertions/Formatting/FormattingOptions.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FluentAssertions.Execution; namespace FluentAssertions.Formatting; @@ -43,7 +43,7 @@ public class FormattingOptions public int MaxLines { get; set; } = 100; /// - /// Removes a scoped formatter that was previously added through . + /// Removes a scoped formatter that was previously added through . /// /// A custom implementation of public void RemoveFormatter(IValueFormatter formatter) diff --git a/Src/FluentAssertions/Formatting/IValueFormatter.cs b/Src/FluentAssertions/Formatting/IValueFormatter.cs index 0c29d895f5..d230fe3e80 100644 --- a/Src/FluentAssertions/Formatting/IValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/IValueFormatter.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.Formatting; +namespace FluentAssertions.Formatting; /// /// Represents a strategy for formatting an arbitrary value into a human-readable string representation. @@ -35,7 +35,7 @@ public interface IValueFormatter /// DO NOT CALL directly, but use /// instead. This will ensure cyclic dependencies are properly detected. /// Also, the may throw - /// an that must be ignored by implementations of this interface. + /// a that must be ignored by implementations of this interface. /// void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild); } diff --git a/Src/FluentAssertions/Formatting/Int16ValueFormatter.cs b/Src/FluentAssertions/Formatting/Int16ValueFormatter.cs index 8f8e29de9d..07598bc245 100644 --- a/Src/FluentAssertions/Formatting/Int16ValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/Int16ValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/Int32ValueFormatter.cs b/Src/FluentAssertions/Formatting/Int32ValueFormatter.cs index 97693fc410..240723feb1 100644 --- a/Src/FluentAssertions/Formatting/Int32ValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/Int32ValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/Int64ValueFormatter.cs b/Src/FluentAssertions/Formatting/Int64ValueFormatter.cs index bda4695f61..f65a1545bf 100644 --- a/Src/FluentAssertions/Formatting/Int64ValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/Int64ValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs b/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs index f6d5b5b260..195750a9a2 100644 --- a/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs +++ b/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs b/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs index 02bdca8483..e549b6cd14 100644 --- a/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs +++ b/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Linq; diff --git a/Src/FluentAssertions/Formatting/NullValueFormatter.cs b/Src/FluentAssertions/Formatting/NullValueFormatter.cs index 62dd35c090..958d479179 100644 --- a/Src/FluentAssertions/Formatting/NullValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/NullValueFormatter.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.Formatting; +namespace FluentAssertions.Formatting; public class NullValueFormatter : IValueFormatter { diff --git a/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs b/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs index c071948942..cbe85985ac 100644 --- a/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -42,7 +42,7 @@ private static Expression ReduceConstantSubExpressions(Expression expression) { return new ConstantSubExpressionReductionVisitor().Visit(expression); } - catch (InvalidOperationException) + catch (Exception e) when (e is InvalidOperationException or NotSupportedException) { // Fallback if we make an invalid rewrite of the expression. return expression; diff --git a/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs b/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs index 4ec7de86e0..da639e0a7f 100644 --- a/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs +++ b/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/SByteValueFormatter.cs b/Src/FluentAssertions/Formatting/SByteValueFormatter.cs index 4d54c84475..3104d9cafb 100644 --- a/Src/FluentAssertions/Formatting/SByteValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/SByteValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/SingleValueFormatter.cs b/Src/FluentAssertions/Formatting/SingleValueFormatter.cs index b018d3e159..74445d8310 100644 --- a/Src/FluentAssertions/Formatting/SingleValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/SingleValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/StringValueFormatter.cs b/Src/FluentAssertions/Formatting/StringValueFormatter.cs index fdf30f7b52..4c6e0b1a7b 100644 --- a/Src/FluentAssertions/Formatting/StringValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/StringValueFormatter.cs @@ -1,4 +1,4 @@ -namespace FluentAssertions.Formatting; +namespace FluentAssertions.Formatting; public class StringValueFormatter : IValueFormatter { diff --git a/Src/FluentAssertions/Formatting/TaskFormatter.cs b/Src/FluentAssertions/Formatting/TaskFormatter.cs index 993597ad2c..4d6a15957c 100644 --- a/Src/FluentAssertions/Formatting/TaskFormatter.cs +++ b/Src/FluentAssertions/Formatting/TaskFormatter.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/TimeSpanValueFormatter.cs b/Src/FluentAssertions/Formatting/TimeSpanValueFormatter.cs index d03cf8992c..c81b89f9af 100644 --- a/Src/FluentAssertions/Formatting/TimeSpanValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/TimeSpanValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; diff --git a/Src/FluentAssertions/Formatting/UInt16ValueFormatter.cs b/Src/FluentAssertions/Formatting/UInt16ValueFormatter.cs index 780dea3b67..5d93e9e116 100644 --- a/Src/FluentAssertions/Formatting/UInt16ValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/UInt16ValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/UInt32ValueFormatter.cs b/Src/FluentAssertions/Formatting/UInt32ValueFormatter.cs index b8e1a8d299..b67a1e1511 100644 --- a/Src/FluentAssertions/Formatting/UInt32ValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/UInt32ValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/UInt64ValueFormatter.cs b/Src/FluentAssertions/Formatting/UInt64ValueFormatter.cs index 80ecb63fc4..72ecbb3fae 100644 --- a/Src/FluentAssertions/Formatting/UInt64ValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/UInt64ValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/XDocumentValueFormatter.cs b/Src/FluentAssertions/Formatting/XDocumentValueFormatter.cs index 195ca000bb..278091b576 100644 --- a/Src/FluentAssertions/Formatting/XDocumentValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/XDocumentValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Xml.Linq; +using System.Xml.Linq; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/Formatting/XElementValueFormatter.cs b/Src/FluentAssertions/Formatting/XElementValueFormatter.cs index 3d34495f38..4fd4c5e450 100644 --- a/Src/FluentAssertions/Formatting/XElementValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/XElementValueFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Xml.Linq; using FluentAssertions.Common; diff --git a/Src/FluentAssertions/Formatting/XmlReaderValueFormatter.cs b/Src/FluentAssertions/Formatting/XmlReaderValueFormatter.cs index 324eb60225..ac93b18506 100644 --- a/Src/FluentAssertions/Formatting/XmlReaderValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/XmlReaderValueFormatter.cs @@ -1,4 +1,4 @@ -using System.Xml; +using System.Xml; namespace FluentAssertions.Formatting; diff --git a/Src/FluentAssertions/JsonAssertionExtensions.cs b/Src/FluentAssertions/JsonAssertionExtensions.cs index 6fc01d88c2..d05748ea4a 100644 --- a/Src/FluentAssertions/JsonAssertionExtensions.cs +++ b/Src/FluentAssertions/JsonAssertionExtensions.cs @@ -1,4 +1,4 @@ -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER using System.Diagnostics; using System.Linq; diff --git a/Src/FluentAssertions/LessThan.cs b/Src/FluentAssertions/LessThan.cs index dcfa40d876..7652d07baf 100644 --- a/Src/FluentAssertions/LessThan.cs +++ b/Src/FluentAssertions/LessThan.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions; +namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public static class LessThan { public static OccurrenceConstraint Twice() => new LessThanTimesConstraint(2); diff --git a/Src/FluentAssertions/License.cs b/Src/FluentAssertions/License.cs index df3a6b8afb..e53fa10225 100644 --- a/Src/FluentAssertions/License.cs +++ b/Src/FluentAssertions/License.cs @@ -3,6 +3,7 @@ namespace FluentAssertions; /// /// Provides access to the licensing state of Fluent Assertions /// +[System.Diagnostics.StackTraceHidden] public static class License { /// diff --git a/Src/FluentAssertions/MoreThan.cs b/Src/FluentAssertions/MoreThan.cs index 0c6ae3572a..14265b1e4f 100644 --- a/Src/FluentAssertions/MoreThan.cs +++ b/Src/FluentAssertions/MoreThan.cs @@ -1,5 +1,6 @@ -namespace FluentAssertions; +namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public static class MoreThan { public static OccurrenceConstraint Once() => new MoreThanTimesConstraint(1); diff --git a/Src/FluentAssertions/Numeric/ByteAssertions.cs b/Src/FluentAssertions/Numeric/ByteAssertions.cs index ccd7399bcf..ec4c529c7b 100644 --- a/Src/FluentAssertions/Numeric/ByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/ByteAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/DecimalAssertions.cs b/Src/FluentAssertions/Numeric/DecimalAssertions.cs index aa31679f46..56a0b33c2e 100644 --- a/Src/FluentAssertions/Numeric/DecimalAssertions.cs +++ b/Src/FluentAssertions/Numeric/DecimalAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/DoubleAssertions.cs b/Src/FluentAssertions/Numeric/DoubleAssertions.cs index 0596191408..f9f82489cb 100644 --- a/Src/FluentAssertions/Numeric/DoubleAssertions.cs +++ b/Src/FluentAssertions/Numeric/DoubleAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/Int16Assertions.cs b/Src/FluentAssertions/Numeric/Int16Assertions.cs index 7d7b9c2102..63d3a27564 100644 --- a/Src/FluentAssertions/Numeric/Int16Assertions.cs +++ b/Src/FluentAssertions/Numeric/Int16Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/Int32Assertions.cs b/Src/FluentAssertions/Numeric/Int32Assertions.cs index cfcb25f472..e0f6d492cc 100644 --- a/Src/FluentAssertions/Numeric/Int32Assertions.cs +++ b/Src/FluentAssertions/Numeric/Int32Assertions.cs @@ -1,11 +1,11 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; namespace FluentAssertions.Numeric; /// -/// Contains a number of methods to assert that a is in the expected state. +/// Contains a number of methods to assert that an is in the expected state. /// [DebuggerNonUserCode] internal class Int32Assertions : NumericAssertions diff --git a/Src/FluentAssertions/Numeric/Int64Assertions.cs b/Src/FluentAssertions/Numeric/Int64Assertions.cs index c733dd294e..ee482615f9 100644 --- a/Src/FluentAssertions/Numeric/Int64Assertions.cs +++ b/Src/FluentAssertions/Numeric/Int64Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableByteAssertions.cs b/Src/FluentAssertions/Numeric/NullableByteAssertions.cs index 9fa6a4c4f0..da4ac20f00 100644 --- a/Src/FluentAssertions/Numeric/NullableByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableByteAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs b/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs index d5b27880c6..c114cef559 100644 --- a/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs b/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs index 802d0c864d..c73ec63f64 100644 --- a/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs b/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs index 7b2400568f..cf31981cb1 100644 --- a/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs b/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs index 1e5e04d386..f78d5dbf3b 100644 --- a/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs b/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs index e83e756404..2a4a4110d3 100644 --- a/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs b/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs index 3a82df8969..2cec4f7c95 100644 --- a/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs b/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs index 0df417d5e2..416ccc64c2 100644 --- a/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs b/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs index 69b94a8086..b650d76a99 100644 --- a/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs b/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs index 6c36e3b703..c8f19561ca 100644 --- a/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs b/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs index 224f4ad286..ba2454c03a 100644 --- a/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs +++ b/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; @@ -10,6 +11,7 @@ namespace FluentAssertions.Numeric; #pragma warning disable CS0659, S1206 // Ignore not overriding Object.GetHashCode() #pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals +[DebuggerNonUserCode] public abstract class NumericAssertionsBase where T : struct, IComparable where TAssertions : NumericAssertionsBase diff --git a/Src/FluentAssertions/Numeric/SByteAssertions.cs b/Src/FluentAssertions/Numeric/SByteAssertions.cs index 7ac217e8a3..726a98eeef 100644 --- a/Src/FluentAssertions/Numeric/SByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/SByteAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; @@ -21,3 +21,4 @@ private protected override string CalculateDifferenceForFailureMessage(sbyte sub return difference != 0 ? difference.ToString(CultureInfo.InvariantCulture) : null; } } + diff --git a/Src/FluentAssertions/Numeric/SingleAssertions.cs b/Src/FluentAssertions/Numeric/SingleAssertions.cs index 2276b01334..10a18ca2ce 100644 --- a/Src/FluentAssertions/Numeric/SingleAssertions.cs +++ b/Src/FluentAssertions/Numeric/SingleAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; @@ -23,3 +23,4 @@ private protected override string CalculateDifferenceForFailureMessage(float sub return difference != 0 ? difference.ToString("R", CultureInfo.InvariantCulture) : null; } } + diff --git a/Src/FluentAssertions/Numeric/UInt16Assertions.cs b/Src/FluentAssertions/Numeric/UInt16Assertions.cs index c1672f1482..39791add6a 100644 --- a/Src/FluentAssertions/Numeric/UInt16Assertions.cs +++ b/Src/FluentAssertions/Numeric/UInt16Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/UInt32Assertions.cs b/Src/FluentAssertions/Numeric/UInt32Assertions.cs index 49bcaad48f..6579ffc493 100644 --- a/Src/FluentAssertions/Numeric/UInt32Assertions.cs +++ b/Src/FluentAssertions/Numeric/UInt32Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Numeric/UInt64Assertions.cs b/Src/FluentAssertions/Numeric/UInt64Assertions.cs index 3060a1c012..f94d214a57 100644 --- a/Src/FluentAssertions/Numeric/UInt64Assertions.cs +++ b/Src/FluentAssertions/Numeric/UInt64Assertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/NumericAssertionsExtensions.cs b/Src/FluentAssertions/NumericAssertionsExtensions.cs index 01e48fe96b..e52f282f88 100644 --- a/Src/FluentAssertions/NumericAssertionsExtensions.cs +++ b/Src/FluentAssertions/NumericAssertionsExtensions.cs @@ -1796,3 +1796,4 @@ private static long GetMaxValue(long value, ulong delta) return maxValue; } } + diff --git a/Src/FluentAssertions/ObjectAssertionsExtensions.cs b/Src/FluentAssertions/ObjectAssertionsExtensions.cs index 238cd317e2..bceadd6257 100644 --- a/Src/FluentAssertions/ObjectAssertionsExtensions.cs +++ b/Src/FluentAssertions/ObjectAssertionsExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Runtime.Serialization; using System.Xml.Serialization; @@ -199,3 +200,4 @@ private static object CreateCloneUsingXmlSerializer(object subject) return serializer.Deserialize(stream); } } + diff --git a/Src/FluentAssertions/OccurrenceConstraint.cs b/Src/FluentAssertions/OccurrenceConstraint.cs index 347df0b750..cfde7aa192 100644 --- a/Src/FluentAssertions/OccurrenceConstraint.cs +++ b/Src/FluentAssertions/OccurrenceConstraint.cs @@ -1,8 +1,9 @@ -using System; +using System; using FluentAssertions.Common; namespace FluentAssertions; +[System.Diagnostics.StackTraceHidden] public abstract class OccurrenceConstraint { protected OccurrenceConstraint(int expectedCount) @@ -26,3 +27,4 @@ internal void RegisterContextData(Action register) register("expectedOccurrence", $"{Mode} {ExpectedCount.Times()}"); } } + diff --git a/Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs b/Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs index d2d901b678..bda9a34fa8 100644 --- a/Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs +++ b/Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs @@ -1,4 +1,4 @@ -#if NET47 || NETSTANDARD2_0 || NETSTANDARD2_1 +#if NET47 || NETSTANDARD2_0 || NETSTANDARD2_1 using System.Collections.Generic; @@ -13,8 +13,11 @@ namespace System.Text; /// internal static class StringBuilderExtensions { - public static StringBuilder AppendLine(this StringBuilder stringBuilder, IFormatProvider _, string value) => - stringBuilder.AppendLine(value); + public static StringBuilder Append(this StringBuilder stringBuilder, IFormatProvider provider, FormattableString formattable) => + stringBuilder.Append(string.Create(provider, formattable)); + + public static StringBuilder AppendLine(this StringBuilder stringBuilder, IFormatProvider provider, FormattableString formattable) => + stringBuilder.AppendLine(string.Create(provider, formattable)); #if NET47 || NETSTANDARD2_0 public static StringBuilder AppendJoin(this StringBuilder stringBuilder, string separator, IEnumerable values) => diff --git a/Src/FluentAssertions/Polyfill/StringExtensions.cs b/Src/FluentAssertions/Polyfill/StringExtensions.cs new file mode 100644 index 0000000000..6f3948c6b3 --- /dev/null +++ b/Src/FluentAssertions/Polyfill/StringExtensions.cs @@ -0,0 +1,15 @@ +#if !NET6_0_OR_GREATER +using System; +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace System; + +internal static class StringExtensions +{ + extension(string) + { + public static string Create(IFormatProvider _, FormattableString formattable) => FormattableString.Invariant(formattable); + } +} +#endif diff --git a/Src/FluentAssertions/Polyfill/SystemExtensions.cs b/Src/FluentAssertions/Polyfill/SystemExtensions.cs index a725bd4f3c..0ac331a182 100644 --- a/Src/FluentAssertions/Polyfill/SystemExtensions.cs +++ b/Src/FluentAssertions/Polyfill/SystemExtensions.cs @@ -1,4 +1,4 @@ -#if NET47 || NETSTANDARD2_0 +#if NET47 || NETSTANDARD2_0 // ReSharper disable once CheckNamespace namespace System; diff --git a/Src/FluentAssertions/Primitives/BooleanAssertions.cs b/Src/FluentAssertions/Primitives/BooleanAssertions.cs index 45fa01878c..9ed804e6e7 100644 --- a/Src/FluentAssertions/Primitives/BooleanAssertions.cs +++ b/Src/FluentAssertions/Primitives/BooleanAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs b/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs index c5e57b5d73..ac36975f37 100644 --- a/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs @@ -1,4 +1,4 @@ -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER using System; using System.Collections.Generic; diff --git a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs index b1f7ffa01a..551471e40a 100644 --- a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -12,7 +12,7 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that a is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -30,7 +30,7 @@ public DateTimeAssertions(DateTime? value, AssertionChain assertionChain) /// Contains a number of methods to assert that a is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -705,6 +705,58 @@ public AndConstraint NotHaveSecond(int unexpected, return new AndConstraint((TAssertions)this); } + /// + /// Asserts that the current has the millisecond. + /// + /// The expected milliseconds of the current value. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint HaveMillisecond(int expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected the milliseconds part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Millisecond == expected) + .FailWith(", but found {0}.", Subject.Value.Millisecond)); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the current does not have the millisecond. + /// + /// The millisecond that should not be in the current value. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint NotHaveMillisecond(int unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Did not expect the milliseconds part of {context:the time} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Millisecond != unexpected) + .FailWith(", but it was.")); + + return new AndConstraint((TAssertions)this); + } + /// /// Returns a object that can be used to assert that the current /// exceeds the specified compared to another . diff --git a/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs index 83d27c8ad0..b17e9b3d3d 100644 --- a/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -12,7 +12,7 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that a is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -31,7 +31,7 @@ public DateTimeOffsetAssertions(DateTimeOffset? value, AssertionChain assertionC /// Contains a number of methods to assert that a is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -263,8 +263,7 @@ public AndConstraint NotBeExactly(DateTimeOffset? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { assertionChain - .ForCondition(!((Subject == null && unexpected == null) || - (Subject != null && unexpected != null && Subject.Value.EqualsExact(unexpected.Value)))) + .ForCondition(!(Subject.HasValue == unexpected.HasValue && Subject.GetValueOrDefault().EqualsExact(unexpected.GetValueOrDefault()))) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:the date and time} to be exactly {0}{reason}, but it was.", unexpected); @@ -837,6 +836,59 @@ public AndConstraint NotHaveSecond(int unexpected, return new AndConstraint((TAssertions)this); } + /// + /// Asserts that the current has the millisecond. + /// + /// The expected milliseconds of the current value. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint HaveMillisecond(int expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected the milliseconds part of {context:the time} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Millisecond == expected) + .FailWith("but it was {0}.", Subject.Value.Millisecond)); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the current does not have the millisecond. + /// + /// The millisecond that should not be in the current value. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint NotHaveMillisecond(int unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Did not expect the milliseconds part of {context:the time} to be {0}{reason}, ", unexpected, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Millisecond != unexpected) + .FailWith("but it was.")); + + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that the current has the offset. /// diff --git a/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs index 964b73c9db..b75bdca8bf 100644 --- a/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -138,3 +138,4 @@ private static string PositionRelativeToTarget(DateTimeOffset actual, DateTimeOf public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?"); } + diff --git a/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs index 6192cfa16b..400215e9a0 100644 --- a/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -12,8 +12,8 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that two objects differ in the expected way. /// /// -/// You can use the and -/// for a more fluent +/// You can use the and +/// for a more fluent /// way of specifying a or a . /// [DebuggerNonUserCode] @@ -140,3 +140,4 @@ private static string PositionRelativeToTarget(DateTime actual, DateTime target) public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?"); } + diff --git a/Src/FluentAssertions/Primitives/EnumAssertions.cs b/Src/FluentAssertions/Primitives/EnumAssertions.cs index b740b3c7d3..cc88b41652 100644 --- a/Src/FluentAssertions/Primitives/EnumAssertions.cs +++ b/Src/FluentAssertions/Primitives/EnumAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -460,3 +460,4 @@ private static string GetName(T @enum) public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); } + diff --git a/Src/FluentAssertions/Primitives/GuidAssertions.cs b/Src/FluentAssertions/Primitives/GuidAssertions.cs index d30fe808df..37faccdc71 100644 --- a/Src/FluentAssertions/Primitives/GuidAssertions.cs +++ b/Src/FluentAssertions/Primitives/GuidAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; diff --git a/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs b/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs index c20c11af84..4cfdcbd70b 100644 --- a/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; @@ -160,7 +160,7 @@ public AndConstraint NotBe(bool? unexpected, public AndConstraint NotBeFalse([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { assertionChain - .ForCondition(Subject is not false) + .ForCondition(Subject != false) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable boolean} not to be {0}{reason}, but found {1}.", false, Subject); @@ -180,10 +180,11 @@ public AndConstraint NotBeFalse([StringSyntax("CompositeFormat")] s public AndConstraint NotBeTrue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { assertionChain - .ForCondition(Subject is not true) + .ForCondition(Subject != true) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable boolean} not to be {0}{reason}, but found {1}.", true, Subject); return new AndConstraint((TAssertions)this); } } + diff --git a/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs index ecdde62a34..41f0bdcf5a 100644 --- a/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs @@ -106,3 +106,4 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin } #endif + diff --git a/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs index d7b8f1bc13..d4a13f5c81 100644 --- a/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; @@ -109,3 +109,4 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin return NotHaveValue(because, becauseArgs); } } + diff --git a/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs index 3508fb2f36..e1b4160635 100644 --- a/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; @@ -9,7 +9,7 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that a nullable is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -25,7 +25,7 @@ public NullableDateTimeOffsetAssertions(DateTimeOffset? expected, AssertionChain /// Contains a number of methods to assert that a nullable is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -112,3 +112,4 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin return NotHaveValue(because, becauseArgs); } } + diff --git a/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs b/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs index 7096c1ab53..f822f58b40 100644 --- a/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; @@ -101,3 +101,4 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin return NotHaveValue(because, becauseArgs); } } + diff --git a/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs b/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs index 1eea1e758b..e08a85eae9 100644 --- a/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; @@ -123,3 +123,4 @@ public AndConstraint Be(Guid? expected, [StringSyntax("CompositeFor return new AndConstraint((TAssertions)this); } } + diff --git a/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs b/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs index 03ffb3d9bd..cc339a6643 100644 --- a/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; @@ -9,7 +9,7 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that a nullable is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -25,7 +25,7 @@ public NullableSimpleTimeSpanAssertions(TimeSpan? value, AssertionChain assertio /// Contains a number of methods to assert that a nullable is in the expected state. /// /// -/// You can use the +/// You can use the /// for a more fluent way of specifying a . /// [DebuggerNonUserCode] @@ -132,3 +132,4 @@ public AndConstraint Be(TimeSpan? expected, return new AndConstraint((TAssertions)this); } } + diff --git a/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs b/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs index 8e6ede5140..0aee3613a2 100644 --- a/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs @@ -106,3 +106,4 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin } #endif + diff --git a/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs b/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs index 634feee5a7..3d974edade 100644 --- a/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs +++ b/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; @@ -249,7 +249,7 @@ public AndConstraint BeCloseTo(TimeSpan nearbyTime, TimeSpan precis TimeSpan maximumValue = nearbyTime + precision; assertionChain - .ForCondition(Subject >= minimumValue && Subject.Value <= maximumValue) + .ForCondition(Subject >= minimumValue && Subject <= maximumValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be within {0} from {1}{reason}, but found {2}.", precision, @@ -302,3 +302,4 @@ public AndConstraint NotBeCloseTo(TimeSpan distantTime, TimeSpan pr public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); } + diff --git a/Src/FluentAssertions/Primitives/StringAssertions.cs b/Src/FluentAssertions/Primitives/StringAssertions.cs index 06e0cb2ffc..540d75483a 100644 --- a/Src/FluentAssertions/Primitives/StringAssertions.cs +++ b/Src/FluentAssertions/Primitives/StringAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -156,7 +156,10 @@ public AndConstraint BeEquivalentTo(string expected, new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be equivalent to"), because, becauseArgs); - var subject = ApplyStringSettings(Subject, options); + string subject = options.TreatNullStringsAsEmpty ? Subject ?? "" : Subject; + expected = options.TreatNullStringsAsEmpty ? expected ?? "" : expected; + + subject = ApplyStringSettings(subject, options); expected = ApplyStringSettings(expected, options); expectation.Validate(subject, expected); diff --git a/Src/FluentAssertions/Primitives/StringContainsStrategy.cs b/Src/FluentAssertions/Primitives/StringContainsStrategy.cs index 6411133327..c9672aae40 100644 --- a/Src/FluentAssertions/Primitives/StringContainsStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringContainsStrategy.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; + using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringContainsStrategy : IStringComparisonStrategy { private readonly IEqualityComparer comparer; @@ -34,3 +36,4 @@ public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, s } } } + diff --git a/Src/FluentAssertions/Primitives/StringEndStrategy.cs b/Src/FluentAssertions/Primitives/StringEndStrategy.cs index 3a2d0044e0..bab06cbcdf 100644 --- a/Src/FluentAssertions/Primitives/StringEndStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringEndStrategy.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; + using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringEndStrategy : IStringComparisonStrategy { private readonly IEqualityComparer comparer; @@ -49,3 +51,4 @@ public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, s private string ExpectationDescription => $"Expected {{context:string}} to {predicateDescription} {{0}}{{reason}}"; } + diff --git a/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs b/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs index 53ccb28ef0..e8b693032a 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringEqualityStrategy : IStringComparisonStrategy { private readonly IEqualityComparer comparer; @@ -23,7 +24,7 @@ public StringEqualityStrategy(IEqualityComparer comparer, string predica /// Gets a value indicating whether the differences between string properties should /// include the full values of the subject and expectation instead of just the fragment that differs. /// - public bool IncludeFullDetails { get; set; } = true; + public bool IncludeFullDetails { get; init; } = true; public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) { @@ -164,17 +165,17 @@ private string GetMismatchSegmentForLongStrings(string subject, string expected, if (IncludeFullDetails && wasTruncated) { - sb.AppendFormat(CultureInfo.InvariantCulture, + sb.Append(CultureInfo.InvariantCulture, $""" Full expectation: - {expected.RenderAsIndentedBlock().AsNonFormattable()}, + {expected.RenderAsIndentedBlock()}, Full subject: - {subject.RenderAsIndentedBlock().AsNonFormattable()} + {subject.RenderAsIndentedBlock()} """); } @@ -204,8 +205,8 @@ private static bool AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(Stri stringBuilder.Append(text .Substring(indexOfStartingPhrase, subjectLength) - .Replace("\r", "\\r", StringComparison.OrdinalIgnoreCase) - .Replace("\n", "\\n", StringComparison.OrdinalIgnoreCase)); + .Replace("\r", "\\r", StringComparison.Ordinal) + .Replace("\n", "\\n", StringComparison.Ordinal)); bool wasTruncated = false; diff --git a/Src/FluentAssertions/Primitives/StringStartStrategy.cs b/Src/FluentAssertions/Primitives/StringStartStrategy.cs index 4738d90355..2d225e11d8 100644 --- a/Src/FluentAssertions/Primitives/StringStartStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringStartStrategy.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; + using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringStartStrategy : IStringComparisonStrategy { private readonly IEqualityComparer comparer; @@ -49,3 +51,4 @@ public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, s private string ExpectationDescription => $"Expected {{context:string}} to {predicateDescription} "; } + diff --git a/Src/FluentAssertions/Primitives/StringValidator.cs b/Src/FluentAssertions/Primitives/StringValidator.cs index c98ff29fda..401cf711c5 100644 --- a/Src/FluentAssertions/Primitives/StringValidator.cs +++ b/Src/FluentAssertions/Primitives/StringValidator.cs @@ -3,6 +3,7 @@ namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringValidator { private readonly IStringComparisonStrategy comparisonStrategy; diff --git a/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs b/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs index cabd1a6e37..849c0faeff 100644 --- a/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs +++ b/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs @@ -4,6 +4,7 @@ namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringValidatorSupportingNull { private readonly IStringComparisonStrategy comparisonStrategy; @@ -27,3 +28,4 @@ public void Validate(string subject, string expected) comparisonStrategy.AssertForEquality(assertionChain, subject, expected); } } + diff --git a/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs b/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs index af35e3de3c..33ddcccdf4 100644 --- a/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs @@ -6,6 +6,7 @@ namespace FluentAssertions.Primitives; +[System.Diagnostics.StackTraceHidden] internal class StringWildcardMatchingStrategy : IStringComparisonStrategy { public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) @@ -39,7 +40,7 @@ public void AssertForEquality(AssertionChain assertionChain, string subject, str else { assertionChain.FailWith($"{ExpectationDescription} {{0}}{{reason}}, but {{1}} {OutcomeDescription}.", expected, - subject); + subject); } } @@ -67,10 +68,10 @@ private bool IsMatch(string subject, string expected) private static string ConvertWildcardToRegEx(string wildcardExpression) { return "^" - + Regex.Escape(wildcardExpression) - .Replace("\\*", ".*", StringComparison.Ordinal) - .Replace("\\?", ".", StringComparison.Ordinal) - + "$"; + + Regex.Escape(wildcardExpression) + .Replace("\\*", ".*", StringComparison.Ordinal) + .Replace("\\?", ".", StringComparison.Ordinal) + + "$"; } private static bool IsLongOrMultiline(string message) diff --git a/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs b/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs index ed92a9c0cb..1796a06c0f 100644 --- a/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs @@ -1,4 +1,4 @@ -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER using System; using System.Collections.Generic; diff --git a/Src/FluentAssertions/Primitives/TimeSpanPredicate.cs b/Src/FluentAssertions/Primitives/TimeSpanPredicate.cs index 9f8e68982c..9d60df5fc0 100644 --- a/Src/FluentAssertions/Primitives/TimeSpanPredicate.cs +++ b/Src/FluentAssertions/Primitives/TimeSpanPredicate.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Primitives; /// /// Provides the logic and the display text for a . /// +[System.Diagnostics.StackTraceHidden] internal class TimeSpanPredicate { private readonly Func lambda; @@ -22,3 +23,4 @@ public bool IsMatchedBy(TimeSpan actual, TimeSpan expected) return lambda(actual, expected) && actual >= TimeSpan.Zero; } } + diff --git a/Src/FluentAssertions/Specialized/ActionAssertions.cs b/Src/FluentAssertions/Specialized/ActionAssertions.cs index 94be29f8c4..0bf9c980b3 100644 --- a/Src/FluentAssertions/Specialized/ActionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ActionAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; diff --git a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs index a775860198..f0e651a3dd 100644 --- a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs @@ -368,7 +368,7 @@ private protected static async Task InvokeWithInterceptionAsync(Func< // not "await action". using (CallerIdentifier.OnlyOneFluentAssertionScopeOnCallStack() ? CallerIdentifier.OverrideStackSearchUsingCurrentScope() - : default) + : null) { await action(); } diff --git a/Src/FluentAssertions/Specialized/DelegateAssertions.cs b/Src/FluentAssertions/Specialized/DelegateAssertions.cs index b0586629b8..f5cdf3342f 100644 --- a/Src/FluentAssertions/Specialized/DelegateAssertions.cs +++ b/Src/FluentAssertions/Specialized/DelegateAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; diff --git a/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs b/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs index fe662c0424..dd75daf1cf 100644 --- a/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs +++ b/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs @@ -26,9 +26,11 @@ private protected DelegateAssertionsBase(TDelegate @delegate, IExtractExceptions IClock clock) : base(@delegate, assertionChain) { + Guard.ThrowIfArgumentIsNull(extractor); + Guard.ThrowIfArgumentIsNull(clock); this.assertionChain = assertionChain; - Extractor = extractor ?? throw new ArgumentNullException(nameof(extractor)); - Clock = clock ?? throw new ArgumentNullException(nameof(clock)); + Extractor = extractor; + Clock = clock; } private protected IClock Clock { get; } @@ -51,6 +53,11 @@ protected ExceptionAssertions ThrowInternal( exception?.GetType(), exception)); + AssertionChain.GetOrCreate() + .ReuseOnce() + .AdvanceToNextIdentifier() + .WithCallerPrefix($"{typeof(TException).Name}."); + return new ExceptionAssertions(expectedExceptions, assertionChain); } diff --git a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs index 3ab2fe9a0d..4589f6e430 100644 --- a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/Src/FluentAssertions/Specialized/ExecutionTime.cs b/Src/FluentAssertions/Specialized/ExecutionTime.cs index e3249e242b..608b236df6 100644 --- a/Src/FluentAssertions/Specialized/ExecutionTime.cs +++ b/Src/FluentAssertions/Specialized/ExecutionTime.cs @@ -1,9 +1,11 @@ -using System; +using System; + using System.Threading.Tasks; using FluentAssertions.Common; namespace FluentAssertions.Specialized; +[System.Diagnostics.StackTraceHidden] public class ExecutionTime { private ITimer timer; @@ -114,3 +116,4 @@ protected ExecutionTime(Func action, string actionDescription, StartTimer internal Exception Exception { get; private set; } } + diff --git a/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs b/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs index b87cd19fc3..8b429e026a 100644 --- a/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -10,6 +11,7 @@ namespace FluentAssertions.Specialized; /// /// Provides methods for asserting that the execution time of an satisfies certain conditions. /// +[DebuggerNonUserCode] public class ExecutionTimeAssertions { private readonly ExecutionTime execution; @@ -21,7 +23,8 @@ public class ExecutionTimeAssertions /// The execution on which time must be asserted. public ExecutionTimeAssertions(ExecutionTime executionTime, AssertionChain assertionChain) { - execution = executionTime ?? throw new ArgumentNullException(nameof(executionTime)); + Guard.ThrowIfArgumentIsNull(executionTime); + execution = executionTime; this.assertionChain = assertionChain; } diff --git a/Src/FluentAssertions/Specialized/FunctionAssertions.cs b/Src/FluentAssertions/Specialized/FunctionAssertions.cs index 4a093c1444..3d9f725022 100644 --- a/Src/FluentAssertions/Specialized/FunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/FunctionAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; diff --git a/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs index f5860af3ad..49cf1b4b00 100644 --- a/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -11,6 +12,7 @@ namespace FluentAssertions.Specialized; /// Contains a number of methods to assert that an asynchronous method yields the expected result. /// /// The type returned in the . +[DebuggerNonUserCode] public class GenericAsyncFunctionAssertions : AsyncFunctionAssertions, GenericAsyncFunctionAssertions> { diff --git a/Src/FluentAssertions/Specialized/JsonNodeAssertions.cs b/Src/FluentAssertions/Specialized/JsonNodeAssertions.cs index 48f76b4aa7..8ede596fdf 100644 --- a/Src/FluentAssertions/Specialized/JsonNodeAssertions.cs +++ b/Src/FluentAssertions/Specialized/JsonNodeAssertions.cs @@ -524,3 +524,4 @@ public AndConstraint> NotBeEquivalentTo( } #endif + diff --git a/Src/FluentAssertions/Specialized/JsonValueExtensions.cs b/Src/FluentAssertions/Specialized/JsonValueExtensions.cs index 551c55fba7..6e7ed2ad40 100644 --- a/Src/FluentAssertions/Specialized/JsonValueExtensions.cs +++ b/Src/FluentAssertions/Specialized/JsonValueExtensions.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Specialized; +[System.Diagnostics.StackTraceHidden] internal static class JsonValueExtensions { public static bool IsNumeric(this JsonValue value) @@ -14,3 +15,4 @@ public static bool IsNumeric(this JsonValue value) } #endif + diff --git a/Src/FluentAssertions/Specialized/MemberExecutionTime.cs b/Src/FluentAssertions/Specialized/MemberExecutionTime.cs index 995d18a7c8..4429676f01 100644 --- a/Src/FluentAssertions/Specialized/MemberExecutionTime.cs +++ b/Src/FluentAssertions/Specialized/MemberExecutionTime.cs @@ -1,9 +1,11 @@ -using System; +using System; + using System.Linq.Expressions; using FluentAssertions.Common; namespace FluentAssertions.Specialized; +[System.Diagnostics.StackTraceHidden] public class MemberExecutionTime : ExecutionTime { /// @@ -18,3 +20,4 @@ public MemberExecutionTime(T subject, Expression> action, StartTimer c { } } + diff --git a/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs index f68a8dac85..0af9a329c4 100644 --- a/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ namespace FluentAssertions.Specialized; /// /// Contains a number of methods to assert that an asynchronous method yields the expected result. /// +[DebuggerNonUserCode] public class NonGenericAsyncFunctionAssertions : AsyncFunctionAssertions { private readonly AssertionChain assertionChain; diff --git a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs index 0081053767..d08039e479 100644 --- a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs +++ b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using FluentAssertions.Common; @@ -10,6 +11,7 @@ namespace FluentAssertions.Specialized; #pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals #if NET6_0_OR_GREATER +[DebuggerNonUserCode] public class TaskCompletionSourceAssertions : TaskCompletionSourceAssertionsBase { private readonly AssertionChain assertionChain; @@ -90,8 +92,10 @@ public async Task> NotCompleteWith return new AndConstraint(this); } } + #endif +[DebuggerNonUserCode] public class TaskCompletionSourceAssertions : TaskCompletionSourceAssertionsBase { private readonly AssertionChain assertionChain; diff --git a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertionsBase.cs b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertionsBase.cs index 7cb21a8c39..aabeacef65 100644 --- a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertionsBase.cs +++ b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertionsBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -10,11 +11,13 @@ namespace FluentAssertions.Specialized; /// /// Implements base functionality for assertions on TaskCompletionSource. /// +[DebuggerNonUserCode] public class TaskCompletionSourceAssertionsBase { protected TaskCompletionSourceAssertionsBase(IClock clock) { - Clock = clock ?? throw new ArgumentNullException(nameof(clock)); + Guard.ThrowIfArgumentIsNull(clock); + Clock = clock; } private protected IClock Clock { get; } diff --git a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs index 1073684a4c..2c195dcbe9 100644 --- a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs +++ b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs @@ -1,14 +1,14 @@ -using System.Diagnostics; -using System.IO; -using FluentAssertions.Execution; -#if NET6_0_OR_GREATER || NETSTANDARD2_1 +using System.Diagnostics; +#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif +using System.IO; +using FluentAssertions.Execution; namespace FluentAssertions.Streams; /// -/// Contains a number of methods to assert that an is in the expected state. +/// Contains a number of methods to assert that a is in the expected state. /// /// [DebuggerNonUserCode] @@ -105,3 +105,4 @@ public BufferedStreamAssertions(BufferedStream stream, AssertionChain assertionC protected override string Identifier => "buffered stream"; } + diff --git a/Src/FluentAssertions/Streams/StreamAssertions.cs b/Src/FluentAssertions/Streams/StreamAssertions.cs index 3116721541..4b2813b23b 100644 --- a/Src/FluentAssertions/Streams/StreamAssertions.cs +++ b/Src/FluentAssertions/Streams/StreamAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -8,7 +8,7 @@ namespace FluentAssertions.Streams; /// -/// Contains a number of methods to assert that an is in the expected state. +/// Contains a number of methods to assert that a is in the expected state. /// [DebuggerNonUserCode] public class StreamAssertions : StreamAssertions @@ -22,6 +22,7 @@ public StreamAssertions(Stream stream, AssertionChain assertionChain) /// /// Contains a number of methods to assert that a is in the expected state. /// +[DebuggerNonUserCode] public class StreamAssertions : ReferenceTypeAssertions where TSubject : Stream where TAssertions : StreamAssertions @@ -516,3 +517,4 @@ public AndConstraint NotBeWriteOnly([StringSyntax("CompositeFormat" return new AndConstraint((TAssertions)this); } } + diff --git a/Src/FluentAssertions/TypeEnumerableExtensions.cs b/Src/FluentAssertions/TypeEnumerableExtensions.cs index 444fc1eea3..8e096c01c5 100644 --- a/Src/FluentAssertions/TypeEnumerableExtensions.cs +++ b/Src/FluentAssertions/TypeEnumerableExtensions.cs @@ -134,3 +134,4 @@ public static IEnumerable UnwrapEnumerableTypes(this IEnumerable typ return new TypeSelector(types).UnwrapEnumerableTypes(); } } + diff --git a/Src/FluentAssertions/TypeExtensions.cs b/Src/FluentAssertions/TypeExtensions.cs index f08db1bf69..e7f7e63e8a 100644 --- a/Src/FluentAssertions/TypeExtensions.cs +++ b/Src/FluentAssertions/TypeExtensions.cs @@ -31,7 +31,7 @@ public static TypeSelector Types(this Type type) } /// - /// Returns a type selector for the current . + /// Returns a type selector for the current . /// public static TypeSelector Types(this IEnumerable types) { @@ -78,3 +78,4 @@ public static PropertyInfoSelector Properties(this TypeSelector typeSelector) return new PropertyInfoSelector(typeSelector.ToList()); } } + diff --git a/Src/FluentAssertions/Types/AllTypes.cs b/Src/FluentAssertions/Types/AllTypes.cs index 54a4778131..8bc235fcbb 100644 --- a/Src/FluentAssertions/Types/AllTypes.cs +++ b/Src/FluentAssertions/Types/AllTypes.cs @@ -11,6 +11,7 @@ namespace FluentAssertions.Types; /// .Should()
/// .BeDecoratedWith<SomeAttribute>() /// +[System.Diagnostics.StackTraceHidden] public static class AllTypes { /// @@ -23,3 +24,4 @@ public static TypeSelector From(Assembly assembly) return assembly.Types(); } } + diff --git a/Src/FluentAssertions/Types/AssemblyAssertions.cs b/Src/FluentAssertions/Types/AssemblyAssertions.cs index f0ba014e31..7833377c6c 100644 --- a/Src/FluentAssertions/Types/AssemblyAssertions.cs +++ b/Src/FluentAssertions/Types/AssemblyAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -224,3 +224,4 @@ private static string ToHexString(byte[] bytes) => /// protected override string Identifier => "assembly"; } + diff --git a/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs b/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs index c65c9d8cfd..48399acf80 100644 --- a/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs +++ b/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs @@ -28,3 +28,4 @@ private static string GetDescriptionFor(ConstructorInfo constructorInfo) return $"{constructorInfo.DeclaringType}({GetParameterString(constructorInfo)})"; } } + diff --git a/Src/FluentAssertions/Types/MemberInfoAssertions.cs b/Src/FluentAssertions/Types/MemberInfoAssertions.cs index 3e214fd28c..2082e9218a 100644 --- a/Src/FluentAssertions/Types/MemberInfoAssertions.cs +++ b/Src/FluentAssertions/Types/MemberInfoAssertions.cs @@ -155,3 +155,4 @@ public AndConstraint NotBeDecoratedWith( private protected virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}"; } + diff --git a/Src/FluentAssertions/Types/MethodBaseAssertions.cs b/Src/FluentAssertions/Types/MethodBaseAssertions.cs index 042c34b27e..503c53d604 100644 --- a/Src/FluentAssertions/Types/MethodBaseAssertions.cs +++ b/Src/FluentAssertions/Types/MethodBaseAssertions.cs @@ -120,3 +120,4 @@ internal static string GetParameterString(MethodBase methodBase) return string.Join(", ", parameterTypes.Select(p => p.FullName)); } } + diff --git a/Src/FluentAssertions/Types/MethodInfoAssertions.cs b/Src/FluentAssertions/Types/MethodInfoAssertions.cs index cfd799d9d5..81700e966e 100644 --- a/Src/FluentAssertions/Types/MethodInfoAssertions.cs +++ b/Src/FluentAssertions/Types/MethodInfoAssertions.cs @@ -311,3 +311,4 @@ internal static string GetDescriptionFor(MethodInfo method) protected override string Identifier => "method"; } + diff --git a/Src/FluentAssertions/Types/MethodInfoSelector.cs b/Src/FluentAssertions/Types/MethodInfoSelector.cs index 511309ae7a..f2dc5d2f87 100644 --- a/Src/FluentAssertions/Types/MethodInfoSelector.cs +++ b/Src/FluentAssertions/Types/MethodInfoSelector.cs @@ -11,6 +11,7 @@ namespace FluentAssertions.Types; /// /// Allows for fluent selection of methods of a type through reflection. /// +[System.Diagnostics.StackTraceHidden] public class MethodInfoSelector : IEnumerable { private IEnumerable selectedMethods; @@ -236,7 +237,7 @@ private static bool HasSpecialName(MethodInfo method) /// Returns an enumerator that iterates through the collection. ///
/// - /// A that can be used to iterate through the collection. + /// An that can be used to iterate through the collection. /// /// 1 public IEnumerator GetEnumerator() @@ -248,7 +249,7 @@ public IEnumerator GetEnumerator() /// Returns an enumerator that iterates through a collection. /// /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() @@ -256,3 +257,4 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } + diff --git a/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs b/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs index 71b3c51213..c18cf46840 100644 --- a/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs @@ -348,3 +348,4 @@ private static string GetDescriptionsFor(IEnumerable methods) public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); } + diff --git a/Src/FluentAssertions/Types/PropertyInfoAssertions.cs b/Src/FluentAssertions/Types/PropertyInfoAssertions.cs index 85ada02e77..023da0ac5d 100644 --- a/Src/FluentAssertions/Types/PropertyInfoAssertions.cs +++ b/Src/FluentAssertions/Types/PropertyInfoAssertions.cs @@ -397,3 +397,4 @@ public AndConstraint NotReturn([StringSyntax("C /// protected override string Identifier => "property"; } + diff --git a/Src/FluentAssertions/Types/PropertyInfoSelector.cs b/Src/FluentAssertions/Types/PropertyInfoSelector.cs index db860f3879..c55e7a6f5b 100644 --- a/Src/FluentAssertions/Types/PropertyInfoSelector.cs +++ b/Src/FluentAssertions/Types/PropertyInfoSelector.cs @@ -10,6 +10,7 @@ namespace FluentAssertions.Types; /// /// Allows for fluent selection of properties of a type through reflection. /// +[System.Diagnostics.StackTraceHidden] public class PropertyInfoSelector : IEnumerable { private IEnumerable selectedProperties; @@ -208,7 +209,7 @@ public PropertyInfo[] ToArray() /// Returns an enumerator that iterates through the collection. /// /// - /// A that can be used to iterate through the collection. + /// An that can be used to iterate through the collection. /// /// 1 public IEnumerator GetEnumerator() @@ -220,7 +221,7 @@ public IEnumerator GetEnumerator() /// Returns an enumerator that iterates through a collection. /// /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() @@ -228,3 +229,4 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } + diff --git a/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs b/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs index 4aca174681..75ad12367b 100644 --- a/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs @@ -238,3 +238,4 @@ private static string GetDescriptionsFor(IEnumerable properties) public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); } + diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index 3fc3d8fdac..2a77c46207 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -292,7 +292,7 @@ public AndWhichConstraint BeDecoratedWith - /// Asserts that the current is decorated with, or inherits from a parent class, the specified . + /// Asserts that the current is decorated with, or inherits from a parent class, the specified . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -317,7 +317,7 @@ public AndWhichConstraint BeDecoratedWithOrInherit - /// Asserts that the current is decorated with, or inherits from a parent class, an attribute of type + /// Asserts that the current is decorated with, or inherits from a parent class, an attribute of type /// that matches the specified . /// /// @@ -1916,3 +1916,4 @@ private void AssertThatSubjectIsClass() } } } + diff --git a/Src/FluentAssertions/Types/TypeSelector.cs b/Src/FluentAssertions/Types/TypeSelector.cs index e6d46458d9..e0f5d423bb 100644 --- a/Src/FluentAssertions/Types/TypeSelector.cs +++ b/Src/FluentAssertions/Types/TypeSelector.cs @@ -10,6 +10,7 @@ namespace FluentAssertions.Types; /// /// Allows for fluent filtering a list of types. /// +[System.Diagnostics.StackTraceHidden] public class TypeSelector : IEnumerable { private List types; @@ -32,7 +33,7 @@ public TypeSelector(IEnumerable types) } /// - /// The resulting objects. + /// The resulting objects. /// public Type[] ToArray() { @@ -348,7 +349,7 @@ public TypeSelector UnwrapEnumerableTypes() /// Returns an enumerator that iterates through the collection. /// /// - /// A that can be used to iterate through the collection. + /// An that can be used to iterate through the collection. /// /// 1 public IEnumerator GetEnumerator() @@ -360,7 +361,7 @@ public IEnumerator GetEnumerator() /// Returns an enumerator that iterates through a collection. /// /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() @@ -368,3 +369,4 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } + diff --git a/Src/FluentAssertions/Types/TypeSelectorAssertions.cs b/Src/FluentAssertions/Types/TypeSelectorAssertions.cs index 41711ae15f..76b42fb34e 100644 --- a/Src/FluentAssertions/Types/TypeSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/TypeSelectorAssertions.cs @@ -489,3 +489,4 @@ public override bool Equals(object obj) => throw new NotSupportedException( "Equals is not part of Fluent Assertions. Did you mean BeInNamespace() or BeDecoratedWith() instead?"); } + diff --git a/Src/FluentAssertions/Value.cs b/Src/FluentAssertions/Value.cs index a07827ceff..ee938df6c9 100644 --- a/Src/FluentAssertions/Value.cs +++ b/Src/FluentAssertions/Value.cs @@ -8,6 +8,7 @@ namespace FluentAssertions; /// /// Provides a fluent API for defining inline assertions. /// +[System.Diagnostics.StackTraceHidden] public static class Value { /// @@ -34,3 +35,4 @@ public static IInlineEquivalencyAssertion ThatSatisfies(Action assertion) return new ActionBasedInlineAssertion(assertion); } } + diff --git a/Src/FluentAssertions/Xml/Equivalency/AttributeData.cs b/Src/FluentAssertions/Xml/Equivalency/AttributeData.cs index 57fb715f6f..2bc866f783 100644 --- a/Src/FluentAssertions/Xml/Equivalency/AttributeData.cs +++ b/Src/FluentAssertions/Xml/Equivalency/AttributeData.cs @@ -1,5 +1,6 @@ namespace FluentAssertions.Xml.Equivalency; +[System.Diagnostics.StackTraceHidden] internal class AttributeData { public AttributeData(string namespaceUri, string localName, string value, string prefix) diff --git a/Src/FluentAssertions/Xml/Equivalency/Failure.cs b/Src/FluentAssertions/Xml/Equivalency/Failure.cs index 8468497957..e21b371161 100644 --- a/Src/FluentAssertions/Xml/Equivalency/Failure.cs +++ b/Src/FluentAssertions/Xml/Equivalency/Failure.cs @@ -1,5 +1,6 @@ namespace FluentAssertions.Xml.Equivalency; +[System.Diagnostics.StackTraceHidden] internal class Failure { public Failure(string formatString, params object[] formatParams) diff --git a/Src/FluentAssertions/Xml/Equivalency/Node.cs b/Src/FluentAssertions/Xml/Equivalency/Node.cs index 8e19a40132..3a83ccf03f 100644 --- a/Src/FluentAssertions/Xml/Equivalency/Node.cs +++ b/Src/FluentAssertions/Xml/Equivalency/Node.cs @@ -5,6 +5,7 @@ namespace FluentAssertions.Xml.Equivalency; +[System.Diagnostics.StackTraceHidden] internal sealed class Node { private readonly List children = []; @@ -78,3 +79,4 @@ private Node AddChildNode(string name) return node; } } + diff --git a/Src/FluentAssertions/Xml/Equivalency/XmlIterator.cs b/Src/FluentAssertions/Xml/Equivalency/XmlIterator.cs index 72f665b136..d90544ccba 100644 --- a/Src/FluentAssertions/Xml/Equivalency/XmlIterator.cs +++ b/Src/FluentAssertions/Xml/Equivalency/XmlIterator.cs @@ -3,6 +3,7 @@ namespace FluentAssertions.Xml.Equivalency; +[System.Diagnostics.StackTraceHidden] internal class XmlIterator { private readonly XmlReader reader; @@ -76,3 +77,4 @@ public IList GetAttributes() return attributes; } } + diff --git a/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs b/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs index 79290986d4..7a973fa867 100644 --- a/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs +++ b/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -7,6 +7,7 @@ namespace FluentAssertions.Xml.Equivalency; +[System.Diagnostics.StackTraceHidden] internal class XmlReaderValidator { private readonly AssertionChain assertionChain; diff --git a/Src/FluentAssertions/Xml/XAttributeAssertions.cs b/Src/FluentAssertions/Xml/XAttributeAssertions.cs index 5c63862d5c..15531a9fc4 100644 --- a/Src/FluentAssertions/Xml/XAttributeAssertions.cs +++ b/Src/FluentAssertions/Xml/XAttributeAssertions.cs @@ -104,3 +104,4 @@ public AndConstraint HaveValue(string expected, /// protected override string Identifier => "XML attribute"; } + diff --git a/Src/FluentAssertions/Xml/XDocumentAssertions.cs b/Src/FluentAssertions/Xml/XDocumentAssertions.cs index e3ce0a033d..9bc7de6893 100644 --- a/Src/FluentAssertions/Xml/XDocumentAssertions.cs +++ b/Src/FluentAssertions/Xml/XDocumentAssertions.cs @@ -364,7 +364,7 @@ public AndWhichConstraint> HaveElemen public AndConstraint NotHaveElement(string unexpectedElement, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); return NotHaveElement(XNamespace.None + unexpectedElement, because, becauseArgs); } @@ -385,7 +385,7 @@ public AndConstraint NotHaveElement(string unexpectedElemen public AndConstraint NotHaveElement(XName unexpectedElement, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); assertionChain .BecauseOf(because, becauseArgs) @@ -420,8 +420,8 @@ public AndConstraint NotHaveElement(XName unexpectedElement public AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); - Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + Guard.ThrowIfArgumentIsNull(expectedElement); + Guard.ThrowIfArgumentIsNull(expectedValue); return HaveElementWithValue(XNamespace.None + expectedElement, expectedValue, because, becauseArgs); } @@ -446,8 +446,8 @@ public AndWhichConstraint HaveElementWithValue(st public AndWhichConstraint HaveElementWithValue(XName expectedElement, string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); - Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + Guard.ThrowIfArgumentIsNull(expectedElement); + Guard.ThrowIfArgumentIsNull(expectedValue); IEnumerable xElements = []; @@ -495,8 +495,8 @@ public AndWhichConstraint HaveElementWithValue(XN public AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); - Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); + Guard.ThrowIfArgumentIsNull(unexpectedValue); return NotHaveElementWithValue(XNamespace.None + unexpectedElement, unexpectedValue, because, becauseArgs); } @@ -521,8 +521,8 @@ public AndConstraint NotHaveElementWithValue(string unexpec public AndConstraint NotHaveElementWithValue(XName unexpectedElement, string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); - Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); + Guard.ThrowIfArgumentIsNull(unexpectedValue); assertionChain .BecauseOf(because, becauseArgs) @@ -543,3 +543,4 @@ public AndConstraint NotHaveElementWithValue(XName unexpect /// protected override string Identifier => "XML document"; } + diff --git a/Src/FluentAssertions/Xml/XElementAssertions.cs b/Src/FluentAssertions/Xml/XElementAssertions.cs index 5570c5f2ef..9b7baf9c2a 100644 --- a/Src/FluentAssertions/Xml/XElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XElementAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -367,8 +367,8 @@ public AndConstraint HaveAttributeWithValue(XName expectedNa public AndConstraint NotHaveAttributeWithValue(string unexpectedName, string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNullOrEmpty(unexpectedName, nameof(unexpectedName)); - Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + Guard.ThrowIfArgumentIsNullOrEmpty(unexpectedName); + Guard.ThrowIfArgumentIsNull(unexpectedValue); return NotHaveAttributeWithValue(XNamespace.None + unexpectedName, unexpectedValue, because, becauseArgs); } @@ -390,8 +390,8 @@ public AndConstraint NotHaveAttributeWithValue(string unexpe public AndConstraint NotHaveAttributeWithValue(XName unexpectedName, string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedName, nameof(unexpectedName)); - Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + Guard.ThrowIfArgumentIsNull(unexpectedName); + Guard.ThrowIfArgumentIsNull(unexpectedValue); string unexpectedText = unexpectedName.ToString(); @@ -574,7 +574,7 @@ public AndWhichConstraint> HaveElement public AndConstraint NotHaveElement(string unexpectedElement, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); return NotHaveElement(XNamespace.None + unexpectedElement, because, becauseArgs); } @@ -595,7 +595,7 @@ public AndConstraint NotHaveElement(string unexpectedElement public AndConstraint NotHaveElement(XName unexpectedElement, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); assertionChain .ForCondition(Subject is not null) @@ -636,8 +636,8 @@ public AndConstraint NotHaveElement(XName unexpectedElement, public AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); - Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + Guard.ThrowIfArgumentIsNull(expectedElement); + Guard.ThrowIfArgumentIsNull(expectedValue); return HaveElementWithValue(XNamespace.None + expectedElement, expectedValue, because, becauseArgs); } @@ -662,8 +662,8 @@ public AndWhichConstraint HaveElementWithValue(str public AndWhichConstraint HaveElementWithValue(XName expectedElement, string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); - Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + Guard.ThrowIfArgumentIsNull(expectedElement); + Guard.ThrowIfArgumentIsNull(expectedValue); IEnumerable xElements = []; @@ -713,8 +713,8 @@ public AndWhichConstraint HaveElementWithValue(XNa public AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); - Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); + Guard.ThrowIfArgumentIsNull(unexpectedValue); return NotHaveElementWithValue(XNamespace.None + unexpectedElement, unexpectedValue, because, becauseArgs); } @@ -739,8 +739,8 @@ public AndConstraint NotHaveElementWithValue(string unexpect public AndConstraint NotHaveElementWithValue(XName unexpectedElement, string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); - Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + Guard.ThrowIfArgumentIsNull(unexpectedElement); + Guard.ThrowIfArgumentIsNull(unexpectedValue); assertionChain .WithExpectation("Did not expect {context:subject} to have an element {0} with value {1}{reason}, ", @@ -762,3 +762,4 @@ public AndConstraint NotHaveElementWithValue(XName unexpecte /// protected override string Identifier => "XML element"; } + diff --git a/Src/FluentAssertions/Xml/XmlElementAssertions.cs b/Src/FluentAssertions/Xml/XmlElementAssertions.cs index daeffcd3fd..7fcf904de2 100644 --- a/Src/FluentAssertions/Xml/XmlElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlElementAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Xml; using FluentAssertions.Common; @@ -172,3 +172,4 @@ public AndWhichConstraint HaveElementWithNames protected override string Identifier => "XML element"; } + diff --git a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs index f903b7ec0a..0a210a4148 100644 --- a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Xml; using FluentAssertions.Execution; @@ -89,3 +89,4 @@ public AndConstraint NotBeEquivalentTo(XmlNode unexpected, /// protected override string Identifier => "XML node"; } + diff --git a/Src/FluentAssertions/Xml/XmlNodeFormatter.cs b/Src/FluentAssertions/Xml/XmlNodeFormatter.cs index b416fc12a3..c9cb146506 100644 --- a/Src/FluentAssertions/Xml/XmlNodeFormatter.cs +++ b/Src/FluentAssertions/Xml/XmlNodeFormatter.cs @@ -1,9 +1,10 @@ -using System.Xml; +using System.Xml; using FluentAssertions.Common; using FluentAssertions.Formatting; namespace FluentAssertions.Xml; +[System.Diagnostics.StackTraceHidden] public class XmlNodeFormatter : IValueFormatter { public bool CanHandle(object value) @@ -25,3 +26,4 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting formattedGraph.AddLine(outerXml.EscapePlaceholders()); } } + diff --git a/Src/FluentAssertions/XmlAssertionExtensions.cs b/Src/FluentAssertions/XmlAssertionExtensions.cs index 79da764778..4eea8cea6a 100644 --- a/Src/FluentAssertions/XmlAssertionExtensions.cs +++ b/Src/FluentAssertions/XmlAssertionExtensions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Xml; using FluentAssertions.Execution; @@ -19,3 +19,4 @@ public static XmlElementAssertions Should([NotNull] this XmlElement actualValue) return new XmlElementAssertions(actualValue, AssertionChain.GetOrCreate()); } } + diff --git a/Tests/.editorconfig b/Tests/.editorconfig index 15629ab354..ab90857a06 100644 --- a/Tests/.editorconfig +++ b/Tests/.editorconfig @@ -216,7 +216,7 @@ dotnet_diagnostic.RCS1213.severity = none # Implement IComparable when implementing IComparable. Disabled since we don't want to be so strict in tests. dotnet_diagnostic.RCS1241.severity = none -# Purpose: Use an overload that has a IEqualityComparer or IComparer parameter +# Purpose: Use an overload that has an IEqualityComparer or IComparer parameter # Reason: Not important in tests dotnet_diagnostic.MA0002.severity = none @@ -278,4 +278,4 @@ dotnet_diagnostic.S3237.severity = none dotnet_diagnostic.S3928.severity = none # Remove this unread private field 'field' or refactor the code to use its value -dotnet_diagnostic.S4487.severity = none \ No newline at end of file +dotnet_diagnostic.S4487.severity = none diff --git a/Tests/Approval.Tests/Approval.Tests.csproj b/Tests/Approval.Tests/Approval.Tests.csproj index ac8f84b5c2..112bf8a4d7 100644 --- a/Tests/Approval.Tests/Approval.Tests.csproj +++ b/Tests/Approval.Tests/Approval.Tests.csproj @@ -1,19 +1,20 @@  - net8.0 + net10.0 + Exe - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index 18b5cdd04c..abe83d9c68 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -403,6 +403,9 @@ namespace FluentAssertions.Collections protected void AssertCollectionStartsWith(System.Collections.Generic.IEnumerable actualItems, System.Collections.Generic.ICollection expected, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(params T[] elements) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -448,6 +451,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint HaveSameCount(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEqualTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -755,6 +759,7 @@ namespace FluentAssertions.Equivalency bool? CompareRecordsByValue { get; } FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } + bool EnableFullDump { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } bool IgnoreCase { get; } @@ -771,6 +776,8 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } System.Collections.Generic.IEnumerable SelectionRules { get; } FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + bool TreatNullCollectionsAsEmpty { get; } + bool TreatNullStringsAsEmpty { get; } bool UseRuntimeTyping { get; } System.Collections.Generic.IEnumerable UserEquivalencySteps { get; } FluentAssertions.Equivalency.EqualityStrategy GetEqualityStrategy(System.Type type); @@ -903,6 +910,7 @@ namespace FluentAssertions.Equivalency protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool EnableFullDump { get; } public bool IgnoreCase { get; } public bool IgnoreJsonPropertyCasing { get; set; } public bool IgnoreLeadingWhitespace { get; } @@ -912,6 +920,8 @@ namespace FluentAssertions.Equivalency [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + public bool TreatNullCollectionsAsEmpty { get; } + public bool TreatNullStringsAsEmpty { get; } protected TSelf AddMatchingRule(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } protected TSelf AddSelectionRule(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf AllowingInfiniteRecursion() { } @@ -921,9 +931,13 @@ namespace FluentAssertions.Equivalency public TSelf ComparingByValue() { } public TSelf ComparingEnumsByName() { } public TSelf ComparingEnumsByValue() { } + public TSelf ComparingNullCollectionsAsEmpty() { } + public TSelf ComparingNullStringsAsEmpty() { } public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf Excluding(System.Type type) { } + public TSelf Excluding() { } public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } public TSelf ExcludingMembersNamed(params string[] memberNames) { } @@ -960,6 +974,7 @@ namespace FluentAssertions.Equivalency where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } public TSelf WithAutoConversion() { } public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithFullDump() { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictTyping() { } @@ -1192,8 +1207,9 @@ namespace FluentAssertions.Execution public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } - public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain ReuseOnce() { } public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } @@ -1729,6 +1745,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveSecond(int expected, string because = "", params object[] becauseArgs) { } @@ -1744,6 +1761,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveSecond(int unexpected, string because = "", params object[] becauseArgs) { } @@ -1780,6 +1798,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveOffset(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } @@ -1797,6 +1816,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTimeOffset unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveOffset(System.TimeSpan unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt index c29f32091e..9438447689 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt @@ -4,17 +4,20 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] namespace FluentAssertions { + [System.Diagnostics.StackTraceHidden] public class AggregateExceptionExtractor : FluentAssertions.Specialized.IExtractExceptions { public AggregateExceptionExtractor() { } public System.Collections.Generic.IEnumerable OfType(System.Exception actualException) where T : System.Exception { } } + [System.Diagnostics.StackTraceHidden] public class AndConstraint { public AndConstraint(TParent parent) { } public TParent And { get; } } + [System.Diagnostics.StackTraceHidden] public class AndWhichConstraint : FluentAssertions.AndConstraint { public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects) { } @@ -25,10 +28,12 @@ namespace FluentAssertions public TSubject Subject { get; } public TSubject Which { get; } } + [System.Diagnostics.StackTraceHidden] public static class AssertionConfiguration { public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } } + [System.Diagnostics.StackTraceHidden] public static class AssertionEngine { public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } @@ -84,10 +89,12 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.Stream actualValue) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.ReadOnlySpan actualValue) { } public static FluentAssertions.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.MethodInfo methodInfo) { } public static FluentAssertions.Types.PropertyInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.PropertyInfo propertyInfo) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.Span actualValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } public static FluentAssertions.Primitives.TimeOnlyAssertions Should(this System.TimeOnly actualValue) { } public static FluentAssertions.Primitives.NullableTimeOnlyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeOnly? actualValue) { } @@ -163,6 +170,10 @@ namespace FluentAssertions public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func> action) { } public static FluentAssertions.Specialized.FunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func func) { } public static FluentAssertions.Numeric.ComparableTypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IComparable comparableValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Memory actualValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.ReadOnlyMemory actualValue) { } + public static FluentAssertions.Collections.SpanAssertions Should(this System.ReadOnlySpan actualValue) { } + public static FluentAssertions.Collections.SpanAssertions Should(this System.Span actualValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] @@ -188,6 +199,7 @@ namespace FluentAssertions public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public static class AtLeast { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -195,6 +207,7 @@ namespace FluentAssertions public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + [System.Diagnostics.StackTraceHidden] public static class AtMost { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -202,6 +215,7 @@ namespace FluentAssertions public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + [System.Diagnostics.StackTraceHidden] public static class CallerIdentifier { public static System.Action Logger { get; set; } @@ -209,11 +223,13 @@ namespace FluentAssertions public static string DetermineCallerIdentity() { } } [System.AttributeUsage(System.AttributeTargets.Method)] + [System.Diagnostics.StackTraceHidden] public class CustomAssertionAttribute : System.Attribute { public CustomAssertionAttribute() { } } [System.AttributeUsage(System.AttributeTargets.Assembly)] + [System.Diagnostics.StackTraceHidden] public sealed class CustomAssertionsAssemblyAttribute : System.Attribute { public CustomAssertionsAssemblyAttribute() { } @@ -231,6 +247,7 @@ namespace FluentAssertions public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, params System.Linq.Expressions.Expression>[] predicates) { } public static FluentAssertions.Events.IEventRecording WithSender(this FluentAssertions.Events.IEventRecording eventRecording, object expectedSender) { } } + [System.Diagnostics.StackTraceHidden] public static class Exactly { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -261,6 +278,7 @@ namespace FluentAssertions public static System.Threading.Tasks.Task> WithoutMessage(this System.Threading.Tasks.Task> task, string wildcardPattern, string because = "", params object[] becauseArgs) where TException : System.Exception { } } + [System.Diagnostics.StackTraceHidden] public static class FluentActions { public static System.Func Awaiting(System.Func action) { } @@ -276,16 +294,19 @@ namespace FluentAssertions public static FluentAssertions.Specialized.JsonNodeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this T jsonNode) where T : System.Text.Json.Nodes.JsonNode { } } + [System.Diagnostics.StackTraceHidden] public static class LessThan { public static FluentAssertions.OccurrenceConstraint Thrice() { } public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + [System.Diagnostics.StackTraceHidden] public static class License { public static bool Accepted { get; set; } } + [System.Diagnostics.StackTraceHidden] public static class MoreThan { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -347,10 +368,15 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public abstract class OccurrenceConstraint { protected OccurrenceConstraint(int expectedCount) { } } + public static class SpanAssertionsExtensions + { + public static FluentAssertions.AndConstraint> Be([System.Diagnostics.CodeAnalysis.NotNull] this FluentAssertions.Collections.SpanAssertions assertions, string expected, string because = "", params object[] becauseArgs) { } + } public static class TypeEnumerableExtensions { public static System.Collections.Generic.IEnumerable ThatAreClasses(this System.Collections.Generic.IEnumerable types) { } @@ -383,6 +409,7 @@ namespace FluentAssertions public static FluentAssertions.Types.TypeSelector Types(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.TypeSelector Types(this System.Type type) { } } + [System.Diagnostics.StackTraceHidden] public static class Value { public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatMatches(System.Linq.Expressions.Expression> condition) { } @@ -422,6 +449,9 @@ namespace FluentAssertions.Collections protected void AssertCollectionStartsWith(System.Collections.Generic.IEnumerable actualItems, System.Collections.Generic.ICollection expected, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(params T[] elements) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -467,6 +497,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint HaveSameCount(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEqualTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -549,6 +580,12 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint NotEqual(T unexpected, string because = "", params object[] becauseArgs) where T : System.Collections.Generic.IEnumerable> { } } + public class SpanAssertions : FluentAssertions.Collections.GenericCollectionAssertions> + { + public SpanAssertions(T[] actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.AndConstraint> BeEqualTo(System.ReadOnlySpan expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> BeEqualTo(System.Span expected, string because = "", params object[] becauseArgs) { } + } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> { public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } @@ -581,6 +618,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint> ThenBeInDescendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInDescendingOrder(System.Linq.Expressions.Expression> propertyExpression, System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class WhoseValueConstraint : FluentAssertions.AndConstraint where TCollection : System.Collections.Generic.IEnumerable> where TAssertions : FluentAssertions.Collections.GenericDictionaryAssertions @@ -626,6 +664,7 @@ namespace FluentAssertions.Common } namespace FluentAssertions.Configuration { + [System.Diagnostics.StackTraceHidden] public class GlobalConfiguration { public GlobalConfiguration() { } @@ -633,6 +672,7 @@ namespace FluentAssertions.Configuration public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } public FluentAssertions.Configuration.TestFramework? TestFramework { get; set; } } + [System.Diagnostics.StackTraceHidden] public class GlobalEquivalencyOptions { public GlobalEquivalencyOptions() { } @@ -640,6 +680,7 @@ namespace FluentAssertions.Configuration public FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } public void Modify(System.Func configureOptions) { } } + [System.Diagnostics.StackTraceHidden] public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions { public GlobalFormattingOptions() { } @@ -659,6 +700,7 @@ namespace FluentAssertions.Configuration } namespace FluentAssertions.Equivalency { + [System.Diagnostics.StackTraceHidden] public class Comparands { public Comparands() { } @@ -670,6 +712,7 @@ namespace FluentAssertions.Equivalency public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyOptions options) { } public override string ToString() { } } + [System.Diagnostics.StackTraceHidden] public class ConversionSelector { public ConversionSelector() { } @@ -701,6 +744,7 @@ namespace FluentAssertions.Equivalency { public EquivalencyOptions() { } } + [System.Diagnostics.StackTraceHidden] public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions> { public EquivalencyOptions() { } @@ -716,6 +760,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } } + [System.Diagnostics.StackTraceHidden] public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { public EquivalencyPlan() { } @@ -738,12 +783,14 @@ namespace FluentAssertions.Equivalency ContinueWithNext = 0, EquivalencyProven = 1, } + [System.Diagnostics.StackTraceHidden] public abstract class EquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { protected EquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator); } + [System.Diagnostics.StackTraceHidden] public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyOptions options) { } @@ -774,6 +821,7 @@ namespace FluentAssertions.Equivalency bool? CompareRecordsByValue { get; } FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } + bool EnableFullDump { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } bool IgnoreCase { get; } @@ -790,6 +838,8 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } System.Collections.Generic.IEnumerable SelectionRules { get; } FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + bool TreatNullCollectionsAsEmpty { get; } + bool TreatNullStringsAsEmpty { get; } bool UseRuntimeTyping { get; } System.Collections.Generic.IEnumerable UserEquivalencySteps { get; } FluentAssertions.Equivalency.EqualityStrategy GetEqualityStrategy(System.Type type); @@ -866,10 +916,12 @@ namespace FluentAssertions.Equivalency { void AssertEquivalencyOf(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); } + [System.Diagnostics.StackTraceHidden] public static class MemberFactory { public static FluentAssertions.Equivalency.IMember Create(System.Reflection.MemberInfo memberInfo, FluentAssertions.Equivalency.INode parent) { } } + [System.Diagnostics.StackTraceHidden] public class MemberSelectionContext { public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyOptions options) { } @@ -886,6 +938,7 @@ namespace FluentAssertions.Equivalency ExplicitlyImplemented = 4, DefaultInterfaceProperties = 8, } + [System.Diagnostics.StackTraceHidden] public class NestedExclusionOptionBuilder { public FluentAssertions.Equivalency.EquivalencyOptions Exclude(System.Linq.Expressions.Expression> expression) { } @@ -897,6 +950,7 @@ namespace FluentAssertions.Equivalency NotStrict = 1, Irrelevant = 2, } + [System.Diagnostics.StackTraceHidden] public class OrderingRuleCollection : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { public OrderingRuleCollection() { } @@ -916,12 +970,14 @@ namespace FluentAssertions.Equivalency public override string ToString() { } public delegate string GetDescription(string pathAndName); } + [System.Diagnostics.StackTraceHidden] public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool EnableFullDump { get; } public bool IgnoreCase { get; } public bool IgnoreJsonPropertyCasing { get; set; } public bool IgnoreLeadingWhitespace { get; } @@ -931,6 +987,8 @@ namespace FluentAssertions.Equivalency [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + public bool TreatNullCollectionsAsEmpty { get; } + public bool TreatNullStringsAsEmpty { get; } protected TSelf AddMatchingRule(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } protected TSelf AddSelectionRule(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf AllowingInfiniteRecursion() { } @@ -940,9 +998,13 @@ namespace FluentAssertions.Equivalency public TSelf ComparingByValue() { } public TSelf ComparingEnumsByName() { } public TSelf ComparingEnumsByValue() { } + public TSelf ComparingNullCollectionsAsEmpty() { } + public TSelf ComparingNullStringsAsEmpty() { } public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf Excluding(System.Type type) { } + public TSelf Excluding() { } public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } public TSelf ExcludingMembersNamed(params string[] memberNames) { } @@ -980,6 +1042,7 @@ namespace FluentAssertions.Equivalency where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } public TSelf WithAutoConversion() { } public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithFullDump() { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictTyping() { } @@ -1000,6 +1063,7 @@ namespace FluentAssertions.Equivalency where TMemberType : TMember { } } } + [System.Diagnostics.StackTraceHidden] public static class SubjectInfoExtensions { public static bool WhichGetterDoesNotHave(this FluentAssertions.Equivalency.IMemberInfo memberInfo, FluentAssertions.Common.CSharpAccessModifier accessModifier) { } @@ -1014,6 +1078,7 @@ namespace FluentAssertions.Equivalency.Inlining { void Execute(FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Equivalency.Comparands comparands); } + [System.Diagnostics.StackTraceHidden] public class InlineEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public InlineEquivalencyStep() { } @@ -1022,104 +1087,124 @@ namespace FluentAssertions.Equivalency.Inlining } namespace FluentAssertions.Equivalency.Steps { + [System.Diagnostics.StackTraceHidden] public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertionAction) { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } + [System.Diagnostics.StackTraceHidden] public class AutoConversionStep : FluentAssertions.Equivalency.IEquivalencyStep { public AutoConversionStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } + [System.Diagnostics.StackTraceHidden] public class DateAndTimeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public DateAndTimeEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } + [System.Diagnostics.StackTraceHidden] public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumEqualityStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class EnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumerableEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class EqualityComparerEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer comparer) { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } + [System.Diagnostics.StackTraceHidden] public class GenericDictionaryEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericDictionaryEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class GenericEnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericEnumerableEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class JsonConversionStep : FluentAssertions.Equivalency.IEquivalencyStep { public JsonConversionStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class ReferenceEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ReferenceEqualityEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class RunAllUserStepsEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public RunAllUserStepsEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class SimpleEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public SimpleEqualityEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class StringEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StringEqualityEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class StructuralEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StructuralEqualityEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class TypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public TypeEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class ValueTypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ValueTypeEquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } + [System.Diagnostics.StackTraceHidden] public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } + [System.Diagnostics.StackTraceHidden] public class XDocumentEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XDocumentEquivalencyStep() { } protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } + [System.Diagnostics.StackTraceHidden] public class XElementEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XElementEquivalencyStep() { } @@ -1161,12 +1246,14 @@ namespace FluentAssertions.Events public FluentAssertions.Events.IEventRecording Raise(string eventName, string because = "", params object[] becauseArgs) { } public FluentAssertions.Events.IEventRecording RaisePropertyChangeFor(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class EventMetadata { public EventMetadata(string eventName, System.Type handlerType) { } public string EventName { get; } public System.Type HandlerType { get; } } + [System.Diagnostics.StackTraceHidden] public class EventMonitorOptions { public EventMonitorOptions() { } @@ -1188,6 +1275,7 @@ namespace FluentAssertions.Events FluentAssertions.Events.IEventRecording GetRecordingFor(string eventName); FluentAssertions.Events.EventAssertions Should(); } + [System.Diagnostics.StackTraceHidden] public class OccurredEvent { public OccurredEvent() { } @@ -1199,6 +1287,7 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { + [System.Diagnostics.StackTraceHidden] public sealed class AssertionChain { public string CallerIdentifier { get; } @@ -1217,8 +1306,9 @@ namespace FluentAssertions.Execution public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } - public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain ReuseOnce() { } public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } @@ -1226,10 +1316,12 @@ namespace FluentAssertions.Execution public FluentAssertions.Execution.AssertionChain WithReportable(string name, System.Func content) { } public static FluentAssertions.Execution.AssertionChain GetOrCreate() { } } + [System.Diagnostics.StackTraceHidden] public class AssertionFailedException : System.Exception { public AssertionFailedException(string message) { } } + [System.Diagnostics.StackTraceHidden] public sealed class AssertionScope : System.IDisposable { public AssertionScope() { } @@ -1245,21 +1337,25 @@ namespace FluentAssertions.Execution public void Dispose() { } public bool HasFailures() { } } + [System.Diagnostics.StackTraceHidden] public class Continuation { public FluentAssertions.Execution.AssertionChain Then { get; } } + [System.Diagnostics.StackTraceHidden] public class ContinuationOfGiven { public bool Succeeded { get; } public FluentAssertions.Execution.GivenSelector Then { get; } } + [System.Diagnostics.StackTraceHidden] public class FailReason { public FailReason(string message, params object[] args) { } public object[] Args { get; } public string Message { get; } } + [System.Diagnostics.StackTraceHidden] public class GivenSelector { public bool Succeeded { get; } @@ -1286,6 +1382,7 @@ namespace FluentAssertions.Execution [System.Diagnostics.CodeAnalysis.DoesNotReturn] void Throw(string message); } + [System.Diagnostics.StackTraceHidden] public class Reason { public Reason(string formattedMessage, object[] arguments) { } @@ -1296,6 +1393,7 @@ namespace FluentAssertions.Execution namespace FluentAssertions.Extensibility { [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] + [System.Diagnostics.StackTraceHidden] public sealed class AssertionEngineInitializerAttribute : System.Attribute { public AssertionEngineInitializerAttribute(System.Type type, string methodName) { } @@ -1805,6 +1903,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveSecond(int expected, string because = "", params object[] becauseArgs) { } @@ -1820,6 +1919,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveSecond(int unexpected, string because = "", params object[] becauseArgs) { } @@ -1856,6 +1956,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveOffset(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } @@ -1873,6 +1974,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTimeOffset unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveOffset(System.TimeSpan unexpected, string because = "", params object[] becauseArgs) { } @@ -2316,6 +2418,7 @@ namespace FluentAssertions.Specialized public virtual FluentAssertions.Specialized.ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithoutMessage(string wildcardPattern, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class ExecutionTime { public ExecutionTime(System.Action action, FluentAssertions.Common.StartTimer createTimer) { } @@ -2380,6 +2483,7 @@ namespace FluentAssertions.Specialized public FluentAssertions.AndConstraint> NotBeUtcDate(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> NotHaveProperty(string code, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class MemberExecutionTime : FluentAssertions.Specialized.ExecutionTime { public MemberExecutionTime(T subject, System.Linq.Expressions.Expression> action, FluentAssertions.Common.StartTimer createTimer) { } @@ -2454,6 +2558,7 @@ namespace FluentAssertions.Streams } namespace FluentAssertions.Types { + [System.Diagnostics.StackTraceHidden] public static class AllTypes { public static FluentAssertions.Types.TypeSelector From(System.Reflection.Assembly assembly) { } @@ -2511,6 +2616,7 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint> Return(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ReturnVoid(string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class MethodInfoSelector : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { public MethodInfoSelector(System.Collections.Generic.IEnumerable types) { } @@ -2578,6 +2684,7 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint Return(System.Type propertyType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Return(string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class PropertyInfoSelector : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { public PropertyInfoSelector(System.Collections.Generic.IEnumerable types) { } @@ -2698,6 +2805,7 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint NotImplement(string because = "", params object[] becauseArgs) where TInterface : class { } } + [System.Diagnostics.StackTraceHidden] public class TypeSelector : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { public TypeSelector(System.Collections.Generic.IEnumerable types) { } @@ -2847,6 +2955,7 @@ namespace FluentAssertions.Xml public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.XmlNode expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.XmlNode unexpected, string because = "", params object[] becauseArgs) { } } + [System.Diagnostics.StackTraceHidden] public class XmlNodeFormatter : FluentAssertions.Formatting.IValueFormatter { public XmlNodeFormatter() { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index b52faa2341..7ced24c3b4 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -395,6 +395,9 @@ namespace FluentAssertions.Collections protected void AssertCollectionStartsWith(System.Collections.Generic.IEnumerable actualItems, System.Collections.Generic.ICollection expected, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(params T[] elements) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -440,6 +443,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint HaveSameCount(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEqualTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -747,6 +751,7 @@ namespace FluentAssertions.Equivalency bool? CompareRecordsByValue { get; } FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } + bool EnableFullDump { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } bool IgnoreCase { get; } @@ -763,6 +768,8 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } System.Collections.Generic.IEnumerable SelectionRules { get; } FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + bool TreatNullCollectionsAsEmpty { get; } + bool TreatNullStringsAsEmpty { get; } bool UseRuntimeTyping { get; } System.Collections.Generic.IEnumerable UserEquivalencySteps { get; } FluentAssertions.Equivalency.EqualityStrategy GetEqualityStrategy(System.Type type); @@ -895,6 +902,7 @@ namespace FluentAssertions.Equivalency protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool EnableFullDump { get; } public bool IgnoreCase { get; } public bool IgnoreJsonPropertyCasing { get; set; } public bool IgnoreLeadingWhitespace { get; } @@ -904,6 +912,8 @@ namespace FluentAssertions.Equivalency [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + public bool TreatNullCollectionsAsEmpty { get; } + public bool TreatNullStringsAsEmpty { get; } protected TSelf AddMatchingRule(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } protected TSelf AddSelectionRule(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf AllowingInfiniteRecursion() { } @@ -913,9 +923,13 @@ namespace FluentAssertions.Equivalency public TSelf ComparingByValue() { } public TSelf ComparingEnumsByName() { } public TSelf ComparingEnumsByValue() { } + public TSelf ComparingNullCollectionsAsEmpty() { } + public TSelf ComparingNullStringsAsEmpty() { } public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf Excluding(System.Type type) { } + public TSelf Excluding() { } public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } public TSelf ExcludingMembersNamed(params string[] memberNames) { } @@ -952,6 +966,7 @@ namespace FluentAssertions.Equivalency where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } public TSelf WithAutoConversion() { } public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithFullDump() { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictTyping() { } @@ -1136,8 +1151,9 @@ namespace FluentAssertions.Execution public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } - public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain ReuseOnce() { } public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } @@ -1673,6 +1689,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveSecond(int expected, string because = "", params object[] becauseArgs) { } @@ -1688,6 +1705,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveSecond(int unexpected, string because = "", params object[] becauseArgs) { } @@ -1724,6 +1742,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveOffset(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } @@ -1741,6 +1760,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTimeOffset unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveOffset(System.TimeSpan unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index fc2d857989..64c73b18c6 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -82,10 +82,12 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.Stream actualValue) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.ReadOnlySpan actualValue) { } public static FluentAssertions.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.MethodInfo methodInfo) { } public static FluentAssertions.Types.PropertyInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.PropertyInfo propertyInfo) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.Span actualValue) { } public static FluentAssertions.Primitives.SimpleTimeSpanAssertions Should(this System.TimeSpan actualValue) { } public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeSpan? actualValue) { } public static FluentAssertions.Types.TypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Type subject) { } @@ -150,6 +152,10 @@ namespace FluentAssertions public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func> action) { } public static FluentAssertions.Specialized.FunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func func) { } public static FluentAssertions.Numeric.ComparableTypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IComparable comparableValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Memory actualValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.ReadOnlyMemory actualValue) { } + public static FluentAssertions.Collections.SpanAssertions Should(this System.ReadOnlySpan actualValue) { } + public static FluentAssertions.Collections.SpanAssertions Should(this System.Span actualValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] @@ -332,6 +338,10 @@ namespace FluentAssertions { protected OccurrenceConstraint(int expectedCount) { } } + public static class SpanAssertionsExtensions + { + public static FluentAssertions.AndConstraint> Be([System.Diagnostics.CodeAnalysis.NotNull] this FluentAssertions.Collections.SpanAssertions assertions, string expected, string because = "", params object[] becauseArgs) { } + } public static class TypeEnumerableExtensions { public static System.Collections.Generic.IEnumerable ThatAreClasses(this System.Collections.Generic.IEnumerable types) { } @@ -403,6 +413,9 @@ namespace FluentAssertions.Collections protected void AssertCollectionStartsWith(System.Collections.Generic.IEnumerable actualItems, System.Collections.Generic.ICollection expected, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(params T[] elements) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEqualTo(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -448,6 +461,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint HaveSameCount(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEqualTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } @@ -530,6 +544,12 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint NotEqual(T unexpected, string because = "", params object[] becauseArgs) where T : System.Collections.Generic.IEnumerable> { } } + public class SpanAssertions : FluentAssertions.Collections.GenericCollectionAssertions> + { + public SpanAssertions(T[] actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.AndConstraint> BeEqualTo(System.ReadOnlySpan expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> BeEqualTo(System.Span expected, string because = "", params object[] becauseArgs) { } + } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> { public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } @@ -755,6 +775,7 @@ namespace FluentAssertions.Equivalency bool? CompareRecordsByValue { get; } FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } + bool EnableFullDump { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } bool IgnoreCase { get; } @@ -771,6 +792,8 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } System.Collections.Generic.IEnumerable SelectionRules { get; } FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + bool TreatNullCollectionsAsEmpty { get; } + bool TreatNullStringsAsEmpty { get; } bool UseRuntimeTyping { get; } System.Collections.Generic.IEnumerable UserEquivalencySteps { get; } FluentAssertions.Equivalency.EqualityStrategy GetEqualityStrategy(System.Type type); @@ -903,6 +926,7 @@ namespace FluentAssertions.Equivalency protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool EnableFullDump { get; } public bool IgnoreCase { get; } public bool IgnoreJsonPropertyCasing { get; set; } public bool IgnoreLeadingWhitespace { get; } @@ -912,6 +936,8 @@ namespace FluentAssertions.Equivalency [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } + public bool TreatNullCollectionsAsEmpty { get; } + public bool TreatNullStringsAsEmpty { get; } protected TSelf AddMatchingRule(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } protected TSelf AddSelectionRule(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf AllowingInfiniteRecursion() { } @@ -921,9 +947,13 @@ namespace FluentAssertions.Equivalency public TSelf ComparingByValue() { } public TSelf ComparingEnumsByName() { } public TSelf ComparingEnumsByValue() { } + public TSelf ComparingNullCollectionsAsEmpty() { } + public TSelf ComparingNullStringsAsEmpty() { } public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf Excluding(System.Type type) { } + public TSelf Excluding() { } public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } public TSelf ExcludingMembersNamed(params string[] memberNames) { } @@ -960,6 +990,7 @@ namespace FluentAssertions.Equivalency where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } public TSelf WithAutoConversion() { } public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithFullDump() { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictTyping() { } @@ -1192,8 +1223,9 @@ namespace FluentAssertions.Execution public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } - public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain ReuseOnce() { } public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } @@ -1729,6 +1761,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveSecond(int expected, string because = "", params object[] becauseArgs) { } @@ -1744,6 +1777,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveSecond(int unexpected, string because = "", params object[] becauseArgs) { } @@ -1780,6 +1814,7 @@ namespace FluentAssertions.Primitives public override bool Equals(object obj) { } public FluentAssertions.AndConstraint HaveDay(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveHour(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveMillisecond(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMinute(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveMonth(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveOffset(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } @@ -1797,6 +1832,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTimeOffset unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveDay(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveHour(int unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveMillisecond(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMinute(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveMonth(int unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveOffset(System.TimeSpan unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Benchmarks/Benchmarks.csproj b/Tests/Benchmarks/Benchmarks.csproj index 0846be0c90..6e0c1bccfb 100644 --- a/Tests/Benchmarks/Benchmarks.csproj +++ b/Tests/Benchmarks/Benchmarks.csproj @@ -1,4 +1,4 @@ - + net472;net8.0 True @@ -9,7 +9,7 @@ - + diff --git a/Tests/Benchmarks/Program.cs b/Tests/Benchmarks/Program.cs index ff2fb9ab18..2405f2befe 100644 --- a/Tests/Benchmarks/Program.cs +++ b/Tests/Benchmarks/Program.cs @@ -24,6 +24,6 @@ public static void Main() var config = ManualConfig.CreateMinimumViable().AddExporter(exporter); - _ = BenchmarkRunner.Run(config); + _ = BenchmarkRunner.Run(config); } } diff --git a/Tests/FSharp.Specs/FSharp.Specs.fsproj b/Tests/FSharp.Specs/FSharp.Specs.fsproj index 529597fa17..c209ea6edd 100644 --- a/Tests/FSharp.Specs/FSharp.Specs.fsproj +++ b/Tests/FSharp.Specs/FSharp.Specs.fsproj @@ -2,7 +2,7 @@ net6.0 - 6.0 + 8.0 true @@ -14,7 +14,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs index e68426d0a0..3baaae2c08 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs @@ -917,5 +917,134 @@ public void The_entire_subject_and_expectation_can_be_included_in_the_failure() // Assert act.Should().Throw().WithMessage("*Full expectation*"); } + + [Fact] + public void Reports_all_relevant_details_to_understand_the_differences() + { + // Arrange + var actual = new[] + { + new Customer + { + Age = 13, + Name = "Jits" + }, + new Customer + { + Age = 16, + Name = "Teddie" + } + }; + + // Act + var act = () => actual.Should().BeEquivalentTo([ + new Customer + { + Age = 52, + Name = "Dennis" + } + ], opt => opt.Excluding(x => x.Id)); + + // Assert + act.Should().Throw().WithMessage( + """ + Expected property actual[0].Name to be "Dennis" with a length of 6, but "Jits" has a length of 4, differs near "Jit" (index 0). + Expected property actual[0].Age to be 52, but found 13. + Expected actual to contain exactly one item, but found one extraneous item at index 1: FluentAssertions.Equivalency.Specs.Customer + { + Age = 16, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Teddie" + } + + With configuration: + - Prefer the declared type of the members + - Compare enums by value + - Compare tuples by their properties + - Compare anonymous types by their properties + - Compare records by their members + - Include non-browsable members + - Include all non-private properties + - Include all non-private fields + - Exclude member Id + - Match (JSON) member by name (or throw) + - Be strict about the order of items in byte arrays + - Without automatic conversion + """); + } + + [Fact] + public void Reports_all_relevant_details_to_understand_the_differences_including_the_full_dump() + { + // Arrange + var actual = new[] + { + new Customer + { + Age = 13, + Name = "Jits" + }, + new Customer + { + Age = 16, + Name = "Teddie" + } + }; + + // Act + var act = () => actual.Should().BeEquivalentTo([ + new Customer + { + Age = 52, + Name = "Dennis" + } + ], opt => opt.Excluding(x => x.Id).WithFullDump()); + + // Assert + act.Should().Throw().WithMessage( + """ + Expected property actual[0].Name to be "Dennis" with a length of 6, but "Jits" has a length of 4, differs near "Jit" (index 0). + Expected property actual[0].Age to be 52, but found 13. + Expected actual to contain exactly one item, but found one extraneous item at index 1: FluentAssertions.Equivalency.Specs.Customer + { + Age = 16, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Teddie" + } + + Full dump of actual: { + FluentAssertions.Equivalency.Specs.Customer + { + Age = 13, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Jits" + }, + FluentAssertions.Equivalency.Specs.Customer + { + Age = 16, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Teddie" + } + } + + With configuration: + - Prefer the declared type of the members + - Compare enums by value + - Compare tuples by their properties + - Compare anonymous types by their properties + - Compare records by their members + - Include non-browsable members + - Include all non-private properties + - Include all non-private fields + - Exclude member Id + - Match (JSON) member by name (or throw) + - Be strict about the order of items in byte arrays + - Without automatic conversion + """); + } } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs index 15e8bd9b0d..47ef46c99c 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs @@ -197,18 +197,18 @@ public void When_the_expectation_is_an_array_of_interface_type_it_should_respect public void When_the_expectation_has_fewer_dimensions_than_a_multi_dimensional_subject_it_should_fail() { // Arrange - object objectA = new(); - object objectB = new(); + string objectA = "A"; + string objectB = "B"; - object[][] actual = [[objectA, objectB]]; - var expected = actual[0]; + string[][] actual = [[objectA, objectB]]; + string[] expected = actual[0]; // Act Action act = () => actual.Should().BeEquivalentTo(expected); // Assert act.Should().Throw() - .WithMessage("*be a collection with 2 item(s)*contains 1 item(s) less than*", + .WithMessage("*actual[0] to be of type System.String, but found {\"A\", \"B\"} of type System.String[]*", "adding a `params object[]` overload cannot distinguish 'an array of objects' from 'an element which is an array of objects'"); } @@ -288,7 +288,7 @@ public void When_a_collection_does_not_match_it_should_include_items_in_message( // Assert action.Should().Throw() - .WithMessage("Expected*but*{1, 2}*1 item(s) less than*{3, 2, 1}*"); + .WithMessage("Expected subject to contain exactly 3 items in any order, but it misses {3}*"); } [Fact] @@ -300,10 +300,10 @@ public void When_collection_of_same_count_does_not_match_it_should_include_at_mo var expectation = Enumerable.Repeat(20, subject.Length).ToArray(); // Act - Action action = () => subject.Should().BeEquivalentTo(expectation); + var act = () => subject.Should().BeEquivalentTo(expectation); // Assert - action.Should().Throw().Which + act.Should().Throw().Which .Message.Should().Contain("[9]").And.NotContain("[10]"); } @@ -378,14 +378,20 @@ public void When_a_collection_contains_less_items_than_expected_it_should_throw( // Assert act.Should().Throw() .WithMessage( - "*Customers*to be a collection with 2 item(s), but*contains 1 item(s) less than*"); + "*Expected property subject.Customers to contain exactly 2 items in any order, but it misses*"); } [Fact] public void When_a_collection_contains_more_items_than_expected_it_should_throw() { // Arrange - var expected = new { Customers = new[] { new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" } } }; + var expected = new + { + Customers = new[] + { + new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" } + } + }; var subject = new { @@ -402,7 +408,7 @@ public void When_a_collection_contains_more_items_than_expected_it_should_throw( // Assert act.Should().Throw() .WithMessage( - "*Customers*to be a collection with 1 item(s), but*contains 1 item(s) more than*"); + "*Customers*to contain exactly one item, but found one extraneous*"); } [Fact] @@ -1385,7 +1391,7 @@ public void When_an_unordered_collection_must_be_strict_using_a_predicate_it_sho // Assert action.Should().Throw() - .WithMessage("*Expected*[0].UnorderedCollection*5 item(s)*empty collection*"); + .WithMessage("*Expected subject[0].UnorderedCollection[4] to be 1, but found 5*"); } [Fact] @@ -1442,7 +1448,7 @@ public void When_an_unordered_collection_must_be_strict_using_an_expression_it_s // Assert action.Should().Throw() .WithMessage( - "*Expected*[0].UnorderedCollection*5 item(s)*empty collection*"); + "*Expected subject[0].UnorderedCollection[4] to be 1, but found 5*"); } [Fact] @@ -1467,7 +1473,7 @@ public void Can_force_strict_ordering_based_on_the_parent_type_of_an_unordered_c // Assert action.Should().Throw() - .WithMessage("*Expected*[0].UnorderedCollection*5 item(s)*empty collection*"); + .WithMessage("*Expected subject[0].UnorderedCollection[4] to be 1, but found 5*"); } [Fact] @@ -2191,7 +2197,7 @@ public void When_the_subject_contains_less_items_than_expected_it_should_throw() // Assert action.Should().Throw() .WithMessage( - "*subject*to be a collection with 2 item(s), but*contains 1 item(s) less than*"); + "*Expected subject to contain exactly 2 items in any order, but it misses*Age = 24*"); } [Fact] @@ -2216,7 +2222,60 @@ public void When_the_subject_contains_more_items_than_expected_it_should_throw() // Assert action.Should().Throw() .WithMessage( - "Expected subject*to be a collection with 1 item(s), but*contains 1 item(s) more than*"); + "*Expected subject to contain exactly one item, but found one extraneous item at index 1:*Age = 24*"); + } + + [Fact] + public void When_the_subject_contains_multiple_extra_items_it_should_include_the_index_of_each() + { + // Arrange + var subject = new List + { + new() { Name = "John", Age = 27, Id = 1 }, + new() { Name = "Jane", Age = 24, Id = 2 }, + new() { Name = "Bob", Age = 32, Id = 3 } + }; + + var expectation = new List + { + new() { Name = "John", Age = 27, Id = 1 } + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation); + + // Assert + action.Should().Throw() + .WithMessage("*extraneous items *at index 1*at index 2*"); + } + + [Fact] + public void When_the_subject_contains_multiple_extra_items_the_failure_message_should_include_their_formatted_properties() + { + // Arrange - Customer objects produce formatted output containing curly braces (e.g. "Customer\r\n{\r\n Age = 24\r\n}") + // which must be escaped before embedding into the FailWith format string to prevent string.Format from + // interpreting them as placeholders and producing a **WARNING** instead of the actual failure message. + var subject = new List + { + new() { Name = "John", Age = 27, Id = 1 }, + new() { Name = "Jane", Age = 24, Id = 2 }, + new() { Name = "Bob", Age = 32, Id = 3 } + }; + + var expectation = new List + { + new() { Name = "John", Age = 27, Id = 1 } + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation); + + // Assert + action.Should().Throw() + .WithMessage("*extraneous items*Age = 24*at index 1*Age = 32*at index 2*") + .WithoutMessage("**WARNING**"); } [Fact] @@ -2818,6 +2877,142 @@ ClassWithLotsOfProperties GetObject(int i) act.ExecutionTime().Should().BeLessThan(20.Seconds()); } + public class ComparingNullCollectionsAsEmpty + { + [Fact] + public void A_null_subject_collection_is_equivalent_to_an_empty_expectation() + { + // Arrange + int[] subject = null; + int[] expectation = []; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + + [Fact] + public void An_empty_subject_collection_is_equivalent_to_a_null_expectation() + { + // Arrange + int[] subject = []; + int[] expectation = null; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + + [Fact] + public void A_null_subject_collection_fails_when_the_expectation_is_non_empty() + { + // Arrange + int[] subject = null; + int[] expectation = [1, 2, 3]; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_non_empty_subject_collection_fails_when_the_expectation_is_null() + { + // Arrange + int[] subject = [1, 2, 3]; + int[] expectation = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Without_the_option_a_null_subject_collection_is_not_equivalent_to_an_empty_expectation() + { + // Arrange + int[] subject = null; + int[] expectation = []; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Without_the_option_an_empty_subject_collection_is_not_equivalent_to_a_null_expectation() + { + // Arrange + int[] subject = []; + int[] expectation = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_null_generic_list_subject_is_equivalent_to_an_empty_generic_list_expectation() + { + // Arrange + List subject = null; + var expectation = new List(); + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + + [Fact] + public void An_empty_generic_list_subject_is_equivalent_to_a_null_generic_list_expectation() + { + // Arrange + var subject = new List(); + List expectation = null; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + + [Fact] + public void A_null_nested_collection_is_equivalent_to_an_empty_nested_collection() + { + // Arrange + var subject = new { Items = (int[])null }; + var expectation = new { Items = Array.Empty() }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + + [Fact] + public void An_empty_nested_collection_is_equivalent_to_a_null_nested_collection() + { + // Arrange + var subject = new { Items = Array.Empty() }; + var expectation = new { Items = (int[])null }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + + [Fact] + public void Two_null_collections_are_equivalent() + { + // Arrange + int[] subject = null; + int[] expectation = null; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullCollectionsAsEmpty()); + } + } + private class ClassWithLotsOfProperties { public string Id { get; set; } diff --git a/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs index ba15c61d19..553ca245e5 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs @@ -1199,6 +1199,7 @@ public void When_a_custom_rule_is_applied_on_a_dictionary_it_should_apply_it_on_ [Fact] public void Passing_the_reason_to_the_inner_equivalency_assertion_works() { + // Arrange var subject = new Dictionary { ["a"] = new List() @@ -1209,8 +1210,10 @@ public void Passing_the_reason_to_the_inner_equivalency_assertion_works() ["a"] = new List { 42 } }; + // Act Action act = () => subject.Should().BeEquivalentTo(expected, "FOO {0}", "BAR"); + // Assert act.Should().Throw().WithMessage("*FOO BAR*"); } diff --git a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj index 77c4b66723..5ed4eef871 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj +++ b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj @@ -25,14 +25,14 @@ - - + + all runtime; build; native; contentfiles; analyzers - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs index c4ca546adb..7614238b37 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs @@ -125,7 +125,7 @@ public void When_not_all_the_properties_of_the_nested_objects_are_equal_it_shoul "- Compare records by their members*" + "- Match (JSON) member by name (or throw)*" + "- Be strict about the order of items in byte arrays*" + - "- Without automatic conversion.*"); + "- Without automatic conversion*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/PerformanceSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/PerformanceSpecs.cs new file mode 100644 index 0000000000..f3affd4b82 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/PerformanceSpecs.cs @@ -0,0 +1,43 @@ +using System.Linq; +using Xunit; + +namespace FluentAssertions.Equivalency.Specs; + +public class PerformanceSpecs +{ + [Fact(Skip = "Performance test")] + public void Compare_complex_collection() + { + int n = 500; + var subject = Enumerable.Range(0, n).Select(index => Create(index)).ToArray(); + var expected = Enumerable.Range(n, n).Select(index => Create(index)).ToArray(); + + subject.Should().BeEquivalentTo(expected, e => e.WithoutStrictOrdering()); + } + + private object Create(int index) => new + { + Value = index, + Name = $"Item-{index}-{new string((char)('A' + (index % 26)), 50)}", + Description = $"Description for item number {index} with extra padding {new string('x', 100)}", + Category = $"Category-{index % 10}", + Nested = new + { + Id = index * 100, + Label = $"Nested-{index}-{new string('z', 50)}", + SubNested = new + { + Code = index * 1000, + Data = $"Data-{index}-{new string('y', 80)}" + } + }, + Meta = new + { + K1 = index, + K2 = index * 2, + K3 = index * 3, + K4 = index * 4, + K5 = index * 5 + } + }; +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs index 44b8686a82..c84eca6151 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs @@ -1259,5 +1259,213 @@ public void Cannot_provide_null_as_a_property_name() act.Should().Throw() .WithMessage("*Member names cannot be null*"); } + + [Fact] + public void Excluding_a_member_by_path_on_a_type_with_value_semantics_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Excluding(o => o.NestedProperty)); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*overrides Equals*" + + "*ComparingByMembers*"); + } + + [Fact] + public void Excluding_a_member_by_path_when_forcing_value_semantics_explicitly_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Excluding(o => o.NestedProperty).ComparingByValue()); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*ComparingByValue was configured*" + + "*remove the ComparingByValue configuration*"); + } + + [Fact] + public void Including_a_member_by_path_when_forcing_value_semantics_explicitly_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Including(o => o.Key).ComparingByValue()); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*ComparingByValue was configured*" + + "*remove the ComparingByValue configuration*"); + } + + [Fact] + public void Excluding_a_member_by_predicate_when_forcing_value_semantics_explicitly_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt + .Excluding(m => m.Name == nameof(ClassWithValueSemanticsOnSingleProperty.NestedProperty)) + .ComparingByValue()); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*ComparingByValue was configured*" + + "*remove the ComparingByValue configuration*"); + } + + [Fact] + public void Excluding_a_member_by_path_and_then_forcing_member_comparison_does_not_fail() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers() + .Excluding(o => o.NestedProperty)); + } + + [Fact] + public void Excluding_a_nested_member_by_path_on_a_type_with_value_semantics_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsAndNestedObject { Key = "same", Child = new NestedObjectWithProperty { Text = "x" } }; + var expected = new ClassWithValueSemanticsAndNestedObject { Key = "same", Child = new NestedObjectWithProperty { Text = "y" } }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Excluding(o => o.Child.Text)); + + // Assert - multi-segment path at root should also be detected as conflicting + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsAndNestedObject*compared by value*overrides Equals*" + + "*ComparingByMembers*"); + } + + [Fact] + public void Excluding_a_member_by_predicate_on_a_type_with_value_semantics_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Excluding(m => m.Name == nameof(ClassWithValueSemanticsOnSingleProperty.NestedProperty))); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*overrides Equals*" + + "*ComparingByMembers*"); + } + + [Fact] + public void Including_members_by_predicate_on_a_type_with_value_semantics_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Including(m => m.Name == nameof(ClassWithValueSemanticsOnSingleProperty.Key))); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*overrides Equals*" + + "*ComparingByMembers*"); + } + + [Fact] + public void Excluding_a_member_of_a_collection_element_with_value_semantics_via_For_and_Exclude_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithCollectionOfValueSemantics + { + Items = [new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }] + }; + + var expected = new ClassWithCollectionOfValueSemantics + { + Items = [new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }] + }; + + // Act + // .For(o => o.Items).Exclude(c => c.NestedProperty) creates path "Items[].NestedProperty" (wildcard), + // which should be detected as conflicting when processing the value-semantic item at Items[0]. + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.For(o => o.Items).Exclude(c => c.NestedProperty)); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*overrides Equals*"); + } + } +} + +public class ClassWithCollectionOfValueSemantics +{ + public List Items { get; set; } +} + +public class ClassWithValueSemanticsAndNestedObject +{ + public string Key { get; init; } + + public NestedObjectWithProperty Child { get; init; } + + protected bool Equals(ClassWithValueSemanticsAndNestedObject other) => Key == other.Key; + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((ClassWithValueSemanticsAndNestedObject)obj); } + + public override int GetHashCode() => Key?.GetHashCode() ?? 0; +} + +public class NestedObjectWithProperty +{ + public string Text { get; set; } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.ExcludingByType.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.ExcludingByType.cs new file mode 100644 index 0000000000..ab2281fd39 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.ExcludingByType.cs @@ -0,0 +1,537 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class ExcludingByType + { + [Fact] + public void Can_exclude_exact_type_using_a_type_parameter() + { + // Arrange + var subject = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(5), + Score = 100 + }; + + var expectation = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(10), // Different value + Score = 100 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Can_exclude_exact_type_using_a_type_instance() + { + // Arrange + var subject = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(5), + Score = 100 + }; + + var expectation = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(10), // Different value + Score = 100 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding(typeof(TimeSpan))); + } + + [Fact] + public void Other_members_are_still_selected() + { + // Arrange + var subject = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(5) + }; + + var expectation = new + { + Name = "Jack", // Different value + Age = 30, + Duration = TimeSpan.FromHours(10) + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + + // Assert + act.Should().Throw() + .WithMessage("*Exclude members of type System.TimeSpan*"); + } + + [Fact] + public void Can_exclude_all_types_implementing_a_generic_interface() + { + // Arrange + var subject = new + { + Name = "John", + Items = new List { 1, 2, 3 }, + Values = new[] { 4, 5, 6 }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + Items = new List { 7, 8, 9 }, // Different value + Values = new[] { 10, 11, 12 }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding>()); + } + + [Fact] + public void Can_exclude_all_closed_generic_types() + { + // Arrange + var subject = new + { + Name = "John", + NullableInt = (int?)42, + NullableDouble = (double?)3.14, + NullableDateTime = (DateTime?)DateTime.Now, + Age = 30 + }; + + var expectation = new + { + Name = "John", + NullableInt = (int?)99, // Different value + NullableDouble = (double?)2.71, // Different value + NullableDateTime = (DateTime?)DateTime.Now.AddDays(1), // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding(typeof(Nullable<>))); + } + + [Fact] + public void Excluding_applies_to_all_levels_in_a_nested_structure() + { + // Arrange + var subject = new + { + Name = "John", + Settings = new + { + Timeout = TimeSpan.FromSeconds(30), + Name = "Config" + }, + Interval = TimeSpan.FromMinutes(5) + }; + + var expectation = new + { + Name = "John", + Settings = new + { + Timeout = TimeSpan.FromSeconds(60), // Different value + Name = "Config" + }, + Interval = TimeSpan.FromMinutes(10) // Different value + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Excluding_applies_to_collections_as_well() + { + // Arrange + var subject = new[] + { + new + { + Name = "John", + Duration = TimeSpan.FromHours(5) + }, + new + { + Name = "Jane", + Duration = TimeSpan.FromHours(3) + } + }; + + var expectation = new[] + { + new + { + Name = "John", + Duration = TimeSpan.FromHours(10) // Different value + }, + new + { + Name = "Jane", + Duration = TimeSpan.FromHours(8) // Different value + } + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Null_is_not_a_valid_type() + { + // Arrange + var subject = new { Name = "John" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(subject, options => options + .Excluding((Type)null)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Reports_the_interface_type() + { + // Arrange + var subject = new + { + Name = "John", + Items = new List { 1, 2, 3 } + }; + + var expectation = new + { + Name = "Jack", + Items = new List { 4, 5, 6 } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options + .Excluding>()); + + // Assert + act.Should().Throw() + .WithMessage("*Exclude members whose type is/derives from*"); + } + + [Fact] + public void Reports_the_open_generic_type() + { + // Arrange + var subject = new + { + Name = "John", + NullableInt = (int?)42 + }; + + var expectation = new + { + Name = "Jack", + NullableInt = (int?)99 + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options + .Excluding(typeof(Nullable<>))); + + // Assert + act.Should().Throw() + .WithMessage("*Exclude members whose type derives from/is a closed generic type of*"); + } + + [Fact] + public void Can_exclude_all_types_deriving_from_an_abstract_type() + { + // Arrange + var subject = new + { + Name = "John", + BaseObject = new ConcreteClass { Value = 10 }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + BaseObject = new ConcreteClass { Value = 20 }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Can_exclude_multiple_types() + { + // Arrange + var subject = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(5), + Score = 100.5 + }; + + var expectation = new + { + Name = "John", + Age = 30, + Duration = TimeSpan.FromHours(10), // Different value + Score = 200.5 // Different value + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding() + .Excluding()); + } + + [Fact] + public void Excluding_string_type_excludes_all_string_members() + { + // Arrange + var subject = new + { + FirstName = "John", + LastName = "Doe", + Age = 30 + }; + + var expectation = new + { + FirstName = "Jane", // Different value + LastName = "Smith", // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Excluding_by_type_with_nested_generics_works() + { + // Arrange + var subject = new + { + Name = "John", + ListOfLists = new List> { new() { 1, 2 } }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + ListOfLists = new List> { new() { 3, 4 } }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding>>()); + } + + [Fact] + public void Can_exclude_value_types() + { + // Arrange + var subject = new + { + Name = "John", + Age = 30, + Score = 100, + Rating = 5 + }; + + var expectation = new + { + Name = "John", + Age = 35, // Different value + Score = 200, // Different value + Rating = 10 // Different value + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Excluding_non_abstract_base_type_excludes_derived_types() + { + // Arrange + var subject = new + { + Name = "John", + BaseObject = new DerivedFromNonAbstract { Value = 10 }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + BaseObject = new DerivedFromNonAbstract { Value = 20 }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [Fact] + public void Excluding_open_generic_excludes_closed_generic_derived_type() + { + // Arrange + var subject = new + { + Name = "John", + ClosedGeneric = new ClosedGeneric { Value = 10 }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + ClosedGeneric = new ClosedGeneric { Value = 20 }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding(typeof(OpenGeneric<>))); + } + + [Fact] + public void Excluding_open_generic_excludes_derived_open_generic_type() + { + // Arrange + var subject = new + { + Name = "John", + DerivedOpenGeneric = new DerivedOpenGeneric { Value = 10 }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + DerivedOpenGeneric = new DerivedOpenGeneric { Value = 20 }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding(typeof(OpenGeneric<>))); + } + + [Fact] + public void Excluding_open_generic_excludes_closed_derived_generic_type() + { + // Arrange + var subject = new + { + Name = "John", + ClosedDerivedGeneric = new ClosedDerivedGeneric { Value = 10 }, + Age = 30 + }; + + var expectation = new + { + Name = "John", + ClosedDerivedGeneric = new ClosedDerivedGeneric { Value = 20 }, // Different value + Age = 30 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding(typeof(OpenGeneric<>))); + } + + [Fact] + public void Excluding_sealed_type_excludes_only_exact_type() + { + // Arrange + var subject = new + { + Name = "John", + Text = "Test", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Text = "Different", // Different value - will be excluded + Age = 30 + }; + + // Act / Assert - string is sealed, so only exact matches are excluded + subject.Should().BeEquivalentTo(expectation, options => options + .Excluding()); + } + + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + private abstract class AbstractBaseClass + { + public int Value { get; set; } + } + + private class ConcreteClass : AbstractBaseClass; + + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + private class NonAbstractBase + { + public int Value { get; set; } + } + + private class DerivedFromNonAbstract : NonAbstractBase; + + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + private class OpenGeneric + { + public int Value { get; set; } + } + + private class ClosedGeneric : OpenGeneric; + + private class DerivedOpenGeneric : OpenGeneric; + + private class ClosedDerivedGeneric : DerivedOpenGeneric; + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs index c43e529ec3..eea424bc19 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs @@ -506,5 +506,23 @@ public void An_anonymous_object_in_combination_with_exclude_selects_nested_field .Match("*Expected*subject.NestedField.FieldB*").And .NotMatch("*Expected*FieldC*FieldD*FieldE*"); } + + [Fact] + public void Including_a_member_by_path_on_a_type_with_value_semantics_fails_with_a_descriptive_error() + { + // Arrange + var actual = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "x" }; + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "same", NestedProperty = "y" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, + opt => opt.Including(o => o.Key)); + + // Assert + act.Should().Throw() + .WithMessage( + "*ClassWithValueSemanticsOnSingleProperty*compared by value*overrides Equals*" + + "*ComparingByMembers*"); + } } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs index 617f4fc276..dad037e49c 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs @@ -31,4 +31,15 @@ public void Tracing_must_be_safe_when_executed_concurrently() AssertionEngine.ResetToDefaults(); } } + + [Fact] + public void Can_trace_the_internals() + { + // Act + var act = () => new { A = "a" }.Should().BeEquivalentTo(new { A = "b" }, opt => opt.WithTracing()); + + // Assert + act.Should().Throw() + .WithMessage("*With trace:*"); + } } diff --git a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs index 8d6e287d63..c5e037e1a2 100644 --- a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs @@ -9,6 +9,7 @@ using FluentAssertions.Primitives; using FluentAssertions.Specialized; using FluentAssertions.Types; +using Reflectify; using Xunit; namespace FluentAssertions.Specs; @@ -162,8 +163,13 @@ public void Should_methods_have_a_matching_overload_to_guard_against_chaining_an [MemberData(nameof(GetShouldMethods), true)] public void Should_methods_returning_reference_or_nullable_type_assertions_are_annotated_with_not_null_attribute(MethodInfo method) { - var notNullAttribute = method.GetParameters().Single().GetCustomAttribute(); - notNullAttribute.Should().NotBeNull(); + ParameterInfo parameter = method.GetParameters().Single(); + + Type type = parameter.ParameterType; + + AssertionChain.GetOrCreate() + .ForCondition(type.IsRefStruct() || type.IsStruct() || parameter.HasAttribute()) + .FailWith($"Should method '{method}' should have [NotNull] attribute for parameter '{parameter.Name}'"); } [Theory] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEqualTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEqualTo.cs new file mode 100644 index 0000000000..c2b930f2e6 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEqualTo.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +/// +/// The [Not]BeEqualTo specs. +/// +public partial class CollectionAssertionSpecs +{ + public class BeEqualTo + { + [Fact] + public void Two_collections_with_the_same_elements_in_the_same_order_are_okay() + { + // Arrange + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 3]; + + // Act / Assert + collection1.Should().BeEqualTo(collection2); + } + + [Fact] + public void Two_null_collections_are_okay() + { + // Arrange + int[] nullColl = null; + + // Act + Action act = () => nullColl.Should().BeEqualTo(null); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void Fails_with_a_descriptive_message_for_a_differing_item() + { + // Arrange + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 5]; + + // Act + Action act = () => collection1.Should().BeEqualTo(collection2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1 to be equal to {1, 2, 5} because we want to test the failure message, but {1, 2, 3} differs at index 2."); + } + + [Fact] + public void Fails_for_a_null_subject_collection() + { + // Arrange + int[] collection = null; + int[] collection1 = [1, 2, 3]; + + // Act + Action act = () => + collection.Should().BeEqualTo(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to be equal to {1, 2, 3} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void Throws_for_a_null_expected_collection() + { + // Arrange + int[] collection = [1, 2, 3]; + int[] collection1 = null; + + // Act + Action act = () => + collection.Should().BeEqualTo(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare collection with .*") + .WithParameterName("expectation"); + } + + [Fact] + public void Succeeds_using_custom_equality_comparison() + { + // Arrange + var actual = new List { "ONE", "TWO", "THREE", "FOUR" }; + + var expected = new[] + { + new { Value = "One" }, + new { Value = "Two" }, + new { Value = "Three" }, + new { Value = "Four" } + }; + + // Act + Action action = () => actual.Should().BeEqualTo(expected, + (a, e) => string.Equals(a, e.Value, StringComparison.OrdinalIgnoreCase)); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Fails_using_custom_equality_comparison_for_a_differing_item() + { + // Arrange + var actual = new List { "ONE", "TWO", "THREE", "FOUR" }; + + var expected = new[] + { + new { Value = "One" }, + new { Value = "Two" }, + new { Value = "Three" }, + new { Value = "Five" } + }; + + // Act + Action action = () => actual.Should().BeEqualTo(expected, + (a, e) => string.Equals(a, e.Value, StringComparison.OrdinalIgnoreCase)); + + // Assert + action + .Should().Throw() + .WithMessage("*Expected*equal to*, but*differs at index 3.*"); + } + + [Fact] + public void Succeeds_for_params_with_same_elements_in_same_order() + { + // Arrange + int[] collection = [1, 2, 3]; + + // Act / Assert + collection.Should().BeEqualTo(1, 2, 3); + } + + [Fact] + public void Fails_for_params_with_a_differing_element() + { + // Arrange + int[] collection = [1, 2, 3]; + + // Act + Action act = () => collection.Should().BeEqualTo(1, 2, 5); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to be equal to {1, 2, 5}, but {1, 2, 3} differs at index 2."); + } + } + + public class NotBeEqualTo + { + [Fact] + public void Succeeds_for_collections_with_different_elements() + { + // Arrange + int[] collection1 = [1, 2, 3]; + int[] collection2 = [3, 1, 2]; + + // Act / Assert + collection1.Should().NotBeEqualTo(collection2); + } + + [Fact] + public void Fails_for_collections_with_the_same_elements_in_the_same_order() + { + // Arrange + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 3]; + + // Act + Action act = () => collection1.Should().NotBeEqualTo(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect collections {1, 2, 3} and {1, 2, 3} to be equal."); + } + + [Fact] + public void Throws_for_a_null_unexpected_collection() + { + // Arrange + int[] collection1 = null; + int[] collection2 = null; + + // Act + Action act = () => + collection1.Should().NotBeEqualTo(collection2, + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare collection with .*") + .WithParameterName("unexpected"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs index 60a28858db..f094c3ffdc 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs @@ -58,7 +58,7 @@ public void When_collections_are_not_equivalent_it_should_throw() // Assert act.Should().Throw().WithMessage( - "Expected*collection*2 item(s)*we treat all alike, but *1 item(s) more than*"); + "Expected collection1*2 items*we treat all alike, but*extraneous item*3*"); } [Fact] @@ -88,7 +88,7 @@ public void When_testing_for_equivalence_against_empty_collection_it_should_thro // Assert act.Should().Throw().WithMessage( - "Expected subject to be a collection with 0 item(s), but*contains 3 item(s)*"); + "Expected subject to be an empty collection, but found extraneous items*1*2*3*"); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEqualTo.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEqualTo.cs new file mode 100644 index 0000000000..898a9db58c --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEqualTo.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class BeEqualTo + { + [Fact] + public void Succeeds_for_collections_with_same_elements_in_same_order() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two", "three"]; + + // Act / Assert + collection1.Should().BeEqualTo(collection2); + } + + [Fact] + public void Succeeds_for_params_with_same_elements_in_same_order() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().BeEqualTo("one", "two", "three"); + } + + [Fact] + public void Fails_for_params_with_a_differing_element() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + System.Action act = () => collection.Should().BeEqualTo("one", "two", "five"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to be equal to {\"one\", \"two\", \"five\"}, but {\"one\", \"two\", \"three\"} differs at index 2."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs index d23523d701..dec6a71f34 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs @@ -53,7 +53,7 @@ public void When_testing_for_equivalence_against_empty_collection_it_should_thro // Assert act.Should().Throw().WithMessage( - "Expected subject*to be a collection with 0 item(s), but*contains 3 item(s)*"); + "Expected subject to be an empty collection, but found extraneous items*one*two*three*"); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/SpanAndMemorySpecs.cs b/Tests/FluentAssertions.Specs/Collections/SpanAndMemorySpecs.cs new file mode 100644 index 0000000000..cdcaf72acc --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/SpanAndMemorySpecs.cs @@ -0,0 +1,350 @@ +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public class SpanAndMemorySpecs +{ + public class GeneralPurposeCollections + { + [Fact] + public void A_span_supports_all_collection_assertions() + { + // Arrange + Span span = new[] { 1, 2, 3 }; + + // Act / Assert + span.Should().HaveCount(3) + .And.Contain(2) + .And.NotContain(4) + .And.StartWith(1) + .And.EndWith(3) + .And.BeInAscendingOrder(); + } + + [Fact] + public void A_readonly_span_supports_all_collection_assertions() + { + // Arrange + ReadOnlySpan span = [1, 2, 3]; + + // Act / Assert + span.Should().HaveCount(3) + .And.Contain(2) + .And.NotContain(4) + .And.StartWith(1) + .And.EndWith(3) + .And.BeInAscendingOrder(); + } + + [Fact] + public void A_memory_object_supports_all_collection_assertions() + { + // Arrange + Memory memory = new[] { 1, 2, 3 }; + + // Act / Assert + memory.Should().HaveCount(3) + .And.Contain(2) + .And.NotContain(4) + .And.StartWith(1) + .And.EndWith(3) + .And.BeInAscendingOrder(); + } + + [Fact] + public void A_readonly_memory_object_supports_all_collection_assertions() + { + // Arrange + ReadOnlyMemory memory = new[] { 1, 2, 3 }; + + // Act / Assert + memory.Should().HaveCount(3) + .And.Contain(2) + .And.NotContain(4) + .And.StartWith(1) + .And.EndWith(3) + .And.BeInAscendingOrder(); + } + + [Fact] + public void A_readonly_span_of_strings_supports_string_collection_assertions() + { + // Arrange + string[] array = ["one", "two", "three"]; + ReadOnlySpan span = new(array); + + // Act / Assert + span.Should().HaveCount(3) + .And.Contain("two") + .And.NotContain("four") + .And.StartWith("one") + .And.EndWith("three") + .And.ContainMatch("o*"); + } + + [Fact] + public void An_empty_span_supports_assertions() + { + // Arrange + Span span = []; + + // Act / Assert + span.Should().BeEmpty() + .And.HaveCount(0) + .And.NotContain(1); + } + + [Fact] + public void An_empty_readonly_span_supports_assertions() + { + // Arrange + ReadOnlySpan span = []; + + // Act / Assert + span.Should().BeEmpty() + .And.HaveCount(0) + .And.NotContain(1); + } + + [Fact] + public void An_empty_memory_supports_assertions() + { + // Arrange + Memory memory = Array.Empty(); + + // Act / Assert + memory.Should().BeEmpty() + .And.HaveCount(0) + .And.NotContain(1); + } + + [Fact] + public void An_empty_readonly_memory_supports_assertions() + { + // Arrange + ReadOnlyMemory memory = Array.Empty(); + + // Act / Assert + memory.Should().BeEmpty() + .And.HaveCount(0) + .And.NotContain(1); + } + + [Fact] + public void A_span_of_strings_supports_string_collection_assertions() + { + // Arrange + Span span = ["one", "two", "three"]; + + // Act / Assert + span.Should().HaveCount(3) + .And.Contain("two") + .And.NotContain("four") + .And.ContainMatch("t*") + .And.OnlyContain(s => s.Length >= 3); + } + + [Fact] + public void Chaining_on_a_span_is_supported() + { + // Arrange + Span span = new[] { 1, 2, 3 }; + + // Act / Assert + span.Should().HaveCount(3).And.Contain(2); + } + } + + public class Equality + { + [Fact] + public void A_span_of_integers_can_be_asserted_for_equality() + { + // Arrange + Span span = [1, 2, 3]; + + // Act / Assert + span.Should().Equal(1, 2, 3); + span.Should().BeEqualTo([1, 2, 3]); + } + + [Fact] + public void A_readonly_span_of_integers_can_be_asserted_for_equality() + { + // Arrange + ReadOnlySpan span = [1, 2, 3]; + + // Act / Assert + span.Should().Equal(1, 2, 3); + span.Should().BeEqualTo([1, 2, 3]); + } + + [Fact] + public void A_span_be_equal_to_accepts_span() + { + // Arrange + Span span = [1, 2, 3]; + Span expected = [1, 2, 3]; + + // Act / Assert + span.Should().BeEqualTo(expected); + } + + [Fact] + public void A_span_be_equal_to_accepts_readonly_span() + { + // Arrange + Span span = [1, 2, 3]; + ReadOnlySpan expected = [1, 2, 3]; + + // Act / Assert + span.Should().BeEqualTo(expected); + } + + [Fact] + public void A_readonly_span_be_equal_to_accepts_readonly_span() + { + // Arrange + ReadOnlySpan span = [1, 2, 3]; + ReadOnlySpan expected = [1, 2, 3]; + + // Act / Assert + span.Should().BeEqualTo(expected); + } + + [Fact] + public void A_span_of_integers_be_equal_to_should_throw_when_values_differ() + { + // Arrange + Span span = [1, 2, 3]; + + try + { + // Act + span.Should().BeEqualTo([1, 2, 4], "because {0}", "reasons"); + } + catch (XunitException e) + { + // Assert + e.Message.Should().Be( + "Expected span to be equal to {1, 2, 4} because reasons, but {1, 2, 3} differs at index 2."); + return; + } + + throw new XunitException("This point should not be reached."); + } + + [Fact] + public void A_readonly_span_of_integers_be_equal_to_should_throw_when_values_differ() + { + // Arrange + ReadOnlySpan span = [1, 2, 3]; + + try + { + // Act + span.Should().BeEqualTo([1, 2, 4]); + } + catch (XunitException e) + { + // Assert + e.Message.Should().Be( + "Expected span to be equal to {1, 2, 4}, but {1, 2, 3} differs at index 2."); + return; + } + + throw new XunitException("This point should not be reached."); + } + } + + public class Strings + { + [Fact] + public void A_span_of_chars_can_be_asserted_using_a_string() + { + // Arrange + Span span = ['a', 'b', 'c']; + + // Act / Assert + span.Should().Be("abc"); + } + + [Fact] + public void A_readonly_span_of_chars_can_be_asserted_using_a_string() + { + // Arrange + ReadOnlySpan span = ['a', 'b', 'c']; + + // Act / Assert + span.Should().Be("abc"); + } + + [Fact] + public void A_span_of_chars_be_should_throw_when_values_differ() + { + // Arrange + Span span = ['a', 'b', 'c']; + + try + { + // Act + span.Should().Be("abd", "because {0}", "reasons"); + } + catch (XunitException e) + { + // Assert + e.Message.Should().Match("Expected span to be \"abd\" because reasons, but*"); + return; + } + + throw new XunitException("This point should not be reached."); + } + + [Fact] + public void A_readonly_span_of_chars_be_should_throw_when_values_differ() + { + // Arrange + ReadOnlySpan span = ['a', 'b', 'c']; + + try + { + // Act + span.Should().Be("abd"); + } + catch (XunitException e) + { + // Assert + e.Message.Should().Match("Expected span to be \"abd\", but*"); + return; + } + + throw new XunitException("This point should not be reached."); + } + + [Fact] + public void A_span_of_chars_be_should_throw_when_expected_is_null() + { + // Arrange + Span span = ['a', 'b', 'c']; + + try + { + // Act + span.Should().Be(null!); + } + catch (ArgumentNullException e) + { + // Assert + e.ParamName.Should().Be("expected"); + return; + } + + throw new XunitException("This point should not be reached."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs index 72c2e297b4..08a138a7ba 100644 --- a/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs @@ -210,7 +210,9 @@ private record struct MyRecordStruct(int Value); private record struct MyRecordStructWithCustomPrintMembers(int Value) { // ReSharper disable once RedundantNameQualifier +#pragma warning disable IDE0001 private bool PrintMembers(System.Text.StringBuilder builder) +#pragma warning restore IDE0001 { builder.Append(Value); return true; diff --git a/Tests/FluentAssertions.Specs/Configuration/EquivalencyOptionsSpecs.cs b/Tests/FluentAssertions.Specs/Configuration/EquivalencyOptionsSpecs.cs index 4e896783cf..3c6ee841df 100644 --- a/Tests/FluentAssertions.Specs/Configuration/EquivalencyOptionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Configuration/EquivalencyOptionsSpecs.cs @@ -28,16 +28,11 @@ public void When_injecting_a_null_configurer_it_should_throw() public void When_concurrently_getting_equality_strategy_it_should_not_throw() { // Arrange / Act - var action = () => - { -#pragma warning disable CA1859 // https://github.com/dotnet/roslyn-analyzers/issues/6704 - IEquivalencyOptions equivalencyOptions = new EquivalencyOptions(); -#pragma warning restore CA1859 - - return () => Parallel.For(0, 10_000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, - _ => equivalencyOptions.GetEqualityStrategy(typeof(IEnumerable)) - ); - }; + IEquivalencyOptions equivalencyOptions = new EquivalencyOptions(); + + var action = () => Parallel.For(0, 10_000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, + _ => equivalencyOptions.GetEqualityStrategy(typeof(IEnumerable)) + ); // Assert action.Should().NotThrow(); diff --git a/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs index ebf4a2aa4e..e2a091388e 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs @@ -47,7 +47,6 @@ public void When_validating_a_subject_against_multiple_conditions_it_should_supp // Act / Assert testSubject - .Should().Throw() .WithInnerException() .WithMessage("inner message"); @@ -199,4 +198,22 @@ public void When_a_method_throws_with_a_non_matching_parameter_name_it_should_fa act.Should().Throw() .WithMessage("*with parameter name \"someParameter\"*we want to test the failure message*\"someOtherParameter\"*"); } + + [Fact] + public void Reports_the_exception_type_for_chained_assertions() + { + // Arrange + Action throwingAction = () => throw new SomeException(); + + // Act + var act = () => throwingAction.Should().Throw().Which.Property.Should().Be("Value"); + + // Assert + act.Should().Throw().WithMessage("*SomeException*Value*"); + } + + private class SomeException : Exception + { + public string Property { get; } = "OtherValue"; + } } diff --git a/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs index d5682199fe..d1b54f9830 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs @@ -51,7 +51,6 @@ public void Long_exception_messages_are_rendered_over_multiple_lines() { // Act testSubject - .Should().Throw() .WithMessage(new string('#', 101)); diff --git a/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj b/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj index 11a0bcabd9..eb96bd99bf 100644 --- a/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj +++ b/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj @@ -25,13 +25,13 @@ - + all runtime; build; native; contentfiles; analyzers - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs index b21bf168f6..bfe7c1ecb5 100644 --- a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs @@ -385,7 +385,7 @@ public void int maxDepth = 10; int iterations = (maxDepth / 2) + 1; // Each iteration adds two levels of depth to the graph - foreach (int i in Enumerable.Range(0, iterations)) + for (var i = 0; i < iterations; i++) { var newHead = new Node(); node.Children.Add(newHead); @@ -410,8 +410,9 @@ public void When_the_maximum_recursion_depth_is_never_reached_it_should_render_t var node = head; int iterations = 10; + int maxDepth = (iterations * 2) + 1; // Each iteration adds two levels of depth to the graph - foreach (int i in Enumerable.Range(0, iterations)) + for (var i = 0; i < iterations; i++) { var newHead = new Node(); node.Children.Add(newHead); @@ -421,8 +422,7 @@ public void When_the_maximum_recursion_depth_is_never_reached_it_should_render_t // Act string result = Formatter.ToString(head, new FormattingOptions { - // Each iteration adds two levels of depth to the graph - MaxDepth = (iterations * 2) + 1 + MaxDepth = maxDepth }); // Assert diff --git a/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs index f8ac7d281e..88f693d41b 100644 --- a/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs @@ -95,12 +95,29 @@ public void When_condition_contains_linq_extension_method_then_extension_method_ int[] allowed = [1, 2, 3]; // Act - string result = Format(a => allowed.Contains(a)); + string result = Format(a => Enumerable.Contains(allowed, a)); // Assert result.Should().Be("value(System.Int32[]).Contains(a)"); } +#if NET6_0_OR_GREATER +#pragma warning disable RCS1196 // Do not call MemoryExtensions.Contains as extension method. This is to exercise first-class spans + [Fact] + public void Methods_using_ReadOnlySpan_can_be_formatted() + { + // Arrange + int[] allowed = [1, 2, 3]; + + // Act + string result = Format(a => MemoryExtensions.Contains(allowed, a)); + + // Assert + result.Should().Match("*.Contains(a)"); + } +#pragma warning restore RCS1196 +#endif + [Fact] public void Formatting_a_lifted_binary_operator() { diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMillisecond.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMillisecond.cs new file mode 100644 index 0000000000..95d335228d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMillisecond.cs @@ -0,0 +1,90 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveMillisecond + { + [Fact] + public void Succeeds_for_datetime_with_the_same_milliseconds() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00, 123); + + // Act / Assert + subject.Should().HaveMillisecond(123); + } + + [Fact] + public void Fails_for_datetime_with_different_milliseconds() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00, 123); + + // Act + Action act = () => subject.Should().HaveMillisecond(124); + + // Assert + act.Should().Throw() + .WithMessage("Expected the milliseconds part of subject to be 124, but found 123."); + } + + [Fact] + public void Fails_for_null_datetime() + { + // Arrange + DateTime? subject = null; + + // Act + Action act = () => subject.Should().HaveMillisecond(22); + + // Assert + act.Should().Throw() + .WithMessage("Expected the milliseconds part of subject to be 22, but found a DateTime."); + } + } + + public class NotHaveMillisecond + { + [Fact] + public void Fails_for_datetime_with_the_same_milliseconds() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00, 123); + + // Act + Action act = () => subject.Should().NotHaveMillisecond(123); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the milliseconds part of subject to be 123, but it was."); + } + + [Fact] + public void Succeeds_for_datetime_with_different_milliseconds() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00, 123); + + // Act / Assert + subject.Should().NotHaveMillisecond(124); + } + + [Fact] + public void Fails_for_null_datetime() + { + // Arrange + DateTime? subject = null; + + // Act + Action act = () => subject.Should().NotHaveMillisecond(22); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the milliseconds part of subject to be 22, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMillisecond.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMillisecond.cs new file mode 100644 index 0000000000..6c103b0c26 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMillisecond.cs @@ -0,0 +1,90 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveMillisecond + { + [Fact] + public void Succeeds_for_datetimeoffset_with_the_same_milliseconds() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00, 123), TimeSpan.Zero); + + // Act / Assert + subject.Should().HaveMillisecond(123); + } + + [Fact] + public void Fails_for_datetimeoffset_with_different_milliseconds() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00, 123), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().HaveMillisecond(124); + + // Assert + act.Should().Throw() + .WithMessage("Expected the milliseconds part of subject to be 124, but it was 123."); + } + + [Fact] + public void Fails_for_null_datetimeoffset() + { + // Arrange + DateTimeOffset? subject = null; + + // Act + Action act = () => subject.Should().HaveMillisecond(22); + + // Assert + act.Should().Throw() + .WithMessage("Expected the milliseconds part of subject to be 22, but found a DateTimeOffset."); + } + } + + public class NotHaveMillisecond + { + [Fact] + public void Fails_for_datetimeoffset_with_the_same_milliseconds() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00, 123), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotHaveMillisecond(123); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the milliseconds part of subject to be 123, but it was."); + } + + [Fact] + public void Succeeds_for_datetimeoffset_with_different_milliseconds() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00, 123), TimeSpan.Zero); + + // Act / Assert + subject.Should().NotHaveMillisecond(124); + } + + [Fact] + public void Fails_for_null_datetimeoffset() + { + // Arrange + DateTimeOffset? subject = null; + + // Act + Action act = () => subject.Should().NotHaveMillisecond(22); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the milliseconds part of subject to be 22, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs index 6cdee35670..b70400df2f 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs @@ -128,6 +128,7 @@ public class DataContractSerializableClass [DataContract] public class DataContractSerializableClassNotRestoringAllProperties { + [UsedImplicitly] public string Name { get; set; } [DataMember] @@ -140,6 +141,7 @@ public class DataContractSerializableClassWithIgnoredDataMember [DataMember] public string Name { get; set; } + [UsedImplicitly] [IgnoreDataMember] public decimal CachedSum { get; set; } } diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs index 6aef7a65e2..9c83a6699c 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs @@ -118,8 +118,10 @@ public void WriteXml(XmlWriter writer) public class XmlSerializableClassWithIgnoredProperties { + [UsedImplicitly] public string Name { get; set; } + [UsedImplicitly] [XmlIgnore] public decimal CachedSum { get; set; } @@ -133,13 +135,16 @@ public class XmlSerializableClassWithIgnoredProperties public class R { + [UsedImplicitly] public Q A { get; set; } + [UsedImplicitly] public Q B { get; set; } } public class Q { + [UsedImplicitly] [XmlIgnore] public string Flarbs { get; set; } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs index ad21556083..d81d007f8e 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs @@ -391,6 +391,38 @@ public void Will_include_the_full_subject_and_expectation_for_long_truncated_str "The streets were quiet except for the faint hum of neon signs flickering in the night. A cat darted across the alley, chasing shadows only it could see. Somewhere, a radio whispered a half-forgotten song, mixing with the distant rumble of a train. The air carried the scent of rain on warm asphalt, heavy and restless. Each step felt like moving between moments—time stretching, folding, and twisting—while the city itself seemed to wait, holding its breath for what might come next." """); } + + [Fact] + public void When_string_contains_opening_brace_it_should_not_throw_format_exception() + { + // Arrange - create a string longer than the default truncation length (80 chars) + // with an opening brace to trigger the full details display which had the bug + const int lengthToTriggerFullDetails = 80; + var actual = "{" + new string('x', lengthToTriggerFullDetails); + var expected = ""; + + // Act + Action act = () => actual.Should().Be(expected); + + // Assert - should throw XunitException, not FormatException + act.Should().Throw() + .WithMessage("*Expected*to be a match*"); + } + + [Fact] + public void When_long_string_contains_braces_it_should_display_properly() + { + // Arrange + const string subject = "public class Test { public void Method() { var x = 1; } }"; + const string expected = "public class Test { public void Method() { var x = 2; } }"; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw() + .WithMessage("*Expected*to be a match*"); + } } public class NotBe diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs index 46787cf393..a073f2a060 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs @@ -375,4 +375,118 @@ public void When_the_actual_string_equivalent_to_the_expected_but_with_trailing_ act.Should().NotThrow(); } } + + public class ComparingNullStringsAsEmpty + { + [Fact] + public void A_null_subject_string_is_equivalent_to_an_empty_expectation_string() + { + // Arrange + string subject = null; + string expectation = ""; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + } + + [Fact] + public void An_empty_subject_string_is_equivalent_to_a_null_expectation_string() + { + // Arrange + string subject = ""; + string expectation = null; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + } + + [Fact] + public void Two_null_strings_are_equivalent() + { + // Arrange + string subject = null; + string expectation = null; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + } + + [Fact] + public void A_null_subject_string_fails_when_the_expectation_is_non_empty() + { + // Arrange + string subject = null; + string expectation = "hello"; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_non_empty_subject_string_fails_when_the_expectation_is_null() + { + // Arrange + string subject = "hello"; + string expectation = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Without_the_option_a_null_subject_string_is_not_equivalent_to_an_empty_expectation_string() + { + // Arrange + string subject = null; + string expectation = ""; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Without_the_option_an_empty_subject_string_is_not_equivalent_to_a_null_expectation_string() + { + // Arrange + string subject = ""; + string expectation = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_null_nested_string_is_equivalent_to_an_empty_nested_string() + { + // Arrange + var subject = new { Name = (string)null }; + var expectation = new { Name = "" }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + } + + [Fact] + public void An_empty_nested_string_is_equivalent_to_a_null_nested_string() + { + // Arrange + var subject = new { Name = "" }; + var expectation = new { Name = (string)null }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt.ComparingNullStringsAsEmpty()); + } + } } diff --git a/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs index d0f632baa4..7277d16dc1 100644 --- a/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs @@ -63,7 +63,7 @@ public void When_the_execution_time_of_an_action_is_less_than_or_equal_to_a_limi Action someAction = () => Thread.Sleep(100); // Act - Action act = () => someAction.ExecutionTime().Should().BeLessThanOrEqualTo(1.Seconds()); + Action act = () => someAction.ExecutionTime().Should().BeLessThanOrEqualTo(2.Seconds()); // Assert act.Should().NotThrow(); diff --git a/Tests/FluentAssertions.Specs/StackTraceHiddenSpecs.cs b/Tests/FluentAssertions.Specs/StackTraceHiddenSpecs.cs new file mode 100644 index 0000000000..4d56a1ad5b --- /dev/null +++ b/Tests/FluentAssertions.Specs/StackTraceHiddenSpecs.cs @@ -0,0 +1,88 @@ +#if NET6_0_OR_GREATER +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using FluentAssertions.Equivalency; +using FluentAssertions.Types; +using Reflectify; +using Xunit; + +namespace FluentAssertions.Specs; + +public class StackTraceHiddenSpecs +{ + [Fact] + public void All_non_assertion_classes_have_their_stack_trace_hidden() + { + // Arrange / Act + var allFluentAssertionsClasses = GetAnnotatableTypes() + .Except(ShouldNotBeAnnotated()) + .Types(); + + // Assert + allFluentAssertionsClasses.Should().BeDecoratedWith( + "because all \"internal\" code should be hidden from the stacktrace to improve the debugging experience"); + } + + [Fact] + public void Assertion_classes_do_not_have_their_stack_trace_hidden() + { + // Arrange / Act + var allFluentAssertionsClasses = ShouldNotBeAnnotated(); + + // Assert + allFluentAssertionsClasses.Should().NotBeDecoratedWith( + "because we do want to see the actual assertion method being used"); + } + + private static TypeSelector GetAnnotatableTypes() + { + return AllTypes + .From(typeof(FluentAssertions.AssertionExtensions).Assembly) + .ThatAreUnderNamespace("FluentAssertions") + .ThatAreClasses() + .ThatSatisfy(t => !t.IsNested) + .ThatSatisfy(t => !t.IsSameOrInherits()) + .ThatSatisfy(t => !t.IsCompilerGenerated() && !t.HasAttribute()); + } + + private static TypeSelector ShouldNotBeAnnotated() + { + string[] typeNameSuffixes = + [ + "Assertions", + "AssertionsBase", + "AssertionExtensions", + "AssertionsExtensions", + "DateTimeExtensions", + "EventRaisingExtensions", + "FluentDateTimeExtensions", + "FluentTimeSpanExtensions", + "OccurrenceConstraintExtensions", + "Common.TypeExtensions", + "TypeEnumerableExtensions", + "FluentAssertions.TypeExtensions", + ]; + + string[] namespaces = + [ + "FluentAssertions.CallerIdentification", + "FluentAssertions.Collections.MaximumMatching", + "FluentAssertions.Equivalency.Matching", + "FluentAssertions.Equivalency.Ordering", + "FluentAssertions.Equivalency.Selection", + "FluentAssertions.Equivalency.Tracing", + "FluentAssertions.Formatting", + ]; + + return GetAnnotatableTypes() + .ThatSatisfy(t => + t == typeof(EquivalencyOptions) || + typeNameSuffixes.Any(bt => bt == t.FullName || t.GetNonGenericName().EndsWith(bt, StringComparison.Ordinal)) || + namespaces.Any(ns => t?.Namespace?.StartsWith(ns, StringComparison.Ordinal) is true) + ); + } + +} +#endif diff --git a/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj b/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj index c0c4b31b65..75894fb995 100644 --- a/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj +++ b/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj @@ -12,7 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Tests/TestFrameworks/MSTestV4.Specs/MSTestV4.Specs.csproj b/Tests/TestFrameworks/MSTestV4.Specs/MSTestV4.Specs.csproj index b815e2e765..dc27b69b7e 100644 --- a/Tests/TestFrameworks/MSTestV4.Specs/MSTestV4.Specs.csproj +++ b/Tests/TestFrameworks/MSTestV4.Specs/MSTestV4.Specs.csproj @@ -12,7 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj b/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj index 37882581c8..e1bc90c220 100644 --- a/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj +++ b/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj b/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj index 80dae3cc6e..38663aa3a4 100644 --- a/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj +++ b/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj @@ -12,8 +12,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj b/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj index 8ca10b1e23..ae89b6c1ae 100644 --- a/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj +++ b/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj @@ -11,9 +11,7 @@ - - - + diff --git a/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj b/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj index 4459530fcf..c7c2d290ca 100644 --- a/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj +++ b/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj @@ -9,7 +9,7 @@ - - + + diff --git a/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj b/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj index 1458da7a3a..8def8827c2 100644 --- a/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj +++ b/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj @@ -9,7 +9,7 @@ - - + + diff --git a/Tests/UWP.Specs/App.xaml b/Tests/UWP.Specs/App.xaml deleted file mode 100644 index 0d5a0c69ec..0000000000 --- a/Tests/UWP.Specs/App.xaml +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/Tests/UWP.Specs/App.xaml.cs b/Tests/UWP.Specs/App.xaml.cs deleted file mode 100644 index 768f758892..0000000000 --- a/Tests/UWP.Specs/App.xaml.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.TestExecutor; -using Windows.ApplicationModel; -using Windows.ApplicationModel.Activation; -using Windows.UI.Xaml; - -namespace UWP.Specs; - -internal sealed partial class App : Application -{ - public App() - { - InitializeComponent(); - Suspending += OnSuspending; - } - - protected override void OnLaunched(LaunchActivatedEventArgs args) - { - UnitTestClient.Run(args.Arguments); - } - - private void OnSuspending(object sender, SuspendingEventArgs e) => - e.SuspendingOperation.GetDeferral().Complete(); -} diff --git a/Tests/UWP.Specs/Assets/LockScreenLogo.scale-200.png b/Tests/UWP.Specs/Assets/LockScreenLogo.scale-200.png deleted file mode 100644 index 735f57adb5..0000000000 Binary files a/Tests/UWP.Specs/Assets/LockScreenLogo.scale-200.png and /dev/null differ diff --git a/Tests/UWP.Specs/Assets/SplashScreen.scale-200.png b/Tests/UWP.Specs/Assets/SplashScreen.scale-200.png deleted file mode 100644 index 023e7f1fed..0000000000 Binary files a/Tests/UWP.Specs/Assets/SplashScreen.scale-200.png and /dev/null differ diff --git a/Tests/UWP.Specs/Assets/Square150x150Logo.scale-200.png b/Tests/UWP.Specs/Assets/Square150x150Logo.scale-200.png deleted file mode 100644 index af49fec1a5..0000000000 Binary files a/Tests/UWP.Specs/Assets/Square150x150Logo.scale-200.png and /dev/null differ diff --git a/Tests/UWP.Specs/Assets/Square44x44Logo.scale-200.png b/Tests/UWP.Specs/Assets/Square44x44Logo.scale-200.png deleted file mode 100644 index ce342a2ec8..0000000000 Binary files a/Tests/UWP.Specs/Assets/Square44x44Logo.scale-200.png and /dev/null differ diff --git a/Tests/UWP.Specs/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Tests/UWP.Specs/Assets/Square44x44Logo.targetsize-24_altform-unplated.png deleted file mode 100644 index f6c02ce97e..0000000000 Binary files a/Tests/UWP.Specs/Assets/Square44x44Logo.targetsize-24_altform-unplated.png and /dev/null differ diff --git a/Tests/UWP.Specs/Assets/StoreLogo.png b/Tests/UWP.Specs/Assets/StoreLogo.png deleted file mode 100644 index 7385b56c0e..0000000000 Binary files a/Tests/UWP.Specs/Assets/StoreLogo.png and /dev/null differ diff --git a/Tests/UWP.Specs/Assets/Wide310x150Logo.scale-200.png b/Tests/UWP.Specs/Assets/Wide310x150Logo.scale-200.png deleted file mode 100644 index 288995b397..0000000000 Binary files a/Tests/UWP.Specs/Assets/Wide310x150Logo.scale-200.png and /dev/null differ diff --git a/Tests/UWP.Specs/Directory.Build.props b/Tests/UWP.Specs/Directory.Build.props deleted file mode 100644 index 0d251bdbd1..0000000000 --- a/Tests/UWP.Specs/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - 13.0 - false - true - - diff --git a/Tests/UWP.Specs/Package.appxmanifest b/Tests/UWP.Specs/Package.appxmanifest deleted file mode 100644 index bbe9c9803a..0000000000 --- a/Tests/UWP.Specs/Package.appxmanifest +++ /dev/null @@ -1,25 +0,0 @@ - - - - - UWP.Specs - FluentAssertions - Assets\StoreLogo.png - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/UWP.Specs/Properties/AssemblyInfo.cs b/Tests/UWP.Specs/Properties/AssemblyInfo.cs deleted file mode 100644 index bccfd85910..0000000000 --- a/Tests/UWP.Specs/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("UWP.Specs")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UWP.Specs")] -[assembly: AssemblyCopyright("Copyright © 2022")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: AssemblyMetadata("TargetPlatform", "UAP")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: ComVisible(false)] diff --git a/Tests/UWP.Specs/Properties/Default.rd.xml b/Tests/UWP.Specs/Properties/Default.rd.xml deleted file mode 100644 index f3d2603c7e..0000000000 --- a/Tests/UWP.Specs/Properties/Default.rd.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Tests/UWP.Specs/UWP.Specs.csproj b/Tests/UWP.Specs/UWP.Specs.csproj deleted file mode 100644 index 3dd53810ee..0000000000 --- a/Tests/UWP.Specs/UWP.Specs.csproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - Debug - x86 - {08087654-2C32-4818-95E4-45362373441D} - AppContainerExe - Properties - UWP.Specs - UWP.Specs - en-US - UAP - 10.0.19041.0 - 10.0.18362.0 - 14 - 512 - {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - $(VisualStudioVersion) - false - $(NoWarn);2008 - true - true - 17.3.3 - - - true - bin\x64\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - full - x64 - false - prompt - true - - - bin\x64\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - pdbonly - x64 - false - prompt - true - - - PackageReference - - - - - - - - App.xaml - - - - - - MSBuild:Compile - Designer - - - - - Designer - - - - - - - - - - - - - - - Src\FluentAssertions\bin\$(Configuration)\netstandard2.0\FluentAssertions.dll - {34E3713D-C02F-4868-BBE7-47DAD2C7F03D} - FluentAssertions - - - - - 6.2.14 - - - 3.10.4 - - - 3.10.4 - - - 4.7.0 - - - - \ No newline at end of file diff --git a/Tests/UWP.Specs/UwpSpecs.cs b/Tests/UWP.Specs/UwpSpecs.cs deleted file mode 100644 index e21dce7d8d..0000000000 --- a/Tests/UWP.Specs/UwpSpecs.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace UWP.Specs; - -[TestClass] -public class UwpSpecs -{ - [TestMethod] - public void Determining_caller_identity_should_not_throw_for_native_programs() - { - // Arrange - Action someAction = () => throw new Exception(); - - // Act - Action act = () => someAction.Should().NotThrow(); - - // Assert - act.Should().Throw(); - } -} diff --git a/Tests/VB.Specs/VB.Specs.vbproj b/Tests/VB.Specs/VB.Specs.vbproj index cfc7e3edc4..80e5416353 100644 --- a/Tests/VB.Specs/VB.Specs.vbproj +++ b/Tests/VB.Specs/VB.Specs.vbproj @@ -9,7 +9,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/agents.md b/agents.md new file mode 100644 index 0000000000..913b610091 --- /dev/null +++ b/agents.md @@ -0,0 +1,196 @@ +# Fluent Assertions – AI Agent Guidelines + +## Project Overview + +Fluent Assertions is a .NET library providing a rich set of fluent extension methods that allow developers to more naturally specify the expected outcome of test assertions. It supports multiple .NET test frameworks (xUnit, NUnit, MSTest, MSpec, TUnit, etc.) and targets `net47`, `net6.0`, and `net8.0`. + +The source lives in `Src/FluentAssertions/` and the tests in `Tests/FluentAssertions.Specs/` and `Tests/FluentAssertions.Equivalency.Specs/`. + +## Build & Test Commands + +```bash +# Build +dotnet build + +# Run all tests +dotnet test + +# Run tests for a specific project +dotnet test Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj + +# Spell check documentation (run before pushing) +./build.sh --target spellcheck # Linux/macOS +.\build.ps1 --target spellcheck # Windows + +# Accept intentional public API changes (after running approval tests) +./AcceptApiChanges.sh # Linux/macOS +.\AcceptApiChanges.ps1 # Windows +``` + +The project uses a [Nuke](https://nuke.build/)-based build system. The build scripts `build.sh` / `build.ps1` / `build.cmd` are thin wrappers around Nuke and support all standard Nuke targets. + +## Contributing Workflow + +- Always target the `main` branch for pull requests +- Prefer rebase over merge when updating a local branch +- **Any change to the public API requires prior approval**: open a GitHub issue, get it labeled `api-approved`, then open the PR +- After intentional public API changes, run `AcceptApiChanges.sh` / `AcceptApiChanges.ps1` to update the API approval baselines in `Tests/Approval.Tests/` +- Update `docs/_pages/releases.md` when adding features or fixing bugs +- Update `docs/_pages/` documentation when assertions are added or changed +- Run the spell checker before pushing: `./build.sh --target spellcheck` + +## Code Style + +Follow the [C# Coding Guidelines](https://csharpcodingguidelines.com/). Key rules enforced by `.editorconfig`: + +- 4 spaces indentation; max line length of 130 characters +- CRLF line endings +- Opening braces on their own line (`csharp_new_line_before_open_brace = all`) +- Use language keywords instead of BCL types (`int` not `Int32`, `string` not `String`) +- Prefer pattern matching over casts (`is` patterns, not `as` + null check) +- Avoid `this.` qualifier unless required for disambiguation +- Use `var` only when the type is immediately apparent from the right-hand side +- Access modifiers are required on all non-interface members +- `readonly` is required on fields that are never reassigned +- Constant fields use PascalCase + +## Assertion Classes + +All assertion classes follow this dual-class pattern (concrete + generic base) to support both simple use and extensibility via inheritance: + +```csharp +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +/// +/// Contains a number of methods to assert that a is in the expected state. +/// +[DebuggerNonUserCode] +public class FooAssertions : FooAssertions +{ + public FooAssertions(Foo value, AssertionChain assertionChain) + : base(value, assertionChain) + { + } +} + +#pragma warning disable CS0659, S1206 // Ignore not overriding Object.GetHashCode() +#pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals +/// +/// Contains a number of methods to assert that a is in the expected state. +/// +[DebuggerNonUserCode] +public class FooAssertions + where TAssertions : FooAssertions +{ + private readonly AssertionChain assertionChain; + + public FooAssertions(Foo value, AssertionChain assertionChain) + { + this.assertionChain = assertionChain; + Subject = value; + } + + /// + /// Gets the object whose value is being asserted. + /// + public Foo Subject { get; } + + /// + /// Asserts that the foo satisfies some condition. + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeSomething( + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + assertionChain + .ForCondition(/* condition */) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:foo} to be something{reason}."); + + return new AndConstraint((TAssertions)this); + } +} +``` + +Key points: +- All assertion classes must have `[DebuggerNonUserCode]` +- Generic parameter is `TAssertions` constrained to the class itself +- `Subject` property exposes the value under test +- Assertion methods return `AndConstraint` or `AndWhichConstraint` to support chaining (`.And`) +- `because` / `becauseArgs` parameters are required on every assertion method +- `because` is decorated with `[StringSyntax("CompositeFormat")]` +- Failure messages use `{context:typename}` for the subject reference and `{reason}` for the `because` clause +- The `Should()` extension method for new types is added to `Src/FluentAssertions/AssertionExtensions.cs` +- XML doc comments on `Should()` extension methods follow the pattern: `Returns an object that can be used to assert the current .` + +## Test Conventions + +Tests use xUnit and the Arrange-Act-Assert (AAA) pattern. Each spec class is a partial class split into one file per assertion method. + +**File naming**: `FooAssertionSpecs.BeSomething.cs` (partial class file per assertion method) + +```csharp +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class FooAssertionSpecs +{ + public class BeSomething // One nested class per assertion method + { + [Fact] + public void Succeeds_for_foo_with_the_expected_value() + { + // Arrange + var subject = new Foo(expectedValue); + + // Act / Assert + subject.Should().BeSomething(); + } + + [Fact] + public void The_foo_must_satisfy_some_condition() + { + // Arrange + var subject = new Foo(unexpectedValue); + + // Act + Action act = () => subject.Should().BeSomething(); + + // Assert + act.Should().Throw() + .WithMessage("Expected foo to be something*"); + } + + [Fact] + public void Fails_for_null_foo() + { + // Arrange + Foo subject = null; + + // Act + Action act = () => subject.Should().BeSomething(); + + // Assert + act.Should().Throw(); + } + } +} +``` + +Naming rules: +- Use fact-based test method names (e.g. `Succeeds_for_*`, `The_X_must_be_Y`, `An_X_is_required`) – avoid "Should", "When", and "Asserting" +- Separate the "Act" and "Assert" steps only when testing failure paths; success-path tests can combine them as `// Act / Assert` diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 4ad8e9de44..2b1a285cb5 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -3,11 +3,13 @@ main: - title: "About" url: /about/ - title: "Documentation" - url: /introduction + url: https://xceed.com/documentation/xceed-fluent-assertions-for-net/ - title: "Releases" url: /releases/ - title: "GitHub" url: https://github.com/fluentassertions/fluentassertions + - title: "Other Products" + url: https://www.xceed.com sidebar: - title: About @@ -101,6 +103,8 @@ sidebar: url: /tips/#improved-assertions - title: Migrating from MSTest url: /tips/#mstest-migration + - title: Migrating from xUnit + url: /tips/#xunit-migration - title: Serialization & Ignored Members url: /serializationignoredmembers diff --git a/docs/_data/xunit-migration/assert.yml b/docs/_data/xunit-migration/assert.yml new file mode 100644 index 0000000000..a17658c827 --- /dev/null +++ b/docs/_data/xunit-migration/assert.yml @@ -0,0 +1,225 @@ +- old: | + Assert.True(actual); + + new: | + actual.Should().BeTrue(); + + old-message: | + Assert.True() Failure + Expected: True + Actual: False + + new-message: | + Expected actual to be True, but found False. + +- old: | + Assert.False(actual); + + new: | + actual.Should().BeFalse(); + + old-message: | + Assert.False() Failure + Expected: False + Actual: True + + new-message: | + Expected actual to be False, but found True. + +- old: | + Assert.Equal(expected, actual); + + new: | + actual.Should().Be(expected); + + old-message: | + Assert.Equal() Failure: Values differ + Expected: 2 + Actual: 1 + + new-message: | + Expected actual to be 2, but found 1. + +- old: | + Assert.NotEqual(expected, actual); + + new: | + actual.Should().NotBe(expected); + + old-message: | + Assert.NotEqual() Failure: Values are equal + Expected: Not 1 + Actual: 1 + + new-message: | + Did not expect actual to be 1. + +- old: | + Assert.Null(actual); + + new: | + actual.Should().BeNull(); + + old-message: | + Assert.Null() Failure: Value is not null + Expected: null + Actual: Object { } + + new-message: | + Expected actual to be , but found System.Object (HashCode=33420276). + +- old: | + Assert.NotNull(actual); + + new: | + actual.Should().NotBeNull(); + + old-message: | + Assert.NotNull() Failure: Value is null + + new-message: | + Expected actual not to be . + +- old: | + Assert.Same(expected, actual); + + new: | + actual.Should().BeSameAs(expected); + + old-message: | + Assert.Same() Failure: Values are not the same instance + Expected: { OtherProperty = "expected", SomeProperty = 2 } + Actual: { OtherProperty = "actual", SomeProperty = 1 } + + new-message: | + Expected actual to refer to { + OtherProperty = "expected", + SomeProperty = 2 + }, but found { + OtherProperty = "actual", + SomeProperty = 1 + }. + +- old: | + Assert.NotSame(expected, actual); + + new: | + actual.Should().NotBeSameAs(expected); + + old-message: | + Assert.NotSame() Failure: Values are the same instance + + new-message: | + Did not expect actual to refer to { + OtherProperty = "expected", + SomeProperty = 1 + }. + +- old: | + Assert.Contains(expected, actual); + + new: | + actual.Should().Contain(expected); + + old-message: | + Assert.Contains() Failure: Item not found in collection + Collection: [1, 2] + Not found: 3 + + new-message: | + Expected actual {1, 2} to contain 3. + +- old: | + Assert.DoesNotContain(unexpected, actual); + + new: | + actual.Should().NotContain(unexpected); + + old-message: | + Assert.DoesNotContain() Failure: Item found in collection + ↓ (pos 1) + Collection: [1, 2] + Found: 2 + + new-message: | + Expected actual {1, 2} to not contain 2. + +- old: | + Assert.Empty(actual); + + new: | + actual.Should().BeEmpty(); + + old-message: | + Assert.Empty() Failure: Collection was not empty + Collection: [1] + + new-message: | + Expected actual to be empty, but found at least one item {1}. + +- old: | + Assert.NotEmpty(actual); + + new: | + actual.Should().NotBeEmpty(); + + old-message: | + Assert.NotEmpty() Failure: Collection was empty + + new-message: | + Expected actual not to be empty. + +- old: | + Assert.Single(actual); + + new: | + actual.Should().ContainSingle(); + + old-message: | + Assert.Single() Failure: The collection contained 2 items + Collection: [1, 2] + + new-message: | + Expected actual to contain a single item, but found {1, 2}. + +- old: | + Assert.IsType(actual); + + new: | + actual.Should().BeOfType(); + + old-message: | + Assert.IsType() Failure: Value is not the exact type + Expected: typeof(string) + Actual: typeof(int) + + new-message: | + Expected type to be System.String, but found System.Int32. + +- old: | + Assert.IsAssignableFrom(actual); + + new: | + actual.Should().BeAssignableTo(); + + old-message: | + Assert.IsAssignableFrom() Failure: Value is an incompatible type + Expected: typeof(string) + Actual: typeof(int) + + new-message: | + Expected actual to be assignable to System.String, but System.Int32 is not. + +- old: | + Assert.Throws(() => ...); + + new: | + action.Should().ThrowExactly(); + + old-message: | + Assert.Throws() Failure: Exception type was not an exact match + Expected: typeof(System.InvalidOperationException) + Actual: typeof(System.ArgumentException) + + new-message: | + Expected type to be System.InvalidOperationException, but found System.ArgumentException. diff --git a/docs/_pages/about.md b/docs/_pages/about.md index 8c0b9e9e6e..b04b499378 100644 --- a/docs/_pages/about.md +++ b/docs/_pages/about.md @@ -37,6 +37,60 @@ will fail with: > Expected numbers to have an item matching (item > 3) because at least 1 item should be larger than 3. +Another example of the power of Fluent Assertions is how you can compare collections. For example, consider the following test: + +```csharp +var actual = new[] +{ + new Customer + { + Age = 13, + Name = "John" + }, + new Customer + { + Age = 16, + Name = "Frank" + } +}; + +actual.Should().BeEquivalentTo([ + new Customer + { + Age = 52, + Name = "James" + } +], opt => opt.Excluding(x => x.Id)); +``` + +This test will fail with the following details: + +``` +Expected property actual[0].Name to be "James" with a length of 6, but "John" has a length of 4, differs near "Joh" (index 0). +Expected property actual[0].Age to be 52, but found 13. +Expected actual to contain exactly one item, but found one extraneous item FluentAssertions.Equivalency.Specs.Customer +{ + Age = 16, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Frank" +} + +With configuration: +- Prefer the declared type of the members +- Compare enums by value +- Compare tuples by their properties +- Compare anonymous types by their properties +- Compare records by their members +- Include non-browsable members +- Include all non-private properties +- Include all non-private fields +- Exclude member Id +- Match (JSON) member by name (or throw) +- Be strict about the order of items in byte arrays +- Without automatic conversion +``` + ## Supported Frameworks and Libraries Fluent Assertions cross-compiles to .NET Framework 4.7, as well as .NET 6, .NET Standard 2.0 and 2.1. diff --git a/docs/_pages/collections.md b/docs/_pages/collections.md index 692900a14c..1339cfaf01 100644 --- a/docs/_pages/collections.md +++ b/docs/_pages/collections.md @@ -90,6 +90,35 @@ singleEquivalent.Should().ContainSingle() .Which.Should().BeEquivalentTo(new { Size = 42 }); ``` +You can use the same collection assertions on `Span`, `ReadOnlySpan`, `Memory`, and `ReadOnlyMemory`. +For spans, `BeEqualTo` is available as a readability alias for `Equal`. + +```csharp +Span span = [1, 2, 3]; +ReadOnlySpan readOnlySpan = [1, 2, 3]; +Memory memory = new[] { 1, 2, 3 }; +ReadOnlyMemory readOnlyMemory = new[] { 1, 2, 3 }; + +span.Should().Equal([1, 2, 3]); +span.Should().BeEqualTo([1, 2, 3]); + +readOnlySpan.Should().Equal([1, 2, 3]); +readOnlySpan.Should().BeEqualTo([1, 2, 3]); + +memory.Should().Contain(2).And.HaveCount(3); +readOnlyMemory.Should().Contain(2).And.HaveCount(3); +``` + +For character spans, you can assert exact string equality with `Be`. + +```csharp +Span chars = ['a', 'b', 'c']; +ReadOnlySpan readOnlyChars = ['a', 'b', 'c']; + +chars.Should().Be("abc"); +readOnlyChars.Should().Be("abc"); +``` + Asserting that a collection contains items in a certain order is as easy as using one of the several overloads of `BeInAscendingOrder` or `BeInDescendingOrder`. The default overload will use the default `Comparer` for the specified type, but overloads also exist that take an `IComparer`, a property expression to sort by an object's property, or a lambda expression to avoid the need for `IComparer` implementations. ```csharp @@ -108,7 +137,7 @@ collection.Should().BeInDescendingOrder(item => item.SomeProp, StringComparer.Cu collection.Should().NotBeInAscendingOrder(item => item.SomeProp, StringComparer.CurrentCulture); collection.Should().NotBeInDescendingOrder(item => item.SomeProp, StringComparer.CurrentCulture); ``` - + For `String` collections there are specific methods to assert the items. For the `ContainMatch` and `NotContainMatch` methods we support wildcards. The pattern can be a combination of literal and wildcard characters, but it doesn't support regular expressions. diff --git a/docs/_pages/objectgraphs.md b/docs/_pages/objectgraphs.md index d353d34c9b..f6668f5195 100644 --- a/docs/_pages/objectgraphs.md +++ b/docs/_pages/objectgraphs.md @@ -165,6 +165,29 @@ orderDto.Should().BeEquivalentTo(order, options => options .ExcludingMembersNamed("ID", "Version")); ``` +You can also exclude all members of a certain type from the comparison. This is useful when you want to ignore certain types throughout the entire object graph: + +```csharp +orderDto.Should().BeEquivalentTo(order, options => options + .Excluding()); +``` + +This works for any type, including interfaces (which will exclude all types implementing that interface): + +```csharp +orderDto.Should().BeEquivalentTo(order, options => options + .Excluding>()); +``` + +You can also exclude members of open generic types by passing the type definition: + +```csharp +orderDto.Should().BeEquivalentTo(order, options => options + .Excluding(typeof(Nullable<>))); +``` + +This will exclude all nullable value types like `int?`, `DateTime?`, etc. The exclusion also applies to derived types, so excluding a base class will also exclude all types that derive from it. + Maybe far-fetched, but you may even decide to exclude a member on a particular nested object by its index. ```csharp @@ -373,7 +396,13 @@ string expect = "A\r\nB\nC"; actual.Should().BeEquivalentTo(expect, o => o.IgnoringNewlineStyle()); ``` -Next to that, when two long strings differ, by default the reporting will only include the relevant fragments needed to highlight the differences. If you want to see the full text of both strings, use the `IncludingFullStringsInDifference()` option. +Next to that, when two long strings differ, by default the reporting will only include the relevant fragments needed to highlight the differences. If you want to see the full text of both strings, use the `IncludingFullStringsInDifference()` option. + +You can also treat `null` strings as equivalent to empty strings using `ComparingNullStringsAsEmpty()`: + +```csharp +((string)null).Should().BeEquivalentTo("", o => o.ComparingNullStringsAsEmpty()); +``` ### Enums @@ -401,6 +430,12 @@ You can also assert that all instances of `OrderDto` are structurally equal to a orderDtos.Should().AllBeEquivalentTo(singleOrder); ``` +If you want to treat `null` collections as equivalent to empty collections, use `ComparingNullCollectionsAsEmpty()`: + +```csharp +((int[])null).Should().BeEquivalentTo([], o => o.ComparingNullCollectionsAsEmpty()); +``` + ### JSON For projects targeting .NET 6 or later, you can also compare a `JsonNode` from the `System.Text.Json` namespace representing a deeply nested JSON block against another object such as an anonymous type. You can even use inline assertions like `Value.ThatSatisfies()` and `Value.ThatMatches()`. @@ -478,7 +513,7 @@ orderDto.Should().BeEquivalentTo(expectation, options => options.WithoutStrictOr `Should().BeEquivalentTo` is a very powerful feature, and one of the unique selling points of Fluent Assertions. But sometimes it can be a bit overwhelming, especially if some assertion fails under unexpected conditions. To help you understand how Fluent Assertions compared two (collections of) object graphs, the failure message will always include the relevant configuration settings: -```csharp +``` Xunit.Sdk.XunitException Expected item[0] to be 0x06, but found 0x01. Expected item[1] to be 0x05, but found 0x02. @@ -496,7 +531,28 @@ With configuration: - Be strict about the order of items in byte arrays ``` -However, sometimes that's not enough. For those scenarios where you need to understand a bit more, you can add the `WithTracing` option. When added to the assertion call, it would extend the above output with something like this: +For more complicated comparisons where some items match, some items are missing or some items are unexpected, it might be useful to dump the entire contents of the subject-under-test. You can do this using the `WithFullDump` option. This will add something like this to the failure message. + +``` +Full dump of actual: { + FluentAssertions.Equivalency.Specs.Customer + { + Age = 13, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Jits" + }, + FluentAssertions.Equivalency.Specs.Customer + { + Age = 16, + Birthdate = <0001-01-01 00:00:00.000>, + Id = 0L, + Name = "Teddie" + } +} +``` + +However, sometimes even that is not enough. For those scenarios where you need to understand a bit more, you can add the `WithTracing` option. When added to the assertion call, it would extend the above output with something like this: ```text With trace: diff --git a/docs/_pages/releases.md b/docs/_pages/releases.md index 2afbc67593..24c5551ee8 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -7,11 +7,27 @@ sidebar: nav: "sidebar" --- -## Unreleased +## 8.9.0 ### What's new -* New overloads of `BeXmlSerializable` and `BeDataContractSerializable` that allow `EquivalencyOptions` to be configured, allowing the use of member selection rules. - [#3107](https://github.com/fluentassertions/fluentassertions/pull/3107) +* Added extensive support for `Span` / `ReadOnlySpan` and `Be(string)` for `Span` / `ReadOnlySpan` - [#3172](https://github.com/fluentassertions/fluentassertions/pull/3172) +* Added `Excluding()` and `Excluding(Type)` overloads to exclude all members of a certain type from equivalency comparisons - [#3115](https://github.com/fluentassertions/fluentassertions/pull/3115) +* `BeEquivalentTo` will now report the missing or extraneous items for differently sized collections - [#3133](https://github.com/fluentassertions/fluentassertions/pull/3133) +* Added `BeEqualTo` and `NotBeEqualTo` to the collection assertions as a more fluent alternative to `Equal` - [#3166](https://github.com/fluentassertions/fluentassertions/pull/3166) +* Added `HaveMillisecond` and `NotHaveMillisecond` assertion methods for `DateTime` and `DateTimeOffset` - [#3164](https://github.com/fluentassertions/fluentassertions/pull/3164) + +### Enhancements +* Added `ComparingNullCollectionsAsEmpty()` and `ComparingNullStringsAsEmpty()` options to `BeEquivalentTo` to treat `null` collections and strings as equivalent to empty ones - [#3202](https://github.com/fluentassertions/fluentassertions/pull/3202) +* `BeEquivalentTo` now includes the original index of each extraneous item in the failure message - [#3203](https://github.com/fluentassertions/fluentassertions/pull/3203) +* Added option `WithFullDump` to `BeEquivalentTo` to include the entire contents of the subject-under-test in the failure message - [#3133](https://github.com/fluentassertions/fluentassertions/pull/3133) +* Remove FluentAssertions code from the stack trace when an assertion fails - [#3152](https://github.com/fluentassertions/fluentassertions/pull/3152) +* Improve reporting the subject when chaining `Throw` with `Which` - [#3160](https://github.com/fluentassertions/fluentassertions/pull/3160) + +### Fixes + +* Fixed a formatting exception when comparing strings containing braces - [#3151](https://github.com/fluentassertions/fluentassertions/pull/3151) +* Path-based `Excluding()` and `Including()` rules on types that use value semantics (i.e. override `Equals`) now fail with a descriptive error instead of being silently ignored - [#3187](https://github.com/fluentassertions/fluentassertions/pull/3187) ## 8.8.0 @@ -19,6 +35,7 @@ sidebar: * Added `WithoutMessage` to allow asserting an exception message does not contain a wildcard pattern - [#3100](https://github.com/fluentassertions/fluentassertions/pull/3100) * Added support for MSTest 4 - [#3111](https://github.com/fluentassertions/fluentassertions/pull/3111) +* New overloads of `BeXmlSerializable` and `BeDataContractSerializable` that allow `EquivalencyOptions` to be configured, allowing the use of member selection rules. - [#3107](https://github.com/fluentassertions/fluentassertions/pull/3107) ### Enhancements @@ -36,13 +53,12 @@ sidebar: ### What's new * Added support for `System.Text.Json.JsonNode` and `JsonArray` through new assertions as well as the `BeEquivalentTo` API - [#3094](https://github.com/fluentassertions/fluentassertions/pull/3094) -* Added `WithoutMessage` to allow asserting an exception message does not contain a wildcard pattern - [#3100](https://github.com/fluentassertions/fluentassertions/pull/3100) ## 8.6.0 ### What's new -* Add `Value.ThatMatches` and `Value.ThatSatifies` to build inline assertions when using `BeEquivalentTo` - [#3076](https://github.com/fluentassertions/fluentassertions/pull/3076) +* Add `Value.ThatMatches` and `Value.ThatSatisfies` to build inline assertions when using `BeEquivalentTo` - [#3076](https://github.com/fluentassertions/fluentassertions/pull/3076) ## 8.5.0 @@ -223,12 +239,12 @@ Version 7 will remain fully open-source indefinitely and receive bugfixes and ot ### Fixes -* The expectation node identified as a cyclic reference is still compared to the subject node using simple equality - [2819](https://github.com/fluentassertions/fluentassertions/pull/2819) +* The expectation node identified as a cyclic reference is still compared to the subject node using simple equality - [#2819](https://github.com/fluentassertions/fluentassertions/pull/2819) ## 6.12.2 ### Fixes -* Better handling of normal vs explicitly implemented vs default interface properties - [2794](https://github.com/fluentassertions/fluentassertions/pull/2794) +* Better handling of normal vs explicitly implemented vs default interface properties - [#2794](https://github.com/fluentassertions/fluentassertions/pull/2794) ## 6.12.1 @@ -407,7 +423,7 @@ Version 7 will remain fully open-source indefinitely and receive bugfixes and ot ### Fixes * Prevent multiple enumeration of `IEnumerable`s in parameter-less `ContainSingle()` - [#1753](https://github.com/fluentassertions/fluentassertions/pull/1753) * Changed `HaveCount()` assertion message order to state expected and actual collection count before dumping its content` - [#1760](https://github.com/fluentassertions/fluentassertions/pull/1760) -* `CompleteWithinAsync` did not take initial sync computation into account when measuring execution time - [1762](https://github.com/fluentassertions/fluentassertions/pull/1762). +* `CompleteWithinAsync` did not take initial sync computation into account when measuring execution time - [#1762](https://github.com/fluentassertions/fluentassertions/pull/1762). ## 6.2.0 diff --git a/docs/_pages/tips.md b/docs/_pages/tips.md index b35d4879fa..04c2a15eb5 100644 --- a/docs/_pages/tips.md +++ b/docs/_pages/tips.md @@ -39,3 +39,12 @@ If you see something missing, please consider submitting a pull request. {% include assertion-comparison.html header1="MSTest" header2="Fluent Assertions" idPrefix="mstest-" caption="CollectionAssert" examples=site.data.mstest-migration.collectionAssert %} {% include assertion-comparison.html header1="MSTest" header2="Fluent Assertions" idPrefix="mstest-" caption="StringAssert" examples=site.data.mstest-migration.stringAssert %} {% include assertion-comparison.html header1="MSTest" header2="Fluent Assertions" idPrefix="mstest-" caption="Exceptions" examples=site.data.mstest-migration.exceptions %} + +## xUnit Migration + +The examples below show how you might write equivalent xUnit assertions using Fluent Assertions including the failure message from each case. +We think this is both a useful migration guide and a convincing argument for switching. + +If you see something missing, please consider submitting a pull request. + +{% include assertion-comparison.html header1="xUnit" header2="Fluent Assertions" idPrefix="xunit-" caption="Assert" examples=site.data.xunit-migration.assert %} diff --git a/docs/_pages/upgradingtov8.md b/docs/_pages/upgradingtov8.md index 323a81ede1..685b49f294 100644 --- a/docs/_pages/upgradingtov8.md +++ b/docs/_pages/upgradingtov8.md @@ -151,6 +151,14 @@ Notice the last argument to the `AndWhichConstraint` constructor. If you need to do so, please refer to [FluentAssertions.Web](https://github.com/adrianiftode/FluentAssertions.Web) which offers a bunch of extensions on the HTTP specific types. +## AssertionOptions has become AssertionConfiguration +Since the various configurations options in versions before v8 had become a bit messy, we consolidated all of those under the `AssertionConfiguration.Current` construct. For example, to the set the global equivalency behavior, you can use something like + +```csharp +AssertionConfiguration.Current.Equivalency.Modify(options => options. + ComparingByValue()); +``` + ## Other breaking changes Check out the [release notes](releases.md) for other changes that might affect the upgrade to v8. diff --git a/global.json b/global.json index c48f9699dd..4ae80ef19e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.300", + "version": "10.0.100", "rollForward": "latestMajor" } } diff --git a/package-lock.json b/package-lock.json index b64663926a..e12e565acf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,127 +7,149 @@ "": { "version": "1.0.1", "dependencies": { - "cspell": "^9.2.1" + "cspell": "^10.0.0" } }, "node_modules/@cspell/cspell-bundled-dicts": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.2.1.tgz", - "integrity": "sha512-85gHoZh3rgZ/EqrHIr1/I4OLO53fWNp6JZCqCdgaT7e3sMDaOOG6HoSxCvOnVspXNIf/1ZbfTCDMx9x79Xq0AQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-10.0.0.tgz", + "integrity": "sha512-ci410HEkng2582oOjlRHQtlGXwh+rUC/mVcN9dObLHpKhvPgzn2S6vT56pARstxxZpcCUG/oLhn3dCqdJlVzmA==", "license": "MIT", "dependencies": { "@cspell/dict-ada": "^4.1.1", "@cspell/dict-al": "^1.1.1", - "@cspell/dict-aws": "^4.0.15", - "@cspell/dict-bash": "^4.2.1", - "@cspell/dict-companies": "^3.2.5", - "@cspell/dict-cpp": "^6.0.12", + "@cspell/dict-aws": "^4.0.17", + "@cspell/dict-bash": "^4.2.2", + "@cspell/dict-companies": "^3.2.11", + "@cspell/dict-cpp": "^7.0.2", "@cspell/dict-cryptocurrencies": "^5.0.5", - "@cspell/dict-csharp": "^4.0.7", - "@cspell/dict-css": "^4.0.18", - "@cspell/dict-dart": "^2.3.1", - "@cspell/dict-data-science": "^2.0.9", - "@cspell/dict-django": "^4.1.5", - "@cspell/dict-docker": "^1.1.16", - "@cspell/dict-dotnet": "^5.0.10", + "@cspell/dict-csharp": "^4.0.8", + "@cspell/dict-css": "^4.1.1", + "@cspell/dict-dart": "^2.3.2", + "@cspell/dict-data-science": "^2.0.13", + "@cspell/dict-django": "^4.1.6", + "@cspell/dict-docker": "^1.1.17", + "@cspell/dict-dotnet": "^5.0.13", "@cspell/dict-elixir": "^4.0.8", - "@cspell/dict-en_us": "^4.4.18", - "@cspell/dict-en-common-misspellings": "^2.1.5", - "@cspell/dict-en-gb-mit": "^3.1.8", - "@cspell/dict-filetypes": "^3.0.13", + "@cspell/dict-en_us": "^4.4.33", + "@cspell/dict-en-common-misspellings": "^2.1.12", + "@cspell/dict-en-gb-mit": "^3.1.22", + "@cspell/dict-filetypes": "^3.0.18", "@cspell/dict-flutter": "^1.1.1", - "@cspell/dict-fonts": "^4.0.5", + "@cspell/dict-fonts": "^4.0.6", "@cspell/dict-fsharp": "^1.1.1", - "@cspell/dict-fullstack": "^3.2.7", + "@cspell/dict-fullstack": "^3.2.9", "@cspell/dict-gaming-terms": "^1.1.2", - "@cspell/dict-git": "^3.0.7", - "@cspell/dict-golang": "^6.0.23", + "@cspell/dict-git": "^3.1.0", + "@cspell/dict-golang": "^6.0.26", "@cspell/dict-google": "^1.0.9", "@cspell/dict-haskell": "^4.0.6", - "@cspell/dict-html": "^4.0.12", - "@cspell/dict-html-symbol-entities": "^4.0.4", + "@cspell/dict-html": "^4.0.15", + "@cspell/dict-html-symbol-entities": "^4.0.5", "@cspell/dict-java": "^5.0.12", "@cspell/dict-julia": "^1.1.1", "@cspell/dict-k8s": "^1.0.12", "@cspell/dict-kotlin": "^1.1.1", - "@cspell/dict-latex": "^4.0.4", + "@cspell/dict-latex": "^5.1.0", "@cspell/dict-lorem-ipsum": "^4.0.5", "@cspell/dict-lua": "^4.0.8", "@cspell/dict-makefile": "^1.0.5", - "@cspell/dict-markdown": "^2.0.12", - "@cspell/dict-monkeyc": "^1.0.11", - "@cspell/dict-node": "^5.0.8", - "@cspell/dict-npm": "^5.2.15", - "@cspell/dict-php": "^4.0.15", + "@cspell/dict-markdown": "^2.0.16", + "@cspell/dict-monkeyc": "^1.0.12", + "@cspell/dict-node": "^5.0.9", + "@cspell/dict-npm": "^5.2.38", + "@cspell/dict-php": "^4.1.1", "@cspell/dict-powershell": "^5.0.15", - "@cspell/dict-public-licenses": "^2.0.15", - "@cspell/dict-python": "^4.2.19", + "@cspell/dict-public-licenses": "^2.0.16", + "@cspell/dict-python": "^4.2.26", "@cspell/dict-r": "^2.1.1", - "@cspell/dict-ruby": "^5.0.9", - "@cspell/dict-rust": "^4.0.12", - "@cspell/dict-scala": "^5.0.8", - "@cspell/dict-shell": "^1.1.1", - "@cspell/dict-software-terms": "^5.1.7", + "@cspell/dict-ruby": "^5.1.1", + "@cspell/dict-rust": "^4.1.2", + "@cspell/dict-scala": "^5.0.9", + "@cspell/dict-shell": "^1.1.2", + "@cspell/dict-software-terms": "^5.2.2", "@cspell/dict-sql": "^2.2.1", "@cspell/dict-svelte": "^1.0.7", "@cspell/dict-swift": "^2.0.6", "@cspell/dict-terraform": "^1.1.3", "@cspell/dict-typescript": "^3.2.3", - "@cspell/dict-vue": "^3.0.5" + "@cspell/dict-vue": "^3.0.5", + "@cspell/dict-zig": "^1.0.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/@cspell/cspell-json-reporter": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.2.1.tgz", - "integrity": "sha512-LiiIWzLP9h2etKn0ap6g2+HrgOGcFEF/hp5D8ytmSL5sMxDcV13RrmJCEMTh1axGyW0SjQEFjPnYzNpCL1JjGA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-10.0.0.tgz", + "integrity": "sha512-hq5dui2ngYMZKbBauX7K1tkqlu81sX/uaCO49ZJLPjeZsE1auZLtHehDLfAr/ZXoj/dLYeQMSKiaJyE+qLVPHA==", "license": "MIT", "dependencies": { - "@cspell/cspell-types": "9.2.1" + "@cspell/cspell-types": "10.0.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" + } + }, + "node_modules/@cspell/cspell-performance-monitor": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-performance-monitor/-/cspell-performance-monitor-10.0.0.tgz", + "integrity": "sha512-2vMh2pLt2dg/ArYvWjMP4v9HCm0pRhONsEJyc8oHdZyOYvX7trixX894I0M39+VBf3yWtPCEgYRh1UDXNIZRig==", + "license": "MIT", + "engines": { + "node": ">=22.18.0" } }, "node_modules/@cspell/cspell-pipe": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.2.1.tgz", - "integrity": "sha512-2N1H63If5cezLqKToY/YSXon4m4REg/CVTFZr040wlHRbbQMh5EF3c7tEC/ue3iKAQR4sm52ihfqo1n4X6kz+g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-10.0.0.tgz", + "integrity": "sha512-qcgHhQvtEX8LSwIVsWrdUgiGim52lN3jT+ghlkdp72v+nBcGKsS2frEKTmbGLug+xcqppkzs6Q6VmsFp1MGtfA==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/@cspell/cspell-resolver": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.2.1.tgz", - "integrity": "sha512-fRPQ6GWU5eyh8LN1TZblc7t24TlGhJprdjJkfZ+HjQo+6ivdeBPT7pC7pew6vuMBQPS1oHBR36hE0ZnJqqkCeg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-10.0.0.tgz", + "integrity": "sha512-8H+IUDB7SmrpcRugQ5f55qG81ZShk6nQRk+natLz41TEY98D8/LCmjHEkh/vhDPph9pVJmNUp7JcM2E1UHEa2g==", "license": "MIT", "dependencies": { - "global-directory": "^4.0.1" + "global-directory": "^5.0.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/@cspell/cspell-service-bus": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.2.1.tgz", - "integrity": "sha512-k4M6bqdvWbcGSbcfLD7Lf4coZVObsISDW+sm/VaWp9aZ7/uwiz1IuGUxL9WO4JIdr9CFEf7Ivmvd2txZpVOCIA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-10.0.0.tgz", + "integrity": "sha512-V7eigqg/TOoKwNK4Q18wr9KGxA8U5SFcoWVS8RyAxv4mQ+yNKHhvHEbRBifjPbQDer66afOrclb2UbqkIy2SOw==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/@cspell/cspell-types": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.2.1.tgz", - "integrity": "sha512-FQHgQYdTHkcpxT0u1ddLIg5Cc5ePVDcLg9+b5Wgaubmc5I0tLotgYj8c/mvStWuKsuZIs6sUopjJrE91wk6Onw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-10.0.0.tgz", + "integrity": "sha512-IQA++Idqb8fZzkCbHq3+T+9yG9WpeaBxomOrG2KcR/Pj0CgnovzuApYKL2cc35UWLePboKinMeqEPiweFpHVug==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.18.0" + } + }, + "node_modules/@cspell/cspell-worker": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-worker/-/cspell-worker-10.0.0.tgz", + "integrity": "sha512-V5bjMldNksilnja3fu8muQmkW5/guyua1yNVOhoE2r7othSvjuDlGMl8g2bQSrWjp+UXu0dP/BEZ6JC/IfNwTA==", + "license": "MIT", + "dependencies": { + "cspell-lib": "10.0.0" + }, + "engines": { + "node": ">=22.18.0" } }, "node_modules/@cspell/dict-ada": { @@ -143,30 +165,30 @@ "license": "MIT" }, "node_modules/@cspell/dict-aws": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.15.tgz", - "integrity": "sha512-aPY7VVR5Os4rz36EaqXBAEy14wR4Rqv+leCJ2Ug/Gd0IglJpM30LalF3e2eJChnjje3vWoEC0Rz3+e5gpZG+Kg==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.17.tgz", + "integrity": "sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==", "license": "MIT" }, "node_modules/@cspell/dict-bash": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.1.tgz", - "integrity": "sha512-SBnzfAyEAZLI9KFS7DUG6Xc1vDFuLllY3jz0WHvmxe8/4xV3ufFE3fGxalTikc1VVeZgZmxYiABw4iGxVldYEg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.2.tgz", + "integrity": "sha512-kyWbwtX3TsCf5l49gGQIZkRLaB/P8g73GDRm41Zu8Mv51kjl2H7Au0TsEvHv7jzcsRLS6aUYaZv6Zsvk1fOz+Q==", "license": "MIT", "dependencies": { - "@cspell/dict-shell": "1.1.1" + "@cspell/dict-shell": "1.1.2" } }, "node_modules/@cspell/dict-companies": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.5.tgz", - "integrity": "sha512-H51R0w7c6RwJJPqH7Gs65tzP6ouZsYDEHmmol6MIIk0kQaOIBuFP2B3vIxHLUr2EPRVFZsMW8Ni7NmVyaQlwsg==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.11.tgz", + "integrity": "sha512-0cmafbcz2pTHXLd59eLR1gvDvN6aWAOM0+cIL4LLF9GX9yB2iKDNrKsvs4tJRqutoaTdwNFBbV0FYv+6iCtebQ==", "license": "MIT" }, "node_modules/@cspell/dict-cpp": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.12.tgz", - "integrity": "sha512-N4NsCTttVpMqQEYbf0VQwCj6np+pJESov0WieCN7R/0aByz4+MXEiDieWWisaiVi8LbKzs1mEj4ZTw5K/6O2UQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-7.0.2.tgz", + "integrity": "sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==", "license": "MIT" }, "node_modules/@cspell/dict-cryptocurrencies": { @@ -176,45 +198,45 @@ "license": "MIT" }, "node_modules/@cspell/dict-csharp": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.7.tgz", - "integrity": "sha512-H16Hpu8O/1/lgijFt2lOk4/nnldFtQ4t8QHbyqphqZZVE5aS4J/zD/WvduqnLY21aKhZS6jo/xF5PX9jyqPKUA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.8.tgz", + "integrity": "sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==", "license": "MIT" }, "node_modules/@cspell/dict-css": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.18.tgz", - "integrity": "sha512-EF77RqROHL+4LhMGW5NTeKqfUd/e4OOv6EDFQ/UQQiFyWuqkEKyEz0NDILxOFxWUEVdjT2GQ2cC7t12B6pESwg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.1.1.tgz", + "integrity": "sha512-y/Vgo6qY08e1t9OqR56qjoFLBCpi4QfWMf2qzD1l9omRZwvSMQGRPz4x0bxkkkU4oocMAeztjzCsmLew//c/8w==", "license": "MIT" }, "node_modules/@cspell/dict-dart": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.1.tgz", - "integrity": "sha512-xoiGnULEcWdodXI6EwVyqpZmpOoh8RA2Xk9BNdR7DLamV/QMvEYn8KJ7NlRiTSauJKPNkHHQ5EVHRM6sTS7jdg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.2.tgz", + "integrity": "sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==", "license": "MIT" }, "node_modules/@cspell/dict-data-science": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.9.tgz", - "integrity": "sha512-wTOFMlxv06veIwKdXUwdGxrQcK44Zqs426m6JGgHIB/GqvieZQC5n0UI+tUm5OCxuNyo4OV6mylT4cRMjtKtWQ==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.13.tgz", + "integrity": "sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==", "license": "MIT" }, "node_modules/@cspell/dict-django": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.5.tgz", - "integrity": "sha512-AvTWu99doU3T8ifoMYOMLW2CXKvyKLukPh1auOPwFGHzueWYvBBN+OxF8wF7XwjTBMMeRleVdLh3aWCDEX/ZWg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.6.tgz", + "integrity": "sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==", "license": "MIT" }, "node_modules/@cspell/dict-docker": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.16.tgz", - "integrity": "sha512-UiVQ5RmCg6j0qGIxrBnai3pIB+aYKL3zaJGvXk1O/ertTKJif9RZikKXCEgqhaCYMweM4fuLqWSVmw3hU164Iw==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.17.tgz", + "integrity": "sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==", "license": "MIT" }, "node_modules/@cspell/dict-dotnet": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.10.tgz", - "integrity": "sha512-ooar8BP/RBNP1gzYfJPStKEmpWy4uv/7JCq6FOnJLeD1yyfG3d/LFMVMwiJo+XWz025cxtkM3wuaikBWzCqkmg==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.13.tgz", + "integrity": "sha512-xPp7jMnFpOri7tzmqmm/dXMolXz1t2bhNqxYkOyMqXhvs08oc7BFs+EsbDY0X7hqiISgeFZGNqn0dOCr+ncPYw==", "license": "MIT" }, "node_modules/@cspell/dict-elixir": { @@ -224,27 +246,27 @@ "license": "MIT" }, "node_modules/@cspell/dict-en_us": { - "version": "4.4.18", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.18.tgz", - "integrity": "sha512-6Le961Q0AIfVp3nKuSJJD/9NfnTYA1N/MLAaeWKCABEvhzhopeyGrykwejd0SA4m64WBUNEfSlsgselYWoDSjQ==", + "version": "4.4.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.33.tgz", + "integrity": "sha512-zWftVqfUStDA37wO1ZNDN1qMJOfcxELa8ucHW8W8wBAZY3TK5Nb6deLogCK/IJi/Qljf30dwwuqqv84Qqle9Tw==", "license": "MIT" }, "node_modules/@cspell/dict-en-common-misspellings": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.5.tgz", - "integrity": "sha512-hlRDSjul7wGTDXeLBADoyTGIZjWWZn6/SP+pt0lG3PRtqF0MWH/QEDgUkS+Yev7ZhHCHVLvwBZtDxOd1uw06Tw==", + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.12.tgz", + "integrity": "sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==", "license": "CC BY-SA 4.0" }, "node_modules/@cspell/dict-en-gb-mit": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.8.tgz", - "integrity": "sha512-wrZDRl6TKd1wReepGDPuT1JNbnRjHLvtAVrozp0DUkFlcDgnrB+YSd/Ne4aKnkXl5qpyVQ2GG7a4Z7INKCX+fw==", + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.22.tgz", + "integrity": "sha512-xE5Vg6gGdMkZ1Ep6z9SJMMioGkkT1GbxS5Mm0U3Ey1/H68P0G7cJcyiVr1CARxFbLqKE4QUpoV1o6jz1Z5Yl9Q==", "license": "MIT" }, "node_modules/@cspell/dict-filetypes": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.13.tgz", - "integrity": "sha512-g6rnytIpQlMNKGJT1JKzWkC+b3xCliDKpQ3ANFSq++MnR4GaLiifaC4JkVON11Oh/UTplYOR1nY3BR4X30bswA==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.18.tgz", + "integrity": "sha512-yU7RKD/x1IWmDLzWeiItMwgV+6bUcU/af23uS0+uGiFUbsY1qWV/D4rxlAAO6Z7no3J2z8aZOkYIOvUrJq0Rcw==", "license": "MIT" }, "node_modules/@cspell/dict-flutter": { @@ -254,9 +276,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-fonts": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.5.tgz", - "integrity": "sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.6.tgz", + "integrity": "sha512-aR/0csY01dNb0A1tw/UmN9rKgHruUxsYsvXu6YlSBJFu60s26SKr/k1o4LavpHTQ+lznlYMqAvuxGkE4Flliqw==", "license": "MIT" }, "node_modules/@cspell/dict-fsharp": { @@ -266,9 +288,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-fullstack": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.7.tgz", - "integrity": "sha512-IxEk2YAwAJKYCUEgEeOg3QvTL4XLlyArJElFuMQevU1dPgHgzWElFevN5lsTFnvMFA1riYsVinqJJX0BanCFEg==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.9.tgz", + "integrity": "sha512-diZX+usW5aZ4/b2T0QM/H/Wl9aNMbdODa1Jq0ReBr/jazmNeWjd+PyqeVgzd1joEaHY+SAnjrf/i9CwKd2ZtWQ==", "license": "MIT" }, "node_modules/@cspell/dict-gaming-terms": { @@ -278,15 +300,15 @@ "license": "MIT" }, "node_modules/@cspell/dict-git": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.7.tgz", - "integrity": "sha512-odOwVKgfxCQfiSb+nblQZc4ErXmnWEnv8XwkaI4sNJ7cNmojnvogYVeMqkXPjvfrgEcizEEA4URRD2Ms5PDk1w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.0.tgz", + "integrity": "sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==", "license": "MIT" }, "node_modules/@cspell/dict-golang": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.23.tgz", - "integrity": "sha512-oXqUh/9dDwcmVlfUF5bn3fYFqbUzC46lXFQmi5emB0vYsyQXdNWsqi6/yH3uE7bdRE21nP7Yo0mR1jjFNyLamg==", + "version": "6.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.26.tgz", + "integrity": "sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==", "license": "MIT" }, "node_modules/@cspell/dict-google": { @@ -302,15 +324,15 @@ "license": "MIT" }, "node_modules/@cspell/dict-html": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.12.tgz", - "integrity": "sha512-JFffQ1dDVEyJq6tCDWv0r/RqkdSnV43P2F/3jJ9rwLgdsOIXwQbXrz6QDlvQLVvNSnORH9KjDtenFTGDyzfCaA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.15.tgz", + "integrity": "sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==", "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.4.tgz", - "integrity": "sha512-afea+0rGPDeOV9gdO06UW183Qg6wRhWVkgCFwiO3bDupAoyXRuvupbb5nUyqSTsLXIKL8u8uXQlJ9pkz07oVXw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", + "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "license": "MIT" }, "node_modules/@cspell/dict-java": { @@ -338,9 +360,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-latex": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.4.tgz", - "integrity": "sha512-YdTQhnTINEEm/LZgTzr9Voz4mzdOXH7YX+bSFs3hnkUHCUUtX/mhKgf1CFvZ0YNM2afjhQcmLaR9bDQVyYBvpA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-5.1.0.tgz", + "integrity": "sha512-qxT4guhysyBt0gzoliXYEBYinkAdEtR2M7goRaUH0a7ltCsoqqAeEV8aXYRIdZGcV77gYSobvu3jJL038tlPAw==", "license": "MIT" }, "node_modules/@cspell/dict-lorem-ipsum": { @@ -362,39 +384,39 @@ "license": "MIT" }, "node_modules/@cspell/dict-markdown": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.12.tgz", - "integrity": "sha512-ufwoliPijAgWkD/ivAMC+A9QD895xKiJRF/fwwknQb7kt7NozTLKFAOBtXGPJAB4UjhGBpYEJVo2elQ0FCAH9A==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.16.tgz", + "integrity": "sha512-976RRqKv6cwhrxdFCQP2DdnBVB86BF57oQtPHy4Zbf4jF/i2Oy29MCrxirnOBalS1W6KQeto7NdfDXRAwkK4PQ==", "license": "MIT", "peerDependencies": { - "@cspell/dict-css": "^4.0.18", - "@cspell/dict-html": "^4.0.12", - "@cspell/dict-html-symbol-entities": "^4.0.4", + "@cspell/dict-css": "^4.1.1", + "@cspell/dict-html": "^4.0.15", + "@cspell/dict-html-symbol-entities": "^4.0.5", "@cspell/dict-typescript": "^3.2.3" } }, "node_modules/@cspell/dict-monkeyc": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.11.tgz", - "integrity": "sha512-7Q1Ncu0urALI6dPTrEbSTd//UK0qjRBeaxhnm8uY5fgYNFYAG+u4gtnTIo59S6Bw5P++4H3DiIDYoQdY/lha8w==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.12.tgz", + "integrity": "sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==", "license": "MIT" }, "node_modules/@cspell/dict-node": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.8.tgz", - "integrity": "sha512-AirZcN2i84ynev3p2/1NCPEhnNsHKMz9zciTngGoqpdItUb2bDt1nJBjwlsrFI78GZRph/VaqTVFwYikmncpXg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.9.tgz", + "integrity": "sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==", "license": "MIT" }, "node_modules/@cspell/dict-npm": { - "version": "5.2.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.15.tgz", - "integrity": "sha512-kb9oX/N5FUlJYoqc5G+tIP/0SolteFMz2VhOVKG2qiXUS/1AybVTjUEo4gZ4uEveUhLzUDcfpZbn40EoUVBVrg==", + "version": "5.2.38", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.38.tgz", + "integrity": "sha512-21ucGRPYYhr91C2cDBoMPTrcIOStQv33xOqJB0JLoC5LAs2Sfj9EoPGhGb+gIFVHz6Ia7JQWE2SJsOVFJD1wmg==", "license": "MIT" }, "node_modules/@cspell/dict-php": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.15.tgz", - "integrity": "sha512-iepGB2gtToMWSTvybesn4/lUp4LwXcEm0s8vasJLP76WWVkq1zYjmeS+WAIzNgsuURyZ/9mGqhS0CWMuo74ODw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.1.tgz", + "integrity": "sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==", "license": "MIT" }, "node_modules/@cspell/dict-powershell": { @@ -404,18 +426,18 @@ "license": "MIT" }, "node_modules/@cspell/dict-public-licenses": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.15.tgz", - "integrity": "sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.16.tgz", + "integrity": "sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==", "license": "MIT" }, "node_modules/@cspell/dict-python": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.19.tgz", - "integrity": "sha512-9S2gTlgILp1eb6OJcVZeC8/Od83N8EqBSg5WHVpx97eMMJhifOzePkE0kDYjyHMtAFznCQTUu0iQEJohNQ5B0A==", + "version": "4.2.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.26.tgz", + "integrity": "sha512-hbjN6BjlSgZOG2dA2DtvYNGBM5Aq0i0dHaZjMOI9K/9vRicVvKbcCiBSSrR3b+jwjhQL5ff7HwG5xFaaci0GQA==", "license": "MIT", "dependencies": { - "@cspell/dict-data-science": "^2.0.9" + "@cspell/dict-data-science": "^2.0.13" } }, "node_modules/@cspell/dict-r": { @@ -425,33 +447,33 @@ "license": "MIT" }, "node_modules/@cspell/dict-ruby": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.9.tgz", - "integrity": "sha512-H2vMcERMcANvQshAdrVx0XoWaNX8zmmiQN11dZZTQAZaNJ0xatdJoSqY8C8uhEMW89bfgpN+NQgGuDXW2vmXEw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.1.tgz", + "integrity": "sha512-LHrp84oEV6q1ZxPPyj4z+FdKyq1XAKYPtmGptrd+uwHbrF/Ns5+fy6gtSi7pS+uc0zk3JdO9w/tPK+8N1/7WUA==", "license": "MIT" }, "node_modules/@cspell/dict-rust": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.12.tgz", - "integrity": "sha512-z2QiH+q9UlNhobBJArvILRxV8Jz0pKIK7gqu4TgmEYyjiu1TvnGZ1tbYHeu9w3I/wOP6UMDoCBTty5AlYfW0mw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.1.2.tgz", + "integrity": "sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==", "license": "MIT" }, "node_modules/@cspell/dict-scala": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.8.tgz", - "integrity": "sha512-YdftVmumv8IZq9zu1gn2U7A4bfM2yj9Vaupydotyjuc+EEZZSqAafTpvW/jKLWji2TgybM1L2IhmV0s/Iv9BTw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.9.tgz", + "integrity": "sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==", "license": "MIT" }, "node_modules/@cspell/dict-shell": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.1.tgz", - "integrity": "sha512-T37oYxE7OV1x/1D4/13Y8JZGa1QgDCXV7AVt3HLXjn0Fe3TaNDvf5sU0fGnXKmBPqFFrHdpD3uutAQb1dlp15g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.2.tgz", + "integrity": "sha512-WqOUvnwcHK1X61wAfwyXq04cn7KYyskg90j4lLg3sGGKMW9Sq13hs91pqrjC44Q+lQLgCobrTkMDw9Wyl9nRFA==", "license": "MIT" }, "node_modules/@cspell/dict-software-terms": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.7.tgz", - "integrity": "sha512-CfNFQCVx8R/D8RfFdFTwSjDvXcSXY0tO+VN2N6TEbNTL1GCmqyzhwm4YI+ZbO3MUmMAMjwu9jZyoLk5BaJkXcg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.2.2.tgz", + "integrity": "sha512-0CaYd6TAsKtEoA7tNswm1iptEblTzEe3UG8beG2cpSTHk7afWIVMtJLgXDv0f/Li67Lf3Z1Jf3JeXR7GsJ2TRw==", "license": "MIT" }, "node_modules/@cspell/dict-sql": { @@ -490,44 +512,71 @@ "integrity": "sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==", "license": "MIT" }, + "node_modules/@cspell/dict-zig": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-zig/-/dict-zig-1.0.0.tgz", + "integrity": "sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==", + "license": "MIT" + }, "node_modules/@cspell/dynamic-import": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.2.1.tgz", - "integrity": "sha512-izYQbk7ck0ffNA1gf7Gi3PkUEjj+crbYeyNK1hxHx5A+GuR416ozs0aEyp995KI2v9HZlXscOj3SC3wrWzHZeA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-10.0.0.tgz", + "integrity": "sha512-fMqu/5Ma1Q5ZCR/Par+Q4pvaTKmx5pKZzQmkwld2hNounVdk2OaIPM9MzpNn6I1mLk5J+wTnIZmfcWNAzNP9aQ==", "license": "MIT", "dependencies": { - "@cspell/url": "9.2.1", + "@cspell/url": "10.0.0", "import-meta-resolve": "^4.2.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/@cspell/filetypes": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.2.1.tgz", - "integrity": "sha512-Dy1y1pQ+7hi2gPs+jERczVkACtYbUHcLodXDrzpipoxgOtVxMcyZuo+84WYHImfu0gtM0wU2uLObaVgMSTnytw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-10.0.0.tgz", + "integrity": "sha512-UP57j9yrDtlCHpFxc/eGho1m8DP5olfu9KRWwd5fiqL9nMSE2rUJtPzQyvqmDwO5bVZt3B+fTVdo4gxuiqw25A==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.18.0" + } + }, + "node_modules/@cspell/rpc": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/rpc/-/rpc-10.0.0.tgz", + "integrity": "sha512-QrpOZMwz2pAjvl6Hky2PauYoMpLCASn3osjn7uKUbgFV70sahyj6tmx4rRgRX7vHu2WQLZev+YsuO4EujiBDOg==", + "license": "MIT", + "engines": { + "node": ">=22.18.0" } }, "node_modules/@cspell/strong-weak-map": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.2.1.tgz", - "integrity": "sha512-1HsQWZexvJSjDocVnbeAWjjgqWE/0op/txxzDPvDqI2sE6pY0oO4Cinj2I8z+IP+m6/E6yjPxdb23ydbQbPpJQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-10.0.0.tgz", + "integrity": "sha512-JRsato0s2IjYdsng+AGL6oAqgZVQgih5aWKdmxs21H6EdhMaoFDmRE5kXm/RT5a6OMdtnzQM9DqeToqBChWIOQ==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/@cspell/url": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.2.1.tgz", - "integrity": "sha512-9EHCoGKtisPNsEdBQ28tKxKeBmiVS3D4j+AN8Yjr+Dmtu+YACKGWiMOddNZG2VejQNIdFx7FwzU00BGX68ELhA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-10.0.0.tgz", + "integrity": "sha512-q+0pHQ8DbqjemyaOn/mTtBRbCuKDqhnsVbZ6J9zkTsxPgMpccjy0s5oLXwomfrrxMRBH+UcbERwtUmE+SbnoIQ==", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=22.18.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/array-timsort": { @@ -536,19 +585,10 @@ "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", "license": "MIT" }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/chalk": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", - "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -558,9 +598,9 @@ } }, "node_modules/chalk-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", - "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.2.tgz", + "integrity": "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==", "license": "MIT", "dependencies": { "chalk": "^5.2.0" @@ -572,234 +612,213 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, - "node_modules/clear-module": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", - "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", - "license": "MIT", - "dependencies": { - "parent-module": "^2.0.0", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", "engines": { "node": ">=20" } }, "node_modules/comment-json": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", - "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "license": "MIT", "dependencies": { "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" + "esprima": "^4.0.1" }, "engines": { "node": ">= 6" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, "node_modules/cspell": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.2.1.tgz", - "integrity": "sha512-PoKGKE9Tl87Sn/jwO4jvH7nTqe5Xrsz2DeJT5CkulY7SoL2fmsAqfbImQOFS2S0s36qD98t6VO+Ig2elEEcHew==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-10.0.0.tgz", + "integrity": "sha512-R25gsSR1SLlcGyw48fwJwp0PjXrVdl7RDO/Dm5+s4DvC1uQSlyiUxsr/8ZtbyC/MPeUJFQN9B4luqLlSm0WelQ==", "license": "MIT", "dependencies": { - "@cspell/cspell-json-reporter": "9.2.1", - "@cspell/cspell-pipe": "9.2.1", - "@cspell/cspell-types": "9.2.1", - "@cspell/dynamic-import": "9.2.1", - "@cspell/url": "9.2.1", - "chalk": "^5.6.0", - "chalk-template": "^1.1.0", - "commander": "^14.0.0", - "cspell-config-lib": "9.2.1", - "cspell-dictionary": "9.2.1", - "cspell-gitignore": "9.2.1", - "cspell-glob": "9.2.1", - "cspell-io": "9.2.1", - "cspell-lib": "9.2.1", + "@cspell/cspell-json-reporter": "10.0.0", + "@cspell/cspell-performance-monitor": "10.0.0", + "@cspell/cspell-pipe": "10.0.0", + "@cspell/cspell-types": "10.0.0", + "@cspell/cspell-worker": "10.0.0", + "@cspell/dynamic-import": "10.0.0", + "@cspell/url": "10.0.0", + "ansi-regex": "^6.2.2", + "chalk": "^5.6.2", + "chalk-template": "^1.1.2", + "commander": "^14.0.3", + "cspell-config-lib": "10.0.0", + "cspell-dictionary": "10.0.0", + "cspell-gitignore": "10.0.0", + "cspell-glob": "10.0.0", + "cspell-io": "10.0.0", + "cspell-lib": "10.0.0", "fast-json-stable-stringify": "^2.1.0", - "flatted": "^3.3.3", - "semver": "^7.7.2", - "tinyglobby": "^0.2.14" + "flatted": "^3.4.2", + "semver": "^7.7.4", + "tinyglobby": "^0.2.15" }, "bin": { "cspell": "bin.mjs", "cspell-esm": "bin.mjs" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" }, "funding": { "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" } }, "node_modules/cspell-config-lib": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.2.1.tgz", - "integrity": "sha512-qqhaWW+0Ilc7493lXAlXjziCyeEmQbmPMc1XSJw2EWZmzb+hDvLdFGHoX18QU67yzBtu5hgQsJDEDZKvVDTsRA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-10.0.0.tgz", + "integrity": "sha512-HWK7SRnJ3N/kOThw/uzmXmQYCzBxu58Jkq2hHyte1voDl118BeNFoaNRWMpYdHbBi3kCj8gaZu8wGtm+Zmdhxw==", "license": "MIT", "dependencies": { - "@cspell/cspell-types": "9.2.1", - "comment-json": "^4.2.5", - "smol-toml": "^1.4.2", - "yaml": "^2.8.1" + "@cspell/cspell-types": "10.0.0", + "comment-json": "^4.6.2", + "smol-toml": "^1.6.1", + "yaml": "^2.8.3" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-dictionary": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.2.1.tgz", - "integrity": "sha512-0hQVFySPsoJ0fONmDPwCWGSG6SGj4ERolWdx4t42fzg5zMs+VYGXpQW4BJneQ5Tfxy98Wx8kPhmh/9E8uYzLTw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-10.0.0.tgz", + "integrity": "sha512-KubSoEAJO+77KPSSWjoLCz0+MIWVNq3joGTSyxucAZrBSJD64Y1O4BHHr1aj6XHIZwXhWWNScQlrQR3OcIulng==", "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.2.1", - "@cspell/cspell-types": "9.2.1", - "cspell-trie-lib": "9.2.1", - "fast-equals": "^5.2.2" + "@cspell/cspell-performance-monitor": "10.0.0", + "@cspell/cspell-pipe": "10.0.0", + "@cspell/cspell-types": "10.0.0", + "cspell-trie-lib": "10.0.0", + "fast-equals": "^6.0.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-gitignore": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.2.1.tgz", - "integrity": "sha512-WPnDh03gXZoSqVyXq4L7t9ljx6lTDvkiSRUudb125egEK5e9s04csrQpLI3Yxcnc1wQA2nzDr5rX9XQVvCHf7g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-10.0.0.tgz", + "integrity": "sha512-55XLH9Y52eR7QgyV28Uaw8V9cN1YZ3PQIyrN9YBR4ndQNBKJxO9+jX1nwSspwnccCZiE/N+GGxFzRBb75JDSCw==", "license": "MIT", "dependencies": { - "@cspell/url": "9.2.1", - "cspell-glob": "9.2.1", - "cspell-io": "9.2.1" + "@cspell/url": "10.0.0", + "cspell-glob": "10.0.0", + "cspell-io": "10.0.0" }, "bin": { "cspell-gitignore": "bin.mjs" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-glob": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.2.1.tgz", - "integrity": "sha512-CrT/6ld3rXhB36yWFjrx1SrMQzwDrGOLr+wYEnrWI719/LTYWWCiMFW7H+qhsJDTsR+ku8+OAmfRNBDXvh9mnQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-10.0.0.tgz", + "integrity": "sha512-bXS35fMcA9X7GEkfnWBfoPd/vTnxxfXW+YHt6tWxu5fejfs00qUbjWp1oLC9FxRaXWxIkfsYp2mi1k1jYl4RVw==", "license": "MIT", "dependencies": { - "@cspell/url": "9.2.1", - "picomatch": "^4.0.3" + "@cspell/url": "10.0.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-grammar": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.2.1.tgz", - "integrity": "sha512-10RGFG7ZTQPdwyW2vJyfmC1t8813y8QYRlVZ8jRHWzer9NV8QWrGnL83F+gTPXiKR/lqiW8WHmFlXR4/YMV+JQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-10.0.0.tgz", + "integrity": "sha512-49udtYzkcCYEIDJbFOb4IwiAJebOYZnYvG6o6Ep19Tq0Xwjk7i4vxUprNiFNDCWFbcbJRPE9cpwQUVwp5WFGLw==", "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.2.1", - "@cspell/cspell-types": "9.2.1" + "@cspell/cspell-pipe": "10.0.0", + "@cspell/cspell-types": "10.0.0" }, "bin": { "cspell-grammar": "bin.mjs" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-io": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.2.1.tgz", - "integrity": "sha512-v9uWXtRzB+RF/Mzg5qMzpb8/yt+1bwtTt2rZftkLDLrx5ybVvy6rhRQK05gFWHmWVtWEe0P/pIxaG2Vz92C8Ag==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-10.0.0.tgz", + "integrity": "sha512-NQCAUhx9DwKApxPuFl7EK1K1XSaQEAPld45yjjwv93xF8rJkEGkgzOwjbqafwAD20eKYv1a7oj/9EC0S5jETSw==", "license": "MIT", "dependencies": { - "@cspell/cspell-service-bus": "9.2.1", - "@cspell/url": "9.2.1" + "@cspell/cspell-service-bus": "10.0.0", + "@cspell/url": "10.0.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-lib": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.2.1.tgz", - "integrity": "sha512-KeB6NHcO0g1knWa7sIuDippC3gian0rC48cvO0B0B0QwhOxNxWVp8cSmkycXjk4ijBZNa++IwFjeK/iEqMdahQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-10.0.0.tgz", + "integrity": "sha512-PowW6JEjuv/F2aFEirZvBxpzHdchOnpsUJbeIcFcai0++taLTbHQObROBEBf7e0S8DnHpVD5TZkqrTME5e44wg==", "license": "MIT", "dependencies": { - "@cspell/cspell-bundled-dicts": "9.2.1", - "@cspell/cspell-pipe": "9.2.1", - "@cspell/cspell-resolver": "9.2.1", - "@cspell/cspell-types": "9.2.1", - "@cspell/dynamic-import": "9.2.1", - "@cspell/filetypes": "9.2.1", - "@cspell/strong-weak-map": "9.2.1", - "@cspell/url": "9.2.1", - "clear-module": "^4.1.2", - "comment-json": "^4.2.5", - "cspell-config-lib": "9.2.1", - "cspell-dictionary": "9.2.1", - "cspell-glob": "9.2.1", - "cspell-grammar": "9.2.1", - "cspell-io": "9.2.1", - "cspell-trie-lib": "9.2.1", - "env-paths": "^3.0.0", - "fast-equals": "^5.2.2", - "gensequence": "^7.0.0", - "import-fresh": "^3.3.1", + "@cspell/cspell-bundled-dicts": "10.0.0", + "@cspell/cspell-performance-monitor": "10.0.0", + "@cspell/cspell-pipe": "10.0.0", + "@cspell/cspell-resolver": "10.0.0", + "@cspell/cspell-types": "10.0.0", + "@cspell/dynamic-import": "10.0.0", + "@cspell/filetypes": "10.0.0", + "@cspell/rpc": "10.0.0", + "@cspell/strong-weak-map": "10.0.0", + "@cspell/url": "10.0.0", + "cspell-config-lib": "10.0.0", + "cspell-dictionary": "10.0.0", + "cspell-glob": "10.0.0", + "cspell-grammar": "10.0.0", + "cspell-io": "10.0.0", + "cspell-trie-lib": "10.0.0", + "env-paths": "^4.0.0", + "gensequence": "^8.0.8", + "import-fresh": "^4.0.0", "resolve-from": "^5.0.0", "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0", "xdg-basedir": "^5.1.0" }, "engines": { - "node": ">=20" + "node": ">=22.18.0" } }, "node_modules/cspell-trie-lib": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.2.1.tgz", - "integrity": "sha512-qOtbL+/tUzGFHH0Uq2wi7sdB9iTy66QNx85P7DKeRdX9ZH53uQd7qC4nEk+/JPclx1EgXX26svxr0jTGISJhLw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-10.0.0.tgz", + "integrity": "sha512-R8qrMx10E/bm3Lecslwxn9XYo5NzSRK1rtandEX5n9UmEYHoBXjZELkg5+TOnV8VgrVaJSK57XtcGrbKp/4kSg==", "license": "MIT", - "dependencies": { - "@cspell/cspell-pipe": "9.2.1", - "@cspell/cspell-types": "9.2.1", - "gensequence": "^7.0.0" - }, "engines": { - "node": ">=20" + "node": ">=22.18.0" + }, + "peerDependencies": { + "@cspell/cspell-types": "10.0.0" } }, "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", + "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", "license": "MIT", + "dependencies": { + "is-safe-filename": "^0.1.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -819,9 +838,9 @@ } }, "node_modules/fast-equals": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", - "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.0.0.tgz", + "integrity": "sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -834,10 +853,13 @@ "license": "MIT" }, "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -848,81 +870,47 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/gensequence": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", - "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-8.0.8.tgz", + "integrity": "sha512-omMVniXEXpdx/vKxGnPRoO2394Otlze28TyxECbFVyoSpZ9H3EO7lemjcB12OpQJzRW4e5tt/dL1rOxry6aMHg==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", "license": "MIT", "dependencies": { - "ini": "4.1.1" + "ini": "6.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-4.0.0.tgz", + "integrity": "sha512-Fpi660c7VPDM3fPKYovStd9IP1CPOikf6v/dGxJJMmHPcwYQIMJ4W7kO1avBYEpMqkCh+Dx3Ln6H7VYqgztLjw==", "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, "engines": { - "node": ">=6" + "node": ">=22.15" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -934,30 +922,30 @@ } }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/parent-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", - "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "node_modules/is-safe-filename": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", + "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", "license": "MIT", - "dependencies": { - "callsites": "^3.1.0" - }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -966,15 +954,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -985,9 +964,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -997,9 +976,9 @@ } }, "node_modules/smol-toml": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.4.2.tgz", - "integrity": "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -1009,13 +988,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -1049,15 +1028,18 @@ } }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } } } diff --git a/package.json b/package.json index 81df088113..a158a38bf2 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,6 @@ "cspell": "cspell --config ./cSpell.json ./docs/**/*.md --no-progress --no-summary" }, "dependencies": { - "cspell": "^9.2.1" + "cspell": "^10.0.0" } } diff --git a/qodana.yaml b/qodana.yaml index c4d518d378..b355925cec 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -7,7 +7,7 @@ profile: path: profile.yaml dotnet: - solution: FluentAssertions.sln + solution: FluentAssertions.slnx frameworks: "!netstandard2.0;!netstandard2.1" exclude: @@ -24,7 +24,6 @@ exclude: - Tests/AssemblyA - Tests/AssemblyB - Tests/Benchmarks - - Tests/UWP.Specs - Src/FluentAssertions/Polyfill - name: UnusedMember.Global - name: ArrangeTrailingCommaInMultilineLists