[
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  push:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      # Checkout all commits, so we get previous tags\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n    # This is needed so that we can create git tags\n      - name: Set git user\n        run: |\n          git config --local user.name \"ci-bot\"\n          git config --local user.email \"github@clave.no\"\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20.x'\n          registry-url: 'https://registry.npmjs.org'\n\n      - name: Get version\n        id: version\n        run: echo \"version=$(npx -q git-conventional-commits version)\" >> $GITHUB_OUTPUT\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: 9.0.x\n\n      - name: Restore dependencies\n        run: dotnet restore\n\n      - name: Build\n        env:\n          VERSION: ${{ steps.version.outputs.version }}\n        run: dotnet build --no-restore --configuration release -p:Version=${{ env.VERSION }}\n\n      - name: Test\n        run: dotnet test --no-build --configuration release --logger \"trx;LogFileName=test-results.trx\"\n\n      - name: Report test results\n        uses: dorny/test-reporter@v1\n        if: success() || failure()        # run this step even if previous step failed\n        with:\n          name: Tests\n          path: '**/test-results.trx'\n          reporter: dotnet-trx\n          fail-on-error: 'false'\n\n      - name: Changelog\n        run: npx -q git-conventional-commits changelog --file CHANGELOG.md\n\n      - name: Pack\n        env:\n          VERSION: ${{ steps.version.outputs.version }}\n        run: dotnet pack --no-build --include-symbols --configuration release -p:PackageVersion=${{ env.VERSION }} --output ./nugets\n\n      - name: Push\n        run: dotnet nuget push \"**/*.nupkg\" --api-key ${{ secrets.NUGET }} --source https://api.nuget.org/v3/index.json --skip-duplicate\n\n      - name: Tag\n        env:\n          VERSION: ${{ steps.version.outputs.version }}\n        run: |\n          git commit -am \"Created release v${{ env.VERSION }}\"\n          git tag \"v${{ env.VERSION }}\"\n          git push origin \"v${{ env.VERSION }}\"\n          git push origin\n"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "content": "name: Check Pull-request\n\non: pull_request_target\n\njobs:\n  version:\n    name: Get version\n    outputs:\n      version: ${{ steps.version.outputs.version }}\n      changelog: ${{ steps.changelog.outputs.changelog }}\n    runs-on: ubuntu-latest\n    steps:\n      # Checkout all commits, so we get previous tags\n      # The ref and repository is needed since we use on: pull_request_target\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          #repository: ${{ github.event.pull_request.head.repo.full_name }}\n          fetch-depth: 0\n          fetch-tags: true\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20.x'\n          registry-url: 'https://registry.npmjs.org'\n\n      - name: Get version\n        id: version\n        env:\n          PR_REF: ${{ github.event.pull_request.head.ref }}\n        run: echo \"version=$(npx -q git-conventional-commits version)-${PR_REF}.${{ github.run_number }}\" >> $GITHUB_OUTPUT\n\n      # Generate changelog\n      - name: Get the changelog\n        id: conventional-commits\n        run: npx -q git-conventional-commits changelog --file 'temp_changelog.md'\n\n      - name: Output changelog\n        id: changelog\n        run: |\n          echo 'changelog<<EOF' >> $GITHUB_OUTPUT\n          cat temp_changelog.md >> $GITHUB_OUTPUT\n          echo 'EOF' >> $GITHUB_OUTPUT\n\n  test:\n    name: Run unit tests\n    runs-on: ubuntu-latest\n    needs: version\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          global-json-file: global.json\n          dotnet-version: 6.0.x\n\n      - name: Restore dependencies\n        run: dotnet restore\n\n      - name: Build\n        run: dotnet build --no-restore --configuration release -p:Version=${{ needs.version.outputs.version }}\n\n      - name: Test\n        run: dotnet test --no-build --configuration release --logger \"trx;LogFileName=test-results.trx\"\n\n      - name: Pack\n        run: dotnet pack --no-build --include-symbols --configuration release -p:PackageVersion=${{ needs.version.outputs.version }} --output ./nugets\n\n      - name: Upload test-results\n        uses: actions/upload-artifact@v4\n        if: success() || failure()        # run this step even if previous step failed\n        with:\n          name: test-results\n          path: '**/test-results.trx'\n\n      - name: Upload package\n        uses: actions/upload-artifact@v4\n        with:\n          name: package\n          path: '**/*.nupkg'\n\n  comment:\n    name: Comment on pull-request\n    runs-on: ubuntu-latest\n    needs: version\n    steps:\n      # Create a comment in the pull request using the tags created\n      - uses: marocchino/sticky-pull-request-comment@v1\n        if: needs.version.outputs.changelog != ''\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          message: |\n            The following versions will be created when this pull-request is merged:\n            ${{ needs.version.outputs.changelog }}\n            ---\n            Pre-release package `${{ needs.version.outputs.version }}` can be pushed to nuget\n\n      - uses: marocchino/sticky-pull-request-comment@v1\n        if: needs.version.outputs.changelog == ''\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          message: |\n            No packages will be created when this pull-request is merged\n\n  publish:\n    name: Publish pre-release\n    runs-on: ubuntu-latest\n    environment: pre-release\n    needs: test\n    timeout-minutes: 30\n    steps:\n      - name: Download artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: package\n          path: './nuget'\n\n      - name: Push\n        run: dotnet nuget push \"**/*.nupkg\" --api-key ${{ secrets.NUGET }} --source https://api.nuget.org/v3/index.json --skip-duplicate\n"
  },
  {
    "path": ".github/workflows/test-report.yml",
    "content": "name: 'Test Report'\non:\n  workflow_run:\n    workflows: ['Check Pull-request']\n    types:\n      - completed\njobs:\n  report:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: dorny/test-reporter@v1\n      with:\n        artifact: test-results            # artifact name\n        name: Tests                       # Name of the check run which will be created\n        path: '**/*.trx'                     # Path to test results (inside artifact .zip)\n        reporter: dotnet-trx              # Format of test results\n        fail-on-error: 'false'"
  },
  {
    "path": ".gitignore",
    "content": "bin/\nobj/\n/output\n/nugets\nTestResults/\n/.vs\n*.user\n/temp-changelog.md\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n   // Use IntelliSense to find out which attributes exist for C# debugging\n   // Use hover for the description of the existing attributes\n   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md\n   \"version\": \"0.2.0\",\n   \"configurations\": [\n        {\n            \"name\": \".NET Core Launch (console)\",\n            \"type\": \"coreclr\",\n            \"request\": \"launch\",\n            \"preLaunchTask\": \"build\",\n            // If you have changed target frameworks, make sure to update the program path.\n            \"program\": \"${workspaceFolder}/src/Clave.Expressionify.Tasks/bin/Debug/netcoreapp5.0/Clave.Expressionify.Tasks.dll\",\n            \"args\": [],\n            \"cwd\": \"${workspaceFolder}/src/Clave.Expressionify.Tasks\",\n            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console\n            \"console\": \"internalConsole\",\n            \"stopAtEntry\": false\n        },\n        {\n            \"name\": \".NET Core Attach\",\n            \"type\": \"coreclr\",\n            \"request\": \"attach\",\n            \"processId\": \"${command:pickProcess}\"\n        }\n    ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"dotnet-test-explorer.testProjectPath\": \"**/*.Tests.csproj\",\n    \"editor.codeLens\": true,\n    \"csharp.testsCodeLens.enabled\": true,\n    \"dotnet-test-explorer.showCodeLens\": true,\n    \"editor.formatOnSave\": true,\n    \"editor.tabSize\": 4,\n    \"editor.detectIndentation\": false,\n    \"dotnet-test-explorer.autoWatch\": false,\n    \"dotnet-test-explorer.addProblems\": true,\n    \"dotnet-test-explorer.treeMode\": \"flat\"\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"label\": \"build\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"build\",\n                \"${workspaceFolder}/src/Clave.Expressionify.Tasks/Clave.Expressionify.Tasks.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        },\n        {\n            \"label\": \"publish\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"publish\",\n                \"${workspaceFolder}/src/Clave.Expressionify.Tasks/Clave.Expressionify.Tasks.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        },\n        {\n            \"label\": \"watch\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"watch\",\n                \"run\",\n                \"${workspaceFolder}/src/Clave.Expressionify.Tasks/Clave.Expressionify.Tasks.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        }\n    ]\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## **10.0.0**&emsp;<sub><sup>2025-11-19 ([ba0427f...ba0427f](https://github.com/ClaveConsulting/Expressionify/compare/ba0427ffd6c2453538092f8ea503ba8af9499ba4...ba0427ffd6c2453538092f8ea503ba8af9499ba4?diff=split))</sup></sub>\n\n### Features\n\n- migrate to EF Core 10 and \\.NET 10 \\(\\#40\\) ([ba0427f](https://github.com/ClaveConsulting/Expressionify/commit/ba0427ffd6c2453538092f8ea503ba8af9499ba4))\n\n\n### BREAKING CHANGES\n-  Upgrade to EF Core 10 and \\.NET 10 ([ba0427f](https://github.com/ClaveConsulting/Expressionify/commit/ba0427ffd6c2453538092f8ea503ba8af9499ba4))\n\n\n  This is a major version upgrade that requires:\n  \\- \\.NET 10 SDK\n  \\- EF Core 10 packages\n  \\- Updated parameter naming in generated SQL \\(@\\_\\_p\\_0 \\-\\> @p\\)\n  \n  Core Changes:\n  \\- Upgrade Microsoft\\.EntityFrameworkCore from 9\\.0\\.0 to 10\\.0\\.0\n  \\- Upgrade to \\.NET 10 SDK and target framework \\(net10\\.0\\)\n  \\- Replace ParameterExtractingExpressionVisitor with ExpressionTreeFuncletizer\n  \\- Remove IParameterValues interface \\(replaced with Dictionary<string, object?\\>\\)\n  \\- Delete ParameterExtractingExpressionVisitor\\.cs \\(~700 lines of internal EF Core code\\)\n  \\- Update parameter detection strategy to check dictionary count after funcletization\n  \\- Update test expectations for simplified parameter naming\n  \\- Remove obsolete test \\(EF Core 10 optimizer handles constant folding\\)\n  \n  All 51 tests passing\\.\n  \n  Co\\-authored\\-by: Fabien Molinet <molinetf@medgate\\.ch\\>\n<br>\n\n## **9.1.0**&emsp;<sub><sup>2025-03-21 ([579137a...579137a](https://github.com/ClaveConsulting/Expressionify/compare/579137a7550db48071b2a568718011031d3a3244...579137a7550db48071b2a568718011031d3a3244?diff=split))</sup></sub>\n\n### Features\n\n- handle nullable propagation expression in arguments ([579137a](https://github.com/ClaveConsulting/Expressionify/commit/579137a7550db48071b2a568718011031d3a3244))\n\n<br>\n\n## **9.0.1**&emsp;<sub><sup>2025-03-21 ([a928912...a928912](https://github.com/ClaveConsulting/Expressionify/compare/a928912b9559dc3963cb1f2be47abf196b7ea991...a928912b9559dc3963cb1f2be47abf196b7ea991?diff=split))</sup></sub>\n\n*no relevant changes*\n<br>\n\n## **9.0.0**&emsp;<sub><sup>2024-12-27 ([0a25faa...ab25144](https://github.com/ClaveConsulting/Expressionify/compare/0a25faa81d7fef929450b4548bef80c7784b7869...ab251447ae87ffcca3c4c8d9f7d0492a63dcb604?diff=split))</sup></sub>\n\n\n### BREAKING CHANGES\n-  supports EF9 ([ab25144](https://github.com/ClaveConsulting/Expressionify/commit/ab251447ae87ffcca3c4c8d9f7d0492a63dcb604))\n\n\n  \\-\\-\\-\\-\\-\\-\\-\\-\\-\n  \n  Co\\-authored\\-by: Fabien Molinet <molinetf@medgate\\.ch\\>\n<br>\n\n## **6.7.1**&emsp;<sub><sup>2024-10-31 ([ad5c447...ddc49b2](https://github.com/ClaveConsulting/Expressionify/compare/ad5c4470f9eb8bc91284c556f719f01b6d0dab49...ddc49b22ebb0feeb77c6c4c7b460117a4a33ef74?diff=split))</sup></sub>\n\n*no relevant changes*\n<br>\n\n## **6.7.0**&emsp;<sub><sup>2022-12-14 ([50641f0...3fae05d](https://github.com/ClaveConsulting/Expressionify/compare/50641f0924d179f8c6cceb0ab1c1eea473ac9428...3fae05d19585f2ffa7b23d533c4ab16d98a61f10?diff=split))</sup></sub>\n\n### Features\n\n- Implemented generic methods ([50641f0](https://github.com/ClaveConsulting/Expressionify/commit/50641f0924d179f8c6cceb0ab1c1eea473ac9428))\n- Generic classes can contain expressionify methods ([3fae05d](https://github.com/ClaveConsulting/Expressionify/commit/3fae05d19585f2ffa7b23d533c4ab16d98a61f10))\n\n<br>\n\n## **6.6.4**&emsp;<sub><sup>2024-11-11 ([2658a0f...2658a0f](https://github.com/ClaveConsulting/Expressionify/compare/2658a0f86c3062e60e2391e43e25fcd690bbfe4f...2658a0f86c3062e60e2391e43e25fcd690bbfe4f?diff=split))</sup></sub>\n\n*no relevant changes*\n<br>\n\n## **6.6.3**&emsp;<sub><sup>2023-07-16 ([4c34cb9...4c34cb9](https://github.com/ClaveConsulting/Expressionify/compare/4c34cb964e517ec5609cc820d969011c7359c447...4c34cb964e517ec5609cc820d969011c7359c447?diff=split))</sup></sub>\n\n*no relevant changes*\n<br>\n\n## **6.6.2** <sub><sup>2022-12-14 ([174ff0d...753ab7a](https://github.com/ClaveConsulting/Expressionify/compare/174ff0d...753ab7a?diff=split))</sup></sub>\n\n*no relevant changes*\n\n## **6.6.1** <sub><sup>2022-12-12 ([6ae459e...2f5c15f](https://github.com/ClaveConsulting/Expressionify/compare/6ae459e...2f5c15f?diff=split))</sup></sub>\n\n### Bug Fixes\n*  Generated source files end in \\.g\\.cs ([6ae459e](https://github.com/ClaveConsulting/Expressionify/commit/6ae459e))\n*  tests ([2f5c15f](https://github.com/ClaveConsulting/Expressionify/commit/2f5c15f))\n\n\n### ???\n*  Replacing Environment\\.NewLine with actual new lines to make Git handle line ending differences between platforms\\. Ref https://github\\.com/dotnet/roslyn/issues/51437\\#issuecomment\\-784750434 ([aa1a1b5](https://github.com/ClaveConsulting/Expressionify/commit/aa1a1b5))\n\n\n## **6.6.0** <sub><sup>2022-11-04 ([e0c50b9...118d2eb](https://github.com/ClaveConsulting/Expressionify/compare/e0c50b9...118d2eb?diff=split))</sup></sub>\n\n### Features\n*  Added support for the EF compiled query cachen when using \\.UseExpressionify\\(\\) ([e0c50b9](https://github.com/ClaveConsulting/Expressionify/commit/e0c50b9))\n\n\n### Bug Fixes\n*  Mark Generator as development dependency ([d8650c2](https://github.com/ClaveConsulting/Expressionify/commit/d8650c2))\n*  Fixed failing tests and an outdated exception message ([1a82a24](https://github.com/ClaveConsulting/Expressionify/commit/1a82a24))\n*  Running tests against the pull request, not the target ([118d2eb](https://github.com/ClaveConsulting/Expressionify/commit/118d2eb))\n\n\n### ???\n*  Use result of ParameterExtractingExpressionVisitor ([6de12cc](https://github.com/ClaveConsulting/Expressionify/commit/6de12cc))\n*  Default query caching ([c56b4d3](https://github.com/ClaveConsulting/Expressionify/commit/c56b4d3))\n*  Renamed ExpressionEvaluationMode enums ([07f00a8](https://github.com/ClaveConsulting/Expressionify/commit/07f00a8))\n\n\n## **6.5.0** <sub><sup>2022-04-29 ([b8b62c1...b8b62c1](https://github.com/ClaveConsulting/Expressionify/compare/b8b62c1...b8b62c1?diff=split))</sup></sub>\n\n### Features\n*  Use QueryCompiler to support Include\\(\\) ([b8b62c1](https://github.com/ClaveConsulting/Expressionify/commit/b8b62c1))\n\n\n## **6.4.1** <sub><sup>2022-04-29 ([993a4c7...7e03b8c](https://github.com/ClaveConsulting/Expressionify/compare/993a4c7...7e03b8c?diff=split))</sup></sub>\n\n### Bug Fixes\n*  support all features that \\.Expressionify\\(\\) does ([993a4c7](https://github.com/ClaveConsulting/Expressionify/commit/993a4c7))\n\n\n### ???\n*  doc: Improved readme ([9e1032a](https://github.com/ClaveConsulting/Expressionify/commit/9e1032a))\n\n\n## **6.4.0** <sub><sup>2022-04-29 ([c8a8dce...01bb336](https://github.com/ClaveConsulting/Expressionify/compare/c8a8dce...01bb336?diff=split))</sup></sub>\n\n### Features\n*  Added \\.UseExpressionify\\(\\) on DbContext\\-Configuration, removing the need to always call \\.Expressionify\\(\\) on each query ([c8a8dce](https://github.com/ClaveConsulting/Expressionify/commit/c8a8dce))\n\n\n### ???\n*  Merge pull request \\#14 from jhartmann123/db\\-context\\-options ([cd6b113](https://github.com/ClaveConsulting/Expressionify/commit/cd6b113))\n*  run on ubuntu ([ba62561](https://github.com/ClaveConsulting/Expressionify/commit/ba62561))\n*  Updated dependencies and inlined the test\\-report ([fc8a819](https://github.com/ClaveConsulting/Expressionify/commit/fc8a819))\n*  Fixed linefeed difference between windows and linux ([69e8a80](https://github.com/ClaveConsulting/Expressionify/commit/69e8a80))\n*  other way around ([caf6cd6](https://github.com/ClaveConsulting/Expressionify/commit/caf6cd6))\n*  Fixed failed build ([01bb336](https://github.com/ClaveConsulting/Expressionify/commit/01bb336))\n\n\n## **6.4.0** <sub><sup>2022-04-21 ([c8a8dce...c8a8dce](https://github.com/ClaveConsulting/Expressionify/compare/c8a8dce...c8a8dce?diff=split))</sup></sub>\n\n### Features\n*  Added \\.UseExpressionify\\(\\) on DbContext\\-Configuration, removing the need to always call \\.Expressionify\\(\\) on each query ([c8a8dce](https://github.com/ClaveConsulting/Expressionify/commit/c8a8dce))"
  },
  {
    "path": "Directory.build.targets",
    "content": "<Project>\n  <Target Name=\"Wipe\" AfterTargets=\"Clean\">\n    <RemoveDir Directories=\"$(TargetDir)\" /> <!-- bin -->\n    <RemoveDir Directories=\"$(ProjectDir)$(BaseIntermediateOutputPath)\" /> <!-- obj -->\n  </Target>\n</Project>"
  },
  {
    "path": "Expressionify.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 17\r\nVisualStudioVersion = 17.0.32112.339\r\nMinimumVisualStudioVersion = 15.0.26124.0\r\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"src\", \"src\", \"{3B1DEF26-D170-4DCD-8B0D-36D845932167}\"\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Clave.Expressionify\", \"src\\Clave.Expressionify\\Clave.Expressionify.csproj\", \"{8D60D663-B6FF-4158-AE86-E352756D001C}\"\r\nEndProject\r\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"tests\", \"tests\", \"{4CD5351B-51D6-421B-889F-88576FA41B09}\"\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Clave.Expressionify.Tests\", \"tests\\Clave.Expressionify.Tests\\Clave.Expressionify.Tests.csproj\", \"{E4AA7774-4C03-4883-B51A-F9171D4F27F0}\"\r\nEndProject\r\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{942AA318-76C0-464F-9ECA-51F55157A1F5}\"\r\n\tProjectSection(SolutionItems) = preProject\r\n\t\tDirectory.build.targets = Directory.build.targets\r\n\t\tgit-conventional-commits.json = git-conventional-commits.json\r\n\t\t.github\\workflows\\publish.yml = .github\\workflows\\publish.yml\r\n\t\tReadme.md = Readme.md\r\n\t\t.github\\workflows\\pull-request.yml = .github\\workflows\\pull-request.yml\r\n\t\t.github\\workflows\\test-report.yml = .github\\workflows\\test-report.yml\r\n\tEndProjectSection\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Clave.Expressionify.Generator\", \"src\\Clave.Expressionify.Generator\\Clave.Expressionify.Generator.csproj\", \"{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}\"\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Clave.Expressionify.Generator.Tests\", \"tests\\Clave.Expressionify.Generator.Tests\\Clave.Expressionify.Generator.Tests.csproj\", \"{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|Any CPU = Debug|Any CPU\r\n\t\tDebug|x64 = Debug|x64\r\n\t\tDebug|x86 = Debug|x86\r\n\t\tRelease|Any CPU = Release|Any CPU\r\n\t\tRelease|x64 = Release|x64\r\n\t\tRelease|x86 = Release|x86\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Debug|x64.ActiveCfg = Debug|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Debug|x64.Build.0 = Debug|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Debug|x86.ActiveCfg = Debug|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Debug|x86.Build.0 = Debug|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Release|x64.ActiveCfg = Release|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Release|x64.Build.0 = Release|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Release|x86.ActiveCfg = Release|Any CPU\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C}.Release|x86.Build.0 = Release|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Debug|x64.ActiveCfg = Debug|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Debug|x64.Build.0 = Debug|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Debug|x86.ActiveCfg = Debug|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Debug|x86.Build.0 = Debug|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Release|x64.ActiveCfg = Release|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Release|x64.Build.0 = Release|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Release|x86.ActiveCfg = Release|Any CPU\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0}.Release|x86.Build.0 = Release|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Debug|x64.ActiveCfg = Debug|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Debug|x64.Build.0 = Debug|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Debug|x86.ActiveCfg = Debug|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Debug|x86.Build.0 = Debug|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Release|x64.ActiveCfg = Release|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Release|x64.Build.0 = Release|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Release|x86.ActiveCfg = Release|Any CPU\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6}.Release|x86.Build.0 = Release|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Debug|x64.ActiveCfg = Debug|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Debug|x64.Build.0 = Debug|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Debug|x86.ActiveCfg = Debug|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Debug|x86.Build.0 = Debug|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Release|x64.ActiveCfg = Release|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Release|x64.Build.0 = Release|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Release|x86.ActiveCfg = Release|Any CPU\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE}.Release|x86.Build.0 = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(NestedProjects) = preSolution\r\n\t\t{8D60D663-B6FF-4158-AE86-E352756D001C} = {3B1DEF26-D170-4DCD-8B0D-36D845932167}\r\n\t\t{E4AA7774-4C03-4883-B51A-F9171D4F27F0} = {4CD5351B-51D6-421B-889F-88576FA41B09}\r\n\t\t{4AC8A74F-99FB-4396-B2FD-71B9BDD788D6} = {3B1DEF26-D170-4DCD-8B0D-36D845932167}\r\n\t\t{1A89B026-78F1-44AE-B0D3-2D6BEC732AFE} = {4CD5351B-51D6-421B-889F-88576FA41B09}\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {E0421317-E314-433D-A10E-9C19B5AF343C}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "License.md",
    "content": "The MIT License\n\nCopyright (c) Clave Consulting\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Readme.md",
    "content": "# Expressionify\n\n[![Nuget](https://img.shields.io/nuget/v/Clave.Expressionify)][1] [![Nuget](https://img.shields.io/nuget/dt/Clave.Expressionify)][1] [![Build Status](https://claveconsulting.visualstudio.com/Nugets/_apis/build/status/ClaveConsulting.Expressionify?branchName=master)][2] [![Azure DevOps tests](https://img.shields.io/azure-devops/tests/ClaveConsulting/Nugets/14)][2]\n\n> Use extension methods in Entity Framework Core queries\n\n## Installing\n\nInstall these two nuget packages:\n\n* `Clave.Expressionify`\n* `Clave.Expressionify.Generator`\n\nMake sure to install the second one properly:\n\n```xml\n  <ItemGroup>\n    <PackageReference Include=\"Clave.Expressionify\" Version=\"6.6.0\" />\n    <PackageReference Include=\"Clave.Expressionify.Generator\" Version=\"6.6.0\">\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n  </ItemGroup>\n```\n\n## How to use\n\n0) Setup your database context with `.UseExpressionify()`\n1) Mark the `public static` expression method with the `[Expressionify]` attribute.\n2) Mark the class with the method as `partial`.\n3) Use the extension method in the query\n\n\n## Example\n\nLets say you have this code:\n\n```csharp\nvar users = await db.Users\n    .Where(user => user.DateOfBirth < DateTime.Now.AddYears(-18))\n    .ToListAsync();\n```\n\nThat second line is a bit long, so it would be nice to pull it out as a reusable extension method:\n\n```csharp\npublic static class Extensions\n{\n    public static bool IsOver18(this User user)\n        => user.DateOfBirth < DateTime.Now.AddYears(-18);\n}\n\n// ...\n\nvar users = await db.Users\n    .Where(user => user.IsOver18())\n    .ToListAsync();\n\n```\n\nUnfortunately this forces Entity Framework to run the query in memory, rather than in the database. That's not very efficient...\n\nBut, with just one additional line of code we can get Entity Framework to understand how translate our extension method to SQL\n\n```diff\n- public static class Extensions\n+ public static partial class Extensions\n  {\n+     [Expressionify]\n      public static bool IsOver18(this User user)\n          => user.DateOfBirth < DateTime.Now.AddYears(-18);\n  }\n\n```\n\n## Setup\n\nThe simplest way to add expressionify support is to configure the database context:\n\n```csharp\nservices\n    .AddDbContext<MyDbContext>(o => o\n        .UseSqlServer(configuration.GetConnectionString(\"DefaultConnection\"))\n        .UseExpressionify());\n```\n\nMake sure to call `.UseExpressionify()` after `.UseSqlServer()` (or whatever other sql provider you want to use).\n\nThe alternative is to only call `.Expressionify()` in the queries where you want it:\n\n```csharp\nvar users = await db.Users\n    .Expressionify()\n    .Where(user => user.DateOfBirth < DateTime.Now.AddYears(-18))\n    .ToListAsync();\n```\n\n### Query caching\n\nWhen configuring the DbContext with `.UseExpressionify()`, Expressionify tries to use the EntityFramework query cache by default. This way the expression tree is only processed once and then cached. \nHowever, this comes with [some limitations](#query-caching-limitations). Expressionify throws an exception if your query cannot be cached. \nTo fix this, you either have to call `.Expressionify()` explicitly on the query or disable query caching:\n\n```csharp\n.UseExpressionify(o => o.WithEvaluationMode(ExpressionEvaluationMode.FullCompatibilityButSlow));\n```\n\n## Upgrading from 3.1 to 5.0\n\nVersion 5 works with net 5.0, and has a few other changes. It relies on [Source generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) and Roslyn Analyzers for generating the code, instead of some very clumpsy msbuild code. This means that you will get help if you forget to mark the methods correctly. \n\nThese are the breaking changes:\n* The class containing the method no longer needs to be `static`.\n* The class containing the method now has to be marked as `partial`.\n* The method no longer needs to be `public`, it can be private or internal.\n\n## Limitations\n\nExpressionify uses the Roslyn code analyzer and generator to look for `static` methods with expression bodies tagged with the `[Expressionify]` attribute in `partial` classes.\n\n```csharp\npublic static partial class Extensions {\n    // ✔ OK\n    [Expressionify]\n    public static int ToInt(this string value) => Convert.ToInt32(value);\n\n    // ✔ OK (it can be private)\n    [Expressionify]\n    private static int ToInt(this string value) => Convert.ToInt32(value);\n    \n    // ❌ Not ok (it's not static)\n    [Expressionify]\n    public int ToInt(this string value) => Convert.ToInt32(value);\n\n    // ❌ Not ok (it's missing the attribute)\n    public static int ToInt(this string value) => Convert.ToInt32(value);\n    \n    // ❌ Not ok (it doesn't have an expression body)\n    [Expressionify]\n    public static int ToInt(this string value)\n    {\n        return Convert.ToInt32(value);\n    }\n}\n\n// ❌ Not ok (it's not a partial class)\npublic static class Extensions {\n    [Expressionify]\n    public static int ToInt(this string value) => Convert.ToInt32(value);\n}\n\n```\n\n### Query caching limitations\n\nUsing [query caching](#query-caching) works fine unless you introduce new query parameters in your `[Expressionify]` method. In that case you'll get an `InvalidOperationException` telling you to explicitly call `.Expressionify()` on the query, as the query cannot be translated.\n\nExamples:\n```csharp\npublic static partial class Extensions {\n   // Example: users.Where(u => u.IsOver18())\n   // ✔ OK for ExpressionEvaluationMode.FullCompatibilityButSlow\n   // ✔ OK for ExpressionEvaluationMode.LimitedCompatibilityButCached\n   // The expression can be translated to SQL without introducing new parameters\n   [Expressionify]\n   public static bool IsOver18(this User user)\n       => user.DateOfBirth < DateTime.Now.AddYears(-18);\n\n   // Example: users.Where(u => u.IsOlderThan(18))\n   // ✔ OK for ExpressionEvaluationMode.FullCompatibilityButSlow\n   // ✔ OK for ExpressionEvaluationMode.LimitedCompatibilityButCached\n   // The parameter 'years' is already present in the query itself. No new parameters are introduced when expanding the query.\n   [Expressionify]\n   public static bool IsOlderThan(this User user, int years)\n       => user.DateOfBirth < DateTime.Now.AddYears(-years);\n\n   // Example: users.Where(u => u.WasAddedRecently())\n   // ✔ OK for ExpressionEvaluationMode.FullCompatibilityButSlow\n   // ❌ Not ok for ExpressionEvaluationMode.LimitedCompatibilityButCached\n   // ✔ OK for ExpressionEvaluationMode.LimitedCompatibilityButCached when explicitly expanding the query with 'query.Expressionify()'\n   // 'TimeProvider.UtcNow' is a new parameter that is not known in the query before calling '.Expressionify()'.\n   [Expressionify]\n   public static bool WasAddedRecently(this User user)\n       => user.Created >= TimeProvider.UtcNow.AddDays(-1);\n\n   // Example: users.Select(u => u.ToTestView(null))\n   // ✔ OK for ExpressionEvaluationMode.FullCompatibilityButSlow\n   // ❌ Not ok for ExpressionEvaluationMode.LimitedCompatibilityButCached\n   // ✔ OK for ExpressionEvaluationMode.LimitedCompatibilityButCached when explicitly expanding the query with 'query.Expressionify()'\n   // With the input 'null' on the address, the expression 'address == null ? null : address.Street' gets replaced with a\n   // new parameter for the value 'null'.\n   [Expressionify]\n   public static TestView ToTestView(this TestEntity testEntity, TestAddress? address)\n       => new() { Name = testEntity.Name, Street = address == null ? null : address.Street };\n}\n```\n\n## Inspiration and help\n\nThe first part of this project relies heavily on the work done by [Luke McGregor](https://twitter.com/staticv0id) in his [LinqExpander](https://github.com/lukemcgregor/LinqExpander) project, as described in his article on [composable repositories - nesting expressions](https://blog.staticvoid.co.nz/2016/composable_repositories_-_nesting_extensions/), and on the updated code by [Ben Cull](https://twitter.com/BenWhoLikesBeer) in his article [Expression and Projection Magic for Entity Framework Core ](https://benjii.me/2018/01/expression-projection-magic-entity-framework-core/).\n\nThe second part of this project uses Roslyn to analyze and generate code, and part of it is built directly on code by [Carlos Mendible](https://twitter.com/cmendibl3) from his article [Create a class with .NET Core and Roslyn](https://carlos.mendible.com/2017/03/02/create-a-class-with-net-core-and-roslyn/).\n\nThe rest is stitched together from various Stack Overflow answers and code snippets found on GitHub.\n\n\n\n[1]: https://www.nuget.org/packages/Clave.Expressionify/\n[2]: https://claveconsulting.visualstudio.com/Nugets/_build/latest?definitionId=14\n"
  },
  {
    "path": "git-conventional-commits.json",
    "content": "{\n  \"convention\": {\n    \"commitTypes\": [\n      \"feat\",\n      \"fix\",\n      \"perf\",\n      \"refactor\",\n      \"style\",\n      \"test\",\n      \"build\",\n      \"ops\",\n      \"docs\",\n      \"merge\"\n    ],\n    \"commitScopes\": [],\n    \"releaseTagGlobPattern\": \"v[0-9]*.[0-9]*.[0-9]*\",\n    \"issueRegexPattern\": \"(^|\\\\s)#\\\\d+(\\\\s|$)\"\n  },\n  \"changelog\": {\n    \"commitTypes\": [\n      \"feat\",\n      \"fix\",\n      \"perf\",\n      \"merge\",\n      \"?\"\n    ],\n    \"commitScopes\": [],\n    \"commitIgnoreRegexPattern\": \"^(WIP|chore)\",\n    \"includeInvalidCommits\": false,\n    \"headlines\": {\n      \"feat\": \"Features\",\n      \"fix\": \"Bug Fixes\",\n      \"perf\": \"Performance Improvements\",\n      \"merge\": \"Merged Branches\",\n      \"breakingChange\": \"BREAKING CHANGES\"\n    },\n    \"commitUrl\": \"https://github.com/ClaveConsulting/Expressionify/commit/%commit%\",\n    \"commitRangeUrl\": \"https://github.com/ClaveConsulting/Expressionify/compare/%from%...%to%?diff=split\",\n    \"issueUrl\": \"https://github.com/ClaveConsulting/Expressionify/issues/%issue%\"\n  }\n}"
  },
  {
    "path": "global.json",
    "content": "{\n  \"sdk\": {\n    \"version\": \"10.0.100\",\n    \"rollForward\": \"feature\"\n  }\n}"
  },
  {
    "path": "src/Clave.Expressionify/Clave.Expressionify.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\r\n\r\n    <PropertyGroup>\r\n        <TargetFramework>net10.0</TargetFramework>\r\n        <DebugType>embedded</DebugType>\r\n        <LangVersion>latest</LangVersion>\r\n        <Nullable>enable</Nullable>\r\n    </PropertyGroup>\r\n\r\n    <ItemGroup>\r\n        <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"10.0.0\" />\r\n    </ItemGroup>\r\n\r\n    <PropertyGroup>\r\n        <Title>Clave.Expressionify</Title>\r\n        <PackageIconUrl>https://raw.githubusercontent.com/ClaveConsulting/logo/master/png/logo_noText.png</PackageIconUrl>\r\n        <RepositoryUrl>https://github.com/ClaveConsulting/Expressionify</RepositoryUrl>\r\n        <PackageProjectUrl>https://github.com/ClaveConsulting/Expressionify</PackageProjectUrl>\r\n        <Authors>Clave Consulting</Authors>\r\n        <Description>Use extension methods in Entity Framework Core queries</Description>\r\n        <PackageLicenseExpression>MIT</PackageLicenseExpression>\r\n    </PropertyGroup>\r\n\r\n</Project>\r\n"
  },
  {
    "path": "src/Clave.Expressionify/DbContextOptionsExtensions.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\n\nnamespace Clave.Expressionify\n{\n    public static class DbContextOptionsExtensions\n    {\n        /// <summary>\n        /// Use Expressionify within your queries.\n        /// Transforms your expressions by replacing any [Expressionify] extension methods with the expressionised versions of those methods.\n        /// </summary>\n        public static DbContextOptionsBuilder<TContext> UseExpressionify<TContext>(this DbContextOptionsBuilder<TContext> optionsBuilder, Action<ExpressionifyDbContextOptionsBuilder>? expressionifyOptionsAction = null)\n            where TContext : DbContext\n        {\n            return (DbContextOptionsBuilder<TContext>)UseExpressionify((DbContextOptionsBuilder)optionsBuilder, expressionifyOptionsAction);\n        }\n\n        /// <summary>\n        /// Use Expressionify within your queries.\n        /// Transforms your expressions by replacing any [Expressionify] extension methods with the expressionised versions of those methods.\n        /// </summary>\n        public static DbContextOptionsBuilder UseExpressionify(this DbContextOptionsBuilder optionsBuilder, Action<ExpressionifyDbContextOptionsBuilder>? expressionifyOptionsAction = null)\n        {\n            var extension = GetOrCreateExtension(optionsBuilder);\n            ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);\n\n            expressionifyOptionsAction?.Invoke(new ExpressionifyDbContextOptionsBuilder(optionsBuilder));\n\n            return optionsBuilder;\n        }\n\n        private static ExpressionifyDbContextOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)\n            => optionsBuilder.Options.FindExtension<ExpressionifyDbContextOptionsExtension>()\n            ?? new ExpressionifyDbContextOptionsExtension();\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionEvaluationMode.cs",
    "content": "﻿namespace Clave.Expressionify;\n\npublic enum ExpressionEvaluationMode\n{\n    /// <summary> Always check for <code>[Expressionify]</code> extension methods when executing a query. </summary>\n    FullCompatibilityButSlow = 0,\n\n    /// <summary>\n    /// Use the EF compiled query cache and only check for <code>[Expressionify]</code> extension methods when a query gets cached.<br/>\n    /// Not all queries work with this mode enabled. For those queries who don't, you get an <code>InvalidOperationException</code> and you need\n    /// to call <code>query.Expressionify()</code> explicitly.<br/>\n    /// This is the case for <code>[Expressionify]</code>-methods that introduce new query-parameters either directly, or indirectly via an EF optimization.\n    /// </summary>\n    LimitedCompatibilityButCached = 1\n}"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionableQuery.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\n\nnamespace Clave.Expressionify\n{\n    public class ExpressionableQuery<T> : IQueryable<T>, IOrderedQueryable<T>, IAsyncEnumerable<T>\n    {\n        private readonly ExpressionableQueryProvider _provider;\n\n        public ExpressionableQuery(ExpressionableQueryProvider provider, Expression expression)\n        {\n            _provider = provider;\n            Expression = expression;\n        }\n\n        IEnumerator<T> IEnumerable<T>.GetEnumerator()\n        {\n            return _provider.ExecuteQuery<T>(Expression).GetEnumerator();\n        }\n\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            return _provider.ExecuteQuery<T>(Expression).GetEnumerator();\n        }\n\n        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)\n        {\n            return _provider.ExecuteQueryAsync<T>(Expression).GetAsyncEnumerator(cancellationToken);\n        }\n\n        public Type ElementType => typeof(T);\n\n        public Expression Expression { get; }\n\n        public IQueryProvider Provider => _provider;\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionableQueryCompiler.cs",
    "content": "﻿using System;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\n\nnamespace Clave.Expressionify\n{\n    [System.Diagnostics.CodeAnalysis.SuppressMessage(\"Usage\", \"EF1001:Internal EF Core API usage.\", Justification = \"<Pending>\")]\n    public class ExpressionableQueryCompiler : IQueryCompiler\n    {\n        private readonly IQueryCompiler _decoratedCompiler;\n\n        public ExpressionableQueryCompiler(IQueryCompiler decoratedCompiler)\n        {\n            _decoratedCompiler = decoratedCompiler;\n        }\n        public Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query) => _decoratedCompiler.CreateCompiledAsyncQuery<TResult>(Visit(query));\n\n        public Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query) => _decoratedCompiler.CreateCompiledQuery<TResult>(Visit(query));\n\n        public TResult Execute<TResult>(Expression query) => _decoratedCompiler.Execute<TResult>(Visit(query));\n\n        public TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken) => _decoratedCompiler.ExecuteAsync<TResult>(Visit(query), cancellationToken);\n\n        private static Expression Visit(Expression exp) => new ExpressionifyVisitor().Visit(exp);\n\n#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.\n        public Expression<Func<QueryContext, TResult>> PrecompileQuery<TResult>(Expression query, bool async) => _decoratedCompiler.PrecompileQuery<TResult>(query, async);\n#pragma warning restore EF9100\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionableQueryProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Query;\n\nnamespace Clave.Expressionify\n{\n    public class ExpressionableQueryProvider : IAsyncQueryProvider\n    {\n        private readonly IQueryProvider _underlyingQueryProvider;\n\n        public ExpressionableQueryProvider(IQueryProvider underlyingQueryProvider)\n        {\n            _underlyingQueryProvider = underlyingQueryProvider;\n        }\n\n        public IQueryable<TElement> CreateQuery<TElement>(Expression expression) => new ExpressionableQuery<TElement>(this, expression);\n\n        public IQueryable CreateQuery(Expression expression)\n        {\n            try\n            {\n                var type = expression.Type.GetElementType();\n\n                if(type == null) throw new Exception($\"Expression type is strange {expression.Type.FullName}\");\n\n                return typeof(ExpressionableQuery<>)\n                    .MakeGenericType(type)\n                    .CreateInstance<IQueryable>(this, expression);\n            }\n            catch (System.Reflection.TargetInvocationException e)\n            {\n                throw e.InnerException ?? e;\n            }\n        }\n\n        internal IEnumerable<T> ExecuteQuery<T>(Expression expression) => _underlyingQueryProvider.CreateQuery<T>(Visit(expression)).AsEnumerable();\n\n        internal IAsyncEnumerable<T> ExecuteQueryAsync<T>(Expression expression) => _underlyingQueryProvider.CreateQuery<T>(Visit(expression)).AsAsyncEnumerable();\n\n        public TResult Execute<TResult>(Expression expression) => _underlyingQueryProvider.Execute<TResult>(Visit(expression));\n\n        public object? Execute(Expression expression) => _underlyingQueryProvider.Execute(Visit(expression));\n\n        public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)\n        {\n            if (_underlyingQueryProvider is IAsyncQueryProvider provider)\n            {\n                return provider.ExecuteAsync<TResult>(Visit(expression), cancellationToken);\n            }\n\n            throw new Exception(\"This shouldn't happen\");\n        }\n\n        private static Expression Visit(Expression exp) => new ExpressionifyVisitor().Visit(exp);\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyAttribute.cs",
    "content": "﻿using System;\r\n\r\nnamespace Clave.Expressionify\r\n{\r\n    [AttributeUsage(AttributeTargets.Method)]\r\n    public class ExpressionifyAttribute : Attribute\r\n    {\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyDbContextOptionsBuilder.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\n\nnamespace Clave.Expressionify;\n\npublic class ExpressionifyDbContextOptionsBuilder\n{\n    private readonly DbContextOptionsBuilder _optionsBuilder;\n\n    internal ExpressionifyDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder) => _optionsBuilder = optionsBuilder;\n\n    public ExpressionifyDbContextOptionsBuilder WithEvaluationMode(ExpressionEvaluationMode mode) => WithOption(e => e.WithEvaluationMode(mode));\n\n    /// <summary>\n    ///     Sets an option by cloning the extension used to store the settings. This ensures the builder\n    ///     does not modify options that are already in use elsewhere.\n    /// </summary>\n    /// <param name=\"setAction\">An action to set the option.</param>\n    /// <returns>The same builder instance so that multiple calls can be chained.</returns>\n    private ExpressionifyDbContextOptionsBuilder WithOption(Func<ExpressionifyDbContextOptionsExtension, ExpressionifyDbContextOptionsExtension> setAction)\n    {\n        var extension = setAction(_optionsBuilder.Options.FindExtension<ExpressionifyDbContextOptionsExtension>()!);\n        ((IDbContextOptionsBuilderInfrastructure)_optionsBuilder).AddOrUpdateExtension(extension);\n\n        return this;\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyDbContextOptionsExtension.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.DependencyInjection.Extensions;\n\nnamespace Clave.Expressionify\n{\n    public class ExpressionifyDbContextOptionsExtension : IDbContextOptionsExtension\n    {\n        public ExpressionifyDbContextOptionsExtension()\n        { }\n\n        public ExpressionifyDbContextOptionsExtension(ExpressionifyDbContextOptionsExtension copyFrom)\n        {\n            EvaluationMode = copyFrom.EvaluationMode;\n        }\n\n        public DbContextOptionsExtensionInfo Info => new ExtensionInfo(this);\n        public ExpressionEvaluationMode EvaluationMode { get; private set; } = ExpressionEvaluationMode.LimitedCompatibilityButCached;\n\n        [System.Diagnostics.CodeAnalysis.SuppressMessage(\"Usage\", \"EF1001:Internal EF Core API usage.\", Justification = \"<Pending>\")]\n        public void ApplyServices(IServiceCollection services)\n        {\n            if (EvaluationMode == ExpressionEvaluationMode.FullCompatibilityButSlow)\n                AddDecorator<IQueryCompiler, ExpressionableQueryCompiler>(services);\n\n            else if (EvaluationMode == ExpressionEvaluationMode.LimitedCompatibilityButCached)\n                AddDecorator<IQueryTranslationPreprocessorFactory, ExpressionifyQueryTranslationPreprocessorFactory>(services);\n\n            else\n                throw new NotSupportedException($\"Unsupported {nameof(EvaluationMode)}\");\n        }\n\n        private static void AddDecorator<TService, TDecorator>(IServiceCollection services)\n            where TDecorator : TService\n        {\n            var descriptor = services.FirstOrDefault(s => s.ServiceType == typeof(TService));\n            if (descriptor == null || descriptor.ImplementationType == null && descriptor.ImplementationFactory == null && descriptor.ImplementationInstance == null)\n                throw new InvalidOperationException($\"No {typeof(TService).Name} is configured yet. Please configure a database provider first.\");\n\n            // Replace service with decorator. Factory creates the decorator with an instance of the decorated type, based on the original registration.\n            services.Replace(ServiceDescriptor.Describe(\n                descriptor.ServiceType,\n                provider => ActivatorUtilities.CreateInstance(provider, typeof(TDecorator), GetInstance(provider, descriptor)),\n                descriptor.Lifetime));\n\n            static object GetInstance(IServiceProvider provider, ServiceDescriptor descriptor)\n            {\n                return descriptor.ImplementationInstance\n                    ?? descriptor.ImplementationFactory?.Invoke(provider)\n                    ?? ActivatorUtilities.GetServiceOrCreateInstance(provider, descriptor.ImplementationType!);\n            }\n        }\n\n        public void Validate(IDbContextOptions options)\n        {\n            // No options to validate\n        }\n\n        public ExpressionifyDbContextOptionsExtension WithEvaluationMode(ExpressionEvaluationMode evaluationMode)\n        {\n            var clone = Clone();\n            clone.EvaluationMode = evaluationMode;\n            return clone;\n        }\n\n        private ExpressionifyDbContextOptionsExtension Clone() => new(this);\n\n        private class ExtensionInfo : DbContextOptionsExtensionInfo\n        {\n            private readonly ExpressionifyDbContextOptionsExtension _extension;\n\n            public ExtensionInfo(ExpressionifyDbContextOptionsExtension extension) : base(extension)\n            {\n                _extension = extension;\n            }\n\n            public override bool IsDatabaseProvider => false;\n            public override string LogFragment => string.Empty;\n\n            public override int GetServiceProviderHashCode()\n            {\n                // Hash all options here\n                return _extension.EvaluationMode.GetHashCode();\n            }\n\n            public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)\n            {\n                // Check if all options are the same\n                return other is ExtensionInfo otherInfo\n                    && otherInfo._extension.EvaluationMode == _extension.EvaluationMode;\n            }\n\n            public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)\n            {\n                debugInfo[\"Expressionify:EvaluationMode\"] = _extension.EvaluationMode.ToString();\n                // Add values of options here\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyExtension.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Reflection;\n\nnamespace Clave.Expressionify\n{\n    public static class ExpressionifyExtension\n    {\n        /// <summary>\n        /// Transforms your expression by replacing any [Expressionify]\n        /// extension methods with the expressionised versions of those methods. This allows the extensions to be used\n        /// by another visitor such as EntityFramework. Should be used at the start of a query.\n        /// </summary>\n        /// <typeparam name=\"T\">the type of the queryable</typeparam>\n        /// <param name=\"source\">The input queryable</param>\n        /// <returns>A queryable which has any of the tagged extension methods replaced.</returns>\n        public static IQueryable<T> Expressionify<T>(this IQueryable<T> source)\n        {\n            if (source is ExpressionableQuery<T> result)\n            {\n                return result;\n            }\n\n            return new ExpressionableQueryProvider(source.Provider).CreateQuery<T>(source.Expression);\n        }\n\n        internal static bool MatchesTypeOf(this MethodInfo property, MethodInfo method)\n        {\n            var methodTypes = method.GetParameters().Select(p => p.ParameterType).Concat(new[] { method.ReturnType });\n            var propertyTypes = property.ReturnType.GetGenericArguments()[0].GetGenericArguments();\n\n            return methodTypes.SequenceEqual(propertyTypes);\n        }\n\n        internal static T CreateInstance<T>(this Type type, params object?[]? args)\n        {\n            if (Activator.CreateInstance(type, args) is T result)\n            {\n                return result;\n            }\n            else\n            {\n                throw new Exception($\"Type {type.FullName} is not of type {typeof(T).FullName}\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyQueryTranslationPreprocessor.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq.Expressions;\nusing Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.Internal;\n\nnamespace Clave.Expressionify\n{\n    public class ExpressionifyQueryTranslationPreprocessor : QueryTranslationPreprocessor\n    {\n        private readonly QueryTranslationPreprocessor _innerPreprocessor;\n\n        public ExpressionifyQueryTranslationPreprocessor(\n            QueryTranslationPreprocessor innerPreprocessor,\n            QueryTranslationPreprocessorDependencies dependencies,\n            QueryCompilationContext compilationContext)\n            : base(dependencies, compilationContext)\n        {\n            _innerPreprocessor = innerPreprocessor;\n        }\n\n        public override Expression Process(Expression query)\n        {\n            var visitor = new ExpressionifyVisitor();\n            query = visitor.Visit(query);\n\n            if (visitor.HasReplacedCalls)\n               query = EvaluateExpression(query);\n\n            return _innerPreprocessor.Process(query);\n        }\n\n        [System.Diagnostics.CodeAnalysis.SuppressMessage(\"Usage\", \"EF1001:Internal EF Core API usage.\", Justification = \"<Pending>\")]\n        private Expression EvaluateExpression(Expression query)\n        {\n            // 1) Ensure that no new parameters are introduced when creating the query\n            // 2) This expression visitor also makes slight optimizations, like replacing evaluatable expressions.\n\n            // With EF10, ParameterExtractingExpressionVisitor was removed and replaced by ExpressionTreeFuncletizer\n            // ExpressionTreeFuncletizer now uses Dictionary<string, object?> instead of IParameterValues\n            var funcletizer = new ExpressionTreeFuncletizer(\n                QueryCompilationContext.Model,\n                Dependencies.EvaluatableExpressionFilter,\n                QueryCompilationContext.ContextType,\n                generateContextAccessors: false,\n                QueryCompilationContext.Logger);\n\n            var throwOnAccess = new ThrowOnParameterAccess();\n            var result = funcletizer.ExtractParameters(query, throwOnAccess, parameterize: true, clearParameterizedValues: true);\n\n            // Check if parameters were added by accessing the base Dictionary\n            // ExpressionTreeFuncletizer bypasses our 'new' method overrides because Dictionary<> is not designed\n            // to be subclassed, so we check the Count via the base class after funcletization completes\n            if (((Dictionary<string, object?>)throwOnAccess).Count > 0)\n            {\n                throw new InvalidOperationException(\n                    \"Adding parameters in a cached query context is not allowed. \" +\n                    $\"Explicitly call .{nameof(ExpressionifyExtension.Expressionify)}() on the query or use {nameof(ExpressionEvaluationMode)}.{nameof(ExpressionEvaluationMode.FullCompatibilityButSlow)}.\");\n            }\n\n            return result;\n        }\n\n        [System.Diagnostics.CodeAnalysis.SuppressMessage(\"Usage\", \"EF1001:Internal EF Core API usage.\", Justification = \"<Pending>\")]\n        private class ThrowOnParameterAccess : Dictionary<string, object?>\n        {\n            // This class exists primarily for documentation purposes - to make it clear that parameter\n            // access should throw an exception in cached mode. However, ExpressionTreeFuncletizer bypasses\n            // these 'new' method overrides by calling base Dictionary<> methods directly.\n            // The actual check happens in EvaluateExpression() after funcletization completes.\n\n            private static InvalidOperationException CreateException()\n                => new InvalidOperationException(\n                    \"Adding parameters in a cached query context is not allowed. \" +\n                    $\"Explicitly call .{nameof(ExpressionifyExtension.Expressionify)}() on the query or use {nameof(ExpressionEvaluationMode)}.{nameof(ExpressionEvaluationMode.FullCompatibilityButSlow)}.\");\n\n            public new object? this[string key]\n            {\n                get => throw CreateException();\n                set => throw CreateException();\n            }\n\n            public new void Add(string key, object? value)\n                => throw CreateException();\n\n            public new bool TryAdd(string key, object? value)\n                => throw CreateException();\n\n            public new bool TryGetValue(string key, out object? value)\n                => throw CreateException();\n\n            public new bool ContainsKey(string key)\n                => throw CreateException();\n\n            public new int Count\n                => throw CreateException();\n        }\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyQueryTranslationPreprocessorFactory.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Query;\n\nnamespace Clave.Expressionify;\n\npublic class ExpressionifyQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory\n{\n    private readonly IQueryTranslationPreprocessorFactory _innerFactory;\n    private readonly QueryTranslationPreprocessorDependencies _preprocessorDependencies;\n\n    public ExpressionifyQueryTranslationPreprocessorFactory(IQueryTranslationPreprocessorFactory innerFactory, QueryTranslationPreprocessorDependencies preprocessorDependencies)\n    {\n        _innerFactory = innerFactory;\n        _preprocessorDependencies = preprocessorDependencies;\n    }\n\n    public QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)\n    {\n        var preprocessor = _innerFactory.Create(queryCompilationContext);\n        return new ExpressionifyQueryTranslationPreprocessor(preprocessor, _preprocessorDependencies, queryCompilationContext);\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify/ExpressionifyVisitor.cs",
    "content": "using System;\nusing System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\n\nnamespace Clave.Expressionify\n{\n    public class ExpressionifyVisitor : ExpressionVisitor\n    {\n        private static readonly IDictionary<MethodInfo, LambdaExpression?> MethodToExpressionMap = new ConcurrentDictionary<MethodInfo, LambdaExpression?>();\n\n        private readonly Dictionary<ParameterExpression, Expression> _replacements = new Dictionary<ParameterExpression, Expression>();\n\n        internal bool HasReplacedCalls { get; private set; }\n\n        protected override Expression VisitMethodCall(MethodCallExpression node)\n        {\n            if (GetMethodExpression(node.Method) is LambdaExpression expression)\n            {\n                HasReplacedCalls = true;\n                RegisterReplacementParameters(node.Arguments, expression);\n                var result = Visit(expression.Body);\n                UnregisterReplacementParameters(expression);\n                return result;\n            }\n\n            return base.VisitMethodCall(node);\n        }\n\n        private static object? GetMethodExpression(MethodInfo method)\n        {\n            if (MethodToExpressionMap.TryGetValue(method, out var result))\n            {\n                return result;\n            }\n\n            if (!method.IsStatic)\n            {\n                return MethodToExpressionMap[method] = null;\n            }\n\n            var shouldUseExpression = method.GetCustomAttributes(typeof(ExpressionifyAttribute), false).Any();\n            if (!shouldUseExpression)\n            {\n                return MethodToExpressionMap[method] = null;\n            }\n\n            var declaringType = method.DeclaringType!;\n\n            if (declaringType.IsGenericType)\n            {\n                declaringType = declaringType\n                    .GetGenericTypeDefinition()\n                    .MakeGenericType(declaringType.GenericTypeArguments);\n            }\n\n            var methods = declaringType.GetRuntimeMethods();\n\n            var expression = methods\n                ?.Where(m => m.Name.StartsWith($\"{method.Name}_Expressionify_\"))\n                .Select(m => m.IsGenericMethod ? m.MakeGenericMethod(method.GetGenericArguments()) : m)\n                .FirstOrDefault(m => m.MatchesTypeOf(method))\n                ?.Invoke(null, Array.Empty<object>());\n\n            if (expression is LambdaExpression lambdaExpression)\n            {\n                return MethodToExpressionMap[method] = lambdaExpression;\n            }\n\n            throw new Exception($\"Code generation seems to have failed, could not find expresion for method {GetFullName(method.DeclaringType)}.{method.Name}()\");\n        }\n\n        private static string GetFullName(Type? type)\n        {\n            if(type?.DeclaringType is Type parent)\n            {\n                return GetFullName(parent) + \".\" + type.Name;\n            }\n\n            return type?.Name ?? \"???\";\n        }\n\n        protected override Expression VisitParameter(ParameterExpression node)\n        {\n            return _replacements.TryGetValue(node, out var replacement)\n                ? Visit(replacement) :\n                base.VisitParameter(node);\n        }\n\n        private void RegisterReplacementParameters(IReadOnlyCollection<Expression> parameterValues, LambdaExpression expressionToVisit)\n        {\n            if (parameterValues.Count != expressionToVisit.Parameters.Count)\n                throw new ArgumentException($\"The parameter values count ({parameterValues.Count}) does not match the expression parameter count ({expressionToVisit.Parameters.Count})\");\n\n            foreach (var (p, v) in expressionToVisit.Parameters.Zip(parameterValues, ValueTuple.Create))\n            {\n                _replacements.Add(p, v);\n            }\n        }\n\n        private void UnregisterReplacementParameters(LambdaExpression expressionToVisit)\n        {\n            foreach (var p in expressionToVisit.Parameters)\n            {\n                _replacements.Remove(p);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/AnalyzerReleases.Shipped.md",
    "content": "﻿; Shipped analyzer releases\n; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md\n\n## Release 6.0\n\n### New Rules\n\nRule ID | Category | Severity | Notes\n--------|----------|----------|-------\nEXPR001 | Syntax | Error | ExpressionifyAnalyzer\nEXPR002 | Syntax | Error | ExpressionifyAnalyzer\nEXPR003 | Syntax | Error | ExpressionifyAnalyzer\nEXPR004 | Syntax | Error | ExpressionifyAnalyzer\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/AnalyzerReleases.Unshipped.md",
    "content": "﻿; Unshipped analyzer release\n; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/Clave.Expressionify.Generator.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>netstandard2.0</TargetFramework>\n        <DebugType>embedded</DebugType>\n        <LangVersion>10.0</LangVersion>\n        <Nullable>enable</Nullable>\n    </PropertyGroup>\n\n    <PropertyGroup>\n        <GeneratePackageOnBuild>false</GeneratePackageOnBuild>\n        <!-- Generates a package at build -->\n        <IncludeBuildOutput>false</IncludeBuildOutput>\n        <!-- Do not include the generator as a lib dependency -->\n        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>\n        <!-- Mark as development dependency and include PrivateAssets/IncludeAssets when installing package -->\n        <DevelopmentDependency>true</DevelopmentDependency>\n    </PropertyGroup>\n\n    <PropertyGroup>\n        <Title>Clave.Expressionify.Generator</Title>\n        <PackageIconUrl>https://raw.githubusercontent.com/ClaveConsulting/logo/master/png/logo_noText.png</PackageIconUrl>\n        <RepositoryUrl>https://github.com/ClaveConsulting/Expressionify</RepositoryUrl>\n        <PackageProjectUrl>https://github.com/ClaveConsulting/Expressionify</PackageProjectUrl>\n        <Authors>Clave Consulting</Authors>\n        <Description>Use extension methods in Entity Framework Core queries</Description>\n        <PackageLicenseExpression>MIT</PackageLicenseExpression>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <!-- Package the generator in the analyzer directory of the nuget package -->\n        <None Include=\"$(OutputPath)\\$(AssemblyName).dll\" Pack=\"true\" PackagePath=\"analyzers/dotnet/cs\" Visible=\"false\" />\n        <None Include=\"_._\" Pack=\"true\" PackagePath=\"lib/netstandard2.0\" Visible=\"false\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Microsoft.CodeAnalysis.CSharp\" Version=\"4.1.0\" PrivateAssets=\"all\" />\n        <PackageReference Include=\"Microsoft.CodeAnalysis.Analyzers\" Version=\"3.3.3\" PrivateAssets=\"all\" />\n        <PackageReference Include=\"Microsoft.CodeAnalysis.Workspaces.Common\" Version=\"4.1.0\" PrivateAssets=\"all\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/ExpressionifyAnalyzer.cs",
    "content": "﻿using System.Collections.Immutable;\nusing Clave.Expressionify.Generator.Internals;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Microsoft.CodeAnalysis.Diagnostics;\n\nnamespace Clave.Expressionify.Generator\n{\n    [DiagnosticAnalyzer(LanguageNames.CSharp)]\n    public class ExpressionifyAnalyzer : DiagnosticAnalyzer\n    {\n        public const string StaticId = \"EXPR001\";\n        public const string ExpressionBodyId = \"EXPR002\";\n        public const string PartialClassId = \"EXPR003\";\n\n        public static readonly DiagnosticDescriptor StaticRule = new DiagnosticDescriptor(\n            id: StaticId,\n            title: \"Method must be static\",\n            messageFormat: \"Method {0} marked with [Expressionify] must be static\",\n            category: \"Syntax\",\n            defaultSeverity: DiagnosticSeverity.Error,\n            isEnabledByDefault: true);\n\n        public static readonly DiagnosticDescriptor ExpressionBodyRule = new DiagnosticDescriptor(\n            id: ExpressionBodyId,\n            title: \"Method must have expression body\",\n            messageFormat: \"Method {0} marked with [Expressionify] must have expression body\",\n            category: \"Syntax\",\n            defaultSeverity: DiagnosticSeverity.Error,\n            isEnabledByDefault: true);\n\n        public static readonly DiagnosticDescriptor PartialClassRule = new DiagnosticDescriptor(\n            id: PartialClassId,\n            title: \"Class must be partial\",\n            messageFormat: \"Class containing a method marked with [Expressionify] must be partial\",\n            category: \"Syntax\",\n            defaultSeverity: DiagnosticSeverity.Error,\n            isEnabledByDefault: true);\n\n        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(\n            StaticRule,\n            ExpressionBodyRule,\n            PartialClassRule);\n\n        public override void Initialize(AnalysisContext context)\n        {\n            context.EnableConcurrentExecution();\n            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);\n            context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.MethodDeclaration);\n        }\n\n        private static void Analyze(SyntaxNodeAnalysisContext context)\n        {\n            if (!(context.Node is MethodDeclarationSyntax methodDeclaration)) return;\n\n            if (!methodDeclaration.HasExpressionifyAttribute()) return;\n\n            if (!methodDeclaration.IsStatic())\n            {\n                context.ReportDiagnostic(Diagnostic.Create(\n                    StaticRule,\n                    methodDeclaration.GetLocation(),\n                    methodDeclaration.Identifier.ToString()));\n            }\n\n            if (!methodDeclaration.HasExpressionBody())\n            {\n                context.ReportDiagnostic(Diagnostic.Create(\n                    ExpressionBodyRule,\n                    methodDeclaration.GetLocation(),\n                    methodDeclaration.Identifier.ToString()));\n            }\n\n            if (methodDeclaration.FindAncestorMissingPartialKeyword() is SyntaxNode typeNode)\n            {\n                context.ReportDiagnostic(Diagnostic.Create(\n                    PartialClassRule,\n                    typeNode.GetLocation()));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/ExpressionifyCodeFixProvider.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Composition;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CodeActions;\nusing Microsoft.CodeAnalysis.CodeFixes;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Document = Microsoft.CodeAnalysis.Document;\nusing static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;\n\nnamespace Clave.Expressionify.Generator\n{\n\n    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ExpressionifyCodeFixProvider)), Shared]\n    public class ExpressionifyCodeFixProvider : CodeFixProvider\n    {\n        public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(\n            ExpressionifyAnalyzer.StaticId,\n            ExpressionifyAnalyzer.PartialClassId);\n\n        public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;\n\n        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)\n        {\n            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);\n\n            var diagnostic = context.Diagnostics.First();\n            var diagnosticSpan = diagnostic.Location;\n\n            // Find the type declaration identified by the diagnostic.\n            var syntaxNode = root!.FindNode(diagnostic.Location.SourceSpan);\n\n            if (diagnostic.Id == ExpressionifyAnalyzer.StaticId)\n                context.RegisterCodeFix(\n                    CodeAction.Create(\n                        title: \"Add static keyword\",\n                        createChangedDocument: c =>\n                            FixMissingStatic(context.Document, root, (syntaxNode as MethodDeclarationSyntax)!),\n                        equivalenceKey: ExpressionifyAnalyzer.StaticId),\n                    diagnostic);\n\n            if (diagnostic.Id == ExpressionifyAnalyzer.PartialClassId)\n                context.RegisterCodeFix(\n                    CodeAction.Create(\n                        title: \"Add partial keyword\",\n                        createChangedDocument: c =>\n                            FixMissingPartial(context.Document, root, (syntaxNode as ClassDeclarationSyntax)!),\n                        equivalenceKey: ExpressionifyAnalyzer.PartialClassId),\n                    diagnostic);\n        }\n\n        private static Task<Document> FixMissingStatic(Document contextDocument, SyntaxNode root, MethodDeclarationSyntax method)\n        {\n            return Task.FromResult(contextDocument.WithSyntaxRoot(root.ReplaceNode(method, method.AddModifiers(Token(SyntaxKind.StaticKeyword)))));\n        }\n\n        private static Task<Document> FixMissingPartial(Document contextDocument, SyntaxNode root, ClassDeclarationSyntax @class)\n        {\n            return Task.FromResult(contextDocument.WithSyntaxRoot(root.ReplaceNode(@class, @class.AddModifiers(Token(SyntaxKind.PartialKeyword)))));\n        }\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify.Generator/ExpressionifySourceGenerator.cs",
    "content": "using System;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing Microsoft.CodeAnalysis.Text;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Clave.Expressionify.Generator.Internals;\nusing Microsoft.CodeAnalysis.CSharp;\nusing System.IO;\n\nnamespace Clave.Expressionify.Generator\n{\n    [Generator]\n    public class ExpressionifySourceGenerator : ISourceGenerator\n    {\n        private static readonly string NullableDirective = \"#nullable enable\\r\\n\\r\\n\";\n\n        public void Initialize(GeneratorInitializationContext context)\n        {\n            context.RegisterForSyntaxNotifications(() => new ExpressionifySyntaxReceiver());\n        }\n\n        public void Execute(GeneratorExecutionContext context)\n        {\n            if (context.SyntaxReceiver is ExpressionifySyntaxReceiver syntaxReceiver)\n                Execute(context, syntaxReceiver.Methods);\n        }\n\n        private record Expressioned(\n            MethodDeclarationSyntax Original,\n            MethodDeclarationSyntax Replaced,\n            (TypeDeclarationSyntax? Head, IEnumerator<TypeDeclarationSyntax> Tail)? Path)\n        {\n            public static Expressioned Create(MethodDeclarationSyntax m, Compilation compilation) => new(\n                m,\n                m.ToExpressionMethod(compilation),\n                m.Ancestors().OfType<TypeDeclarationSyntax>().Reverse().HeadAndTail());\n        }\n\n        private static void Execute(GeneratorExecutionContext context, IEnumerable<MethodDeclarationSyntax> methods)\n        {\n            try\n            {\n                var i = 0;\n\n                static MemberDeclarationSyntax[] Group(IEnumerable<Expressioned> methods) =>\n                    methods\n                        .GroupBy(x => x.Path?.Head, x => x with { Path = x.Path?.Tail.HeadAndTail() })\n                        .Select(g => g.Key!.WithOnlyTheseMembers(g\n                            .Where(x => x.Path is null)\n                            .Select(x => x.Replaced)\n                            .GroupBy(p => p.Identifier.Text)\n                            .SelectMany(x => x.Select((y, i) => y.GeneratedName(i)))\n                            .Concat(Group(g.Where(x => x.Path is not null)))))\n                        .ToArray();\n\n                var replacedTypes = methods\n                    .Select(x => Expressioned.Create(x, context.Compilation))\n                    .GroupBy(m => m.Original.SyntaxTree.GetRoot(), (root, x) => (root.SyntaxTree.FilePath, root.WithOnlyTheseTypes(Group(x))));\n\n                foreach (var (path, source) in replacedTypes)\n                {\n                    var sourceCode = NullableDirective + source.ToFullString();\n\n                    context.AddSource(\n                        Path.GetFileNameWithoutExtension(path) + $\"_expressionify_{i++}.g.cs\",\n                        SourceText.From(sourceCode, Encoding.UTF8));\n                }\n            }\n            catch (Exception e)\n            {\n                context.ReportDiagnostic(Diagnostic.Create(\n                    new DiagnosticDescriptor(\"EXPR001\", \"Error generating expression\", $\"{e.Message}\\n{e.StackTrace}\", \"error\",\n                        DiagnosticSeverity.Error, true, e.StackTrace), Location.None));\n            }\n        }\n\n        private class ExpressionifySyntaxReceiver : ISyntaxReceiver\n        {\n            public readonly HashSet<TypeDeclarationSyntax> Types = new HashSet<TypeDeclarationSyntax>();\n\n            public readonly HashSet<MethodDeclarationSyntax> Methods = new HashSet<MethodDeclarationSyntax>();\n\n            public void OnVisitSyntaxNode(SyntaxNode syntaxNode)\n            {\n                if (syntaxNode is MethodDeclarationSyntax methodDeclaration)\n                {\n                    if (!methodDeclaration.HasExpressionBody()) return;\n                    if (!methodDeclaration.HasExpressionifyAttribute()) return;\n                    if (!methodDeclaration.IsStatic()) return;\n\n                    if (methodDeclaration.Ancestors().OfType<TypeDeclarationSyntax>().LastOrDefault() is { } type)\n                    {\n                        if (!type.Modifiers.Includes(SyntaxKind.PartialKeyword)) return;\n\n                        Methods.Add(methodDeclaration);\n\n                        Types.Add(type);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/Extensions.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace Clave.Expressionify.Generator\n{\n    internal static class Extensions\n    {\n        public static (T Head, IEnumerator<T> Tail)? HeadAndTail<T>(this IEnumerable<T> enumerable) => enumerable.GetEnumerator().HeadAndTail();\n\n        public static (T Head, IEnumerator<T> Tail)? HeadAndTail<T>(this IEnumerator<T> enumerator)\n        {\n            if (!enumerator.MoveNext()) return null;\n            return (enumerator.Current, enumerator);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/Internals/Checks.cs",
    "content": "﻿using System.Linq;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\n\nnamespace Clave.Expressionify.Generator.Internals\n{\n    public static class Checks\n    {\n        public static bool HasExpressionifyAttribute(this MethodDeclarationSyntax m) =>\n            m.AttributeLists.SelectMany(l => l.Attributes).Any(a => a.Name.ToString() == \"Expressionify\");\n\n        public static bool IsStatic(this MethodDeclarationSyntax method) =>\n            method.Modifiers.Includes(SyntaxKind.StaticKeyword);\n\n        public static bool Includes(this SyntaxTokenList modifiers, SyntaxKind modifier) =>\n            modifiers.Any(m => m.IsKind(modifier));\n\n        public static bool HasExpressionBody(this MethodDeclarationSyntax method) =>\n            method.ExpressionBody is not null;\n\n        public static TypeDeclarationSyntax? FindAncestorMissingPartialKeyword(this MethodDeclarationSyntax method) =>\n            method.Ancestors().OfType<TypeDeclarationSyntax>().FirstOrDefault(t => !t.Modifiers.Includes(SyntaxKind.PartialKeyword));\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify.Generator/Internals/ClassGenerator.cs",
    "content": "using System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;\n\nnamespace Clave.Expressionify.Generator.Internals\n{\n    public static class ClassGenerator\n    {\n        public static TypeDeclarationSyntax WithOnlyTheseMembers(this TypeDeclarationSyntax type, IEnumerable<MemberDeclarationSyntax> members)\n            => TypeDeclaration(type.Kind(), type.Identifier)\n                .WithModifiers(type.Modifiers)\n                .WithTypeParameterList(type.TypeParameterList)\n                .AddMembers(members.ToArray());\n\n        public static SyntaxNode WithOnlyTheseTypes(this SyntaxNode root, IEnumerable<MemberDeclarationSyntax> members)\n        {\n            var namespaceName = root.DescendantNodes()\n                .OfType<NamespaceDeclarationSyntax>()\n                .FirstOrDefault()?.Name\n\n                ?? root.DescendantNodes()\n                .OfType<FileScopedNamespaceDeclarationSyntax>()\n                .FirstOrDefault().Name;\n\n            var usings = root.DescendantNodes()\n                .OfType<UsingDirectiveSyntax>()\n                .ToArray();\n\n            return NamespaceDeclaration(namespaceName)\n                .AddUsings(usings)\n                .AddMembers(members.ToArray())\n                .NormalizeWhitespace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/Internals/ExpressionRewriter.cs",
    "content": "namespace Clave.Expressionify.Generator.Internals;\n\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing System.Linq;\n\ninternal sealed class ExpressionRewriter : CSharpSyntaxRewriter\n{\n    private readonly Compilation _compilation;\n    private readonly SyntaxNode _root;\n\n    public ExpressionRewriter(Compilation compilation, SyntaxNode root)\n    {\n        _compilation = compilation;\n        _root = root;\n    }\n\n    public ExpressionSyntax VisitExpression(ExpressionSyntax expression) =>\n        (ExpressionSyntax)Visit(expression);\n\n    public override SyntaxNode Visit(SyntaxNode node)\n    {\n        var res = base.Visit(node);\n\n        if (SyntaxFactory.AreEquivalent(node, res))\n            return res;\n\n        return _root\n            .ReplaceNode(node, res)\n            .DescendantNodes()\n            .Where(x => x.FullSpan.Start == node.FullSpan.Start)\n            .Where(x => x.FullSpan.Length == res.FullSpan.Length)\n            .First(x => x.GetType() == res.GetType());\n    }\n\n    public override SyntaxNode? VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node)\n    {\n        var expression = Visit(node.Expression);\n        var expressionType = _compilation\n            .ReplaceSyntaxTree(_root.SyntaxTree, expression.SyntaxTree)\n            .GetSemanticModel(expression.SyntaxTree)\n            .GetTypeInfo(expression)\n            .Type;\n\n        var forgiver = expressionType is INamedTypeSymbol\n        {\n            IsValueType: true,\n            OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,\n        }\n            ? \"!.Value\"\n            : \"!\";\n\n        return SyntaxFactory.ParseExpression($\"{expression}{forgiver}{Visit(node.WhenNotNull)}\");\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/Internals/PropertyGenerator.cs",
    "content": "using System.Linq;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Syntax;\nusing static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;\n\nnamespace Clave.Expressionify.Generator.Internals\n{\n    public static class PropertyGenerator\n    {\n        public static MethodDeclarationSyntax GeneratedName(this MethodDeclarationSyntax p, int i)\n            => p.WithIdentifier(Identifier($\"{p.Identifier.Text}_Expressionify_{i}\"));\n\n        public static MethodDeclarationSyntax ToExpressionMethod(\n            this MethodDeclarationSyntax method,\n            Compilation compilation)\n            => MethodDeclaration(GetExpressionType(method), method.Identifier)\n                .WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword)))\n                .WithTypeParameterList(method.TypeParameterList)\n                .WithConstraintClauses(method.ConstraintClauses)\n                .WithExpressionBody(ArrowExpressionClause(GetBody(method, compilation)))\n                .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))\n                .NormalizeWhitespace();\n\n        private static ParenthesizedLambdaExpressionSyntax GetBody(\n            BaseMethodDeclarationSyntax method,\n            Compilation compilation)\n        {\n            var expressionRewriter = new ExpressionRewriter(compilation, method.SyntaxTree.GetRoot());\n            \n            return ParenthesizedLambdaExpression(\n                ParameterList(SeparatedList(method.ParameterList.Parameters.Select(p => p.WithModifiers(TokenList())))),\n                expressionRewriter.VisitExpression(method.ExpressionBody!.Expression)\n            );\n        }\n\n        private static QualifiedNameSyntax GetExpressionType(MethodDeclarationSyntax method) =>\n            Expression(Func(\n                SeparatedList(method.ParameterList.Parameters\n                    .Select(p => p.Type!)\n                    .ToArray()\n                    .Append(method.ReturnType))\n            ));\n\n        private static QualifiedNameSyntax Func(SeparatedSyntaxList<TypeSyntax> types) =>\n            QualifiedName(\n                IdentifierName(\"System\"),\n                GenericName(Identifier(\"Func\"))\n                    .WithTypeArgumentList(TypeArgumentList(types))\n            );\n\n        private static QualifiedNameSyntax Expression(TypeSyntax genericPart) =>\n            QualifiedName(\n                QualifiedName(QualifiedName(IdentifierName(\"System\"), IdentifierName(\"Linq\")), IdentifierName(\"Expressions\")),\n                GenericName(Identifier(\"Expression\"))\n                    .WithTypeArgumentList(TypeArgumentList(SingletonSeparatedList(genericPart)))\n            );\n    }\n}\n"
  },
  {
    "path": "src/Clave.Expressionify.Generator/IsExternalInit.cs",
    "content": "﻿namespace System.Runtime.CompilerServices\n{\n    using System.ComponentModel;\n    /// <summary>\n    /// Reserved to be used by the compiler for tracking metadata.\n    /// This class should not be used by developers in source code.\n    /// </summary>\n    [EditorBrowsable(EditorBrowsableState.Never)]\n    internal static class IsExternalInit\n    {\n    }\n}"
  },
  {
    "path": "src/Clave.Expressionify.Generator/_._",
    "content": ""
  },
  {
    "path": "tests/Clave.Expressionify.Generator.Tests/Clave.Expressionify.Generator.Tests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>net10.0</TargetFramework>\n        <LangVersion>latest</LangVersion>\n        <Nullable>enable</Nullable>\n        <IsPackable>false</IsPackable>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"NUnit\" Version=\"3.13.3\" />\n        <PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.2.1\" />\n        <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.1.0\" />\n        <PackageReference Include=\"Shouldly\" Version=\"4.0.3\" />\n\t\t<PackageReference Include=\"Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit\" Version=\"1.1.1\" />\n\t\t<PackageReference Include=\"Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit\" Version=\"1.1.1\" />\n\t\t<PackageReference Include=\"Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit\" Version=\"1.1.1\" />\n\t\t<PackageReference Include=\"Microsoft.CodeAnalysis.Analyzers\" Version=\"3.3.3\">\n\t\t\t<PrivateAssets>all</PrivateAssets>\n\t\t\t<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n\t\t</PackageReference>\n\t\t<PackageReference Include=\"Microsoft.CodeAnalysis.CSharp.Workspaces\" Version=\"4.1.0\" />\n\t</ItemGroup>\n\n    <ItemGroup>\n        <ProjectReference Include=\"../../src/Clave.Expressionify.Generator/Clave.Expressionify.Generator.csproj\" />\n    </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "tests/Clave.Expressionify.Generator.Tests/CodeFixTests.cs",
    "content": "using System.Threading.Tasks;\nusing NUnit.Framework;\nusing Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier<Clave.Expressionify.Generator.ExpressionifyAnalyzer, Clave.Expressionify.Generator.ExpressionifyCodeFixProvider>;\n\nnamespace Clave.Expressionify.Generator.Tests\n{\n    public class CodeFixTests\n    {\n        [Test]\n        public async Task TestNothing()\n        {\n            var test = @\"\";\n\n            await Verify.VerifyAnalyzerAsync(test);\n        }\n\n        [Test]\n        public async Task TestOkMethod()\n        {\n            var test = @\"\n                namespace ConsoleApplication1\n                {\n                    public partial class Extensions\n                    {\n                        [Expressionify]\n                        public static int Foo(int x) => 8;\n                    }\n                    \n                    [System.AttributeUsage(System.AttributeTargets.Method)]\n                    public class ExpressionifyAttribute : System.Attribute {}\n                }\";\n\n            await Verify.VerifyAnalyzerAsync(test);\n        }\n\n        [Test]\n        public async Task TestWithoutNamespace()\n        {\n\n            var test = @\"\n                public partial class Extensions\n                {\n                    [Expressionify]\n                    public static int Foo(int x) => 8;\n                }\n                    \n                [System.AttributeUsage(System.AttributeTargets.Method)]\n                public class ExpressionifyAttribute : System.Attribute {}\n                \";\n\n            await Verify.VerifyAnalyzerAsync(test);\n        }\n\n        [Test]\n        public async Task TestWithFileScopedNamespace()\n        {\n            var test = @\"\n                namespace ConsoleApplication1;\n\n                public partial class Extensions\n                {\n                    [Expressionify]\n                    public static int Foo(int x) => 8;\n                }\n\n                [System.AttributeUsage(System.AttributeTargets.Method)]\n                public class ExpressionifyAttribute : System.Attribute {}\n                \";\n\n            await Verify.VerifyAnalyzerAsync(test);\n        }\n\n        [Test]\n        public async Task TestMissingStatic()\n        {\n            var test = @\"\n                namespace ConsoleApplication1\n                {\n                    public partial class Extensions\n                    {\n                        [Expressionify]\n                        public int Foo(int x) => 8;\n                    }\n                    \n                    [System.AttributeUsage(System.AttributeTargets.Method)]\n                    public class ExpressionifyAttribute : System.Attribute {}\n                }\";\n\n            var expected = Verify.Diagnostic(ExpressionifyAnalyzer.StaticRule)\n                .WithSpan(\"/0/Test0.cs\", 6, 25, 7, 52)\n                .WithArguments(\"Foo\");\n\n            var fixtest = @\"\n                namespace ConsoleApplication1\n                {\n                    public partial class Extensions\n                    {\n                        [Expressionify]\n                        public static int Foo(int x) => 8;\n                    }\n                    \n                    [System.AttributeUsage(System.AttributeTargets.Method)]\n                    public class ExpressionifyAttribute : System.Attribute {}\n                }\";\n\n            await Verify.VerifyCodeFixAsync(test, expected, fixtest);\n        }\n\n        [Test]\n        public async Task TestNotExpressionBody()\n        {\n            var test = @\"\n                namespace ConsoleApplication1\n                {\n                    public static partial class Extensions\n                    {\n                        [Expressionify]\n                        public static int Foo(int x) { return 8; }\n                    }\n                    \n                    [System.AttributeUsage(System.AttributeTargets.Method)]\n                    public class ExpressionifyAttribute : System.Attribute {}\n                }\";\n\n            var expected = Verify.Diagnostic(ExpressionifyAnalyzer.ExpressionBodyRule)\n                .WithSpan(\"/0/Test0.cs\", 6, 25, 7, 67)\n                .WithArguments(\"Foo\");\n\n            await Verify.VerifyCodeFixAsync(test, expected, test);\n        }\n\n        [Test]\n        public async Task TestNotInPartialClass()\n        {\n            var test = @\"\n                namespace ConsoleApplication1\n                {\n                    public static class Extensions\n                    {\n                        [Expressionify]\n                        public static int Foo(int x) => 8;\n                    }\n                    \n                    [System.AttributeUsage(System.AttributeTargets.Method)]\n                    public class ExpressionifyAttribute : System.Attribute {}\n                }\";\n\n            var expected = Verify.Diagnostic(ExpressionifyAnalyzer.PartialClassRule)\n                .WithSpan(\"/0/Test0.cs\", 4, 21, 8, 22);\n\n            await Verify.VerifyAnalyzerAsync(test, new [] { expected });\n\n            var fixtest = @\"\n                namespace ConsoleApplication1\n                {\n                    public static partial class Extensions\n                    {\n                        [Expressionify]\n                        public static int Foo(int x) => 8;\n                    }\n                    \n                    [System.AttributeUsage(System.AttributeTargets.Method)]\n                    public class ExpressionifyAttribute : System.Attribute {}\n                }\";\n\n            await Verify.VerifyCodeFixAsync(test, expected, fixtest);\n        }\n    }\n}"
  },
  {
    "path": "tests/Clave.Expressionify.Generator.Tests/CodeGeneratorTests.cs",
    "content": "﻿using System;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.CodeAnalysis.Text;\nusing NUnit.Framework;\nusing Verify = Clave.Expressionify.Generator.Tests.Verifiers.CSharpSourceGeneratorVerifier<Clave.Expressionify.Generator.ExpressionifySourceGenerator>;\n\nnamespace Clave.Expressionify.Generator.Tests\n{\n    [TestFixture]\n    public class CodeGeneratorTests\n    {\n        private const string AttributeCode = @\"\n        namespace ConsoleApplication1\n        {\n            using System;\n\n            [AttributeUsage(AttributeTargets.Method)]\n            public class ExpressionifyAttribute : Attribute\n            {\n            }\n        }\";\n\n        // Normal scenario\n        [TestCase(@\"namespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static int Foo(int x) => 8;\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<int, int>> Foo_Expressionify_0() => (int x) => 8;\n    }\n}\", TestName = \"Normal scenario\")]\n\n        // File scoped namespace\n        [TestCase(@\"namespace ConsoleApplication1;\npublic partial class Extensions\n{\n    [Expressionify]\n    public static int Foo(int x) => 8;\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<int, int>> Foo_Expressionify_0() => (int x) => 8;\n    }\n}\", TestName = \"File scoped namespace\")]\n\n        // Nested class\n        [TestCase(@\"namespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static int Foo(int x) => 8;\n\n        public partial class Nested\n        {\n            [Expressionify]\n            public static int Foo(int x) => 8;\n        }\n    }\n}\",\n    @\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<int, int>> Foo_Expressionify_0() => (int x) => 8;\n        public partial class Nested\n        {\n            private static System.Linq.Expressions.Expression<System.Func<int, int>> Foo_Expressionify_0() => (int x) => 8;\n        }\n    }\n}\", TestName = \"Nested class\")]\n\n        // Nullable\n        [TestCase(@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static string? Foo(int x) => x < 10 ? null : \"\"bar\"\";\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<int, string?>> Foo_Expressionify_0() => (int x) => x < 10 ? null : \"\"bar\"\";\n    }\n}\", TestName = \"Nullable\")]\n\n        // Nullable enabled but not used\n        [TestCase(@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static string Foo(int x) => \"\"bar\"\";\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<int, string>> Foo_Expressionify_0() => (int x) => \"\"bar\"\";\n    }\n}\", TestName = \"Nullable enabled but not used\")]\n\n        [TestCase(@\"namespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static string Foo<T>(T x) => \"\"bar\"\";\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<T, string>> Foo_Expressionify_0<T>() => (T x) => \"\"bar\"\";\n    }\n}\", TestName = \"Generic extension method\")]\n\n        [TestCase(@\"namespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static string Foo<T>(T x) where T : System.Collections.IEnumerable => \"\"bar\"\";\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<T, string>> Foo_Expressionify_0<T>()\n            where T : System.Collections.IEnumerable => (T x) => \"\"bar\"\";\n    }\n}\", TestName = \"Generic extension method with constraints\")]\n\n        [TestCase(@\"namespace ConsoleApplication1\n{\n    public partial class Extensions<T>\n    {\n        [Expressionify]\n        public static string Foo(T x) => \"\"bar\"\";\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    public partial class Extensions<T>\n    {\n        private static System.Linq.Expressions.Expression<System.Func<T, string>> Foo_Expressionify_0() => (T x) => \"\"bar\"\";\n    }\n}\", TestName = \"Generic type\")]\n\n        // Null propagation\n        [TestCase(@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    using System;\n\n    public partial class Extensions\n    {\n        [Expressionify]\n        public static int? GetYear(DateTime? x) => x?.Year;\n\n        [Expressionify]\n        public static string? GetYearString(DateTime? x) => (x?.AddDays(1).Year)?.ToString();\n\n        [Expressionify]\n        public static byte? First(byte[]? x) => x?[0];\n\n        [Expressionify]\n        public static string? FirstString(byte[]? x) => x?[0].ToString();\n\n        [Expressionify]\n        public static int? FirstYear(DateTime?[]? x) => x?[0]?.Year;\n\n        [Expressionify]\n        public static int? GetYearOfNextDay(DateTime? x) => GetYear(x?.AddDays(1));\n    }\n}\",\n@\"#nullable enable\n\nnamespace ConsoleApplication1\n{\n    using System;\n\n    public partial class Extensions\n    {\n        private static System.Linq.Expressions.Expression<System.Func<DateTime?, int?>> GetYear_Expressionify_0() => (DateTime? x) => x!.Value.Year;\n        private static System.Linq.Expressions.Expression<System.Func<DateTime?, string?>> GetYearString_Expressionify_0() => (DateTime? x) => (x!.Value.AddDays(1).Year)!.ToString();\n        private static System.Linq.Expressions.Expression<System.Func<byte[]?, byte?>> First_Expressionify_0() => (byte[]? x) => x![0];\n        private static System.Linq.Expressions.Expression<System.Func<byte[]?, string?>> FirstString_Expressionify_0() => (byte[]? x) => x![0].ToString();\n        private static System.Linq.Expressions.Expression<System.Func<DateTime? []?, int?>> FirstYear_Expressionify_0() => (DateTime? []? x) => x![0]!.Value.Year;\n        private static System.Linq.Expressions.Expression<System.Func<DateTime?, int?>> GetYearOfNextDay_Expressionify_0() => (DateTime? x) => GetYear(x!.Value.AddDays(1));\n    }\n}\", TestName = \"Null propagation\")]\n        public async Task TestGenerator(string source, string generated)\n        {\n            await VerifyGenerated(source, generated);\n        }\n\n        public async Task VerifyGenerated(string source, string generated)\n        {\n            await new Verify.Test\n            {\n                TestState =\n                {\n                    Sources = { source, AttributeCode },\n                    GeneratedSources =\n                    {\n                        (typeof(ExpressionifySourceGenerator), \"Test0_expressionify_0.g.cs\", SourceText.From(generated.Replace(Environment.NewLine, \"\\r\\n\"), Encoding.UTF8, SourceHashAlgorithm.Sha1)),\n                    }\n                }\n            }.RunAsync();\n        }\n    }\n}"
  },
  {
    "path": "tests/Clave.Expressionify.Generator.Tests/Verifiers/CSharpSourceGeneratorVerifier.cs",
    "content": "﻿using System;\nusing System.Collections.Immutable;\nusing Microsoft.CodeAnalysis;\nusing Microsoft.CodeAnalysis.CSharp;\nusing Microsoft.CodeAnalysis.CSharp.Testing;\nusing Microsoft.CodeAnalysis.Testing.Verifiers;\n\nnamespace Clave.Expressionify.Generator.Tests.Verifiers\n{\n    public static class CSharpSourceGeneratorVerifier<TSourceGenerator>\n        where TSourceGenerator : ISourceGenerator, new()\n    {\n        public class Test : CSharpSourceGeneratorTest<TSourceGenerator, NUnitVerifier>\n        {\n            public Test()\n            {\n            }\n\n            protected override CompilationOptions CreateCompilationOptions()\n            {\n                var compilationOptions = base.CreateCompilationOptions();\n                return compilationOptions.WithSpecificDiagnosticOptions(\n                     compilationOptions.SpecificDiagnosticOptions.SetItems(GetNullableWarningsFromCompiler()));\n            }\n\n            public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.Default;\n\n            private static ImmutableDictionary<string, ReportDiagnostic> GetNullableWarningsFromCompiler()\n            {\n                string[] args = { \"/warnaserror:nullable\" };\n                var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory);\n                var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;\n\n                return nullableWarnings;\n            }\n\n            protected override ParseOptions CreateParseOptions()\n            {\n                return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion);\n            }\n        }\n    }\n}"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Clave.Expressionify.Tests.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\r\n\r\n    <PropertyGroup>\r\n        <TargetFramework>net10.0</TargetFramework>\r\n        <LangVersion>latest</LangVersion>\r\n        <Nullable>enable</Nullable>\r\n        <IsPackable>false</IsPackable>\r\n    </PropertyGroup>\r\n\r\n    <ItemGroup>\r\n        <PackageReference Include=\"Microsoft.EntityFrameworkCore.Sqlite\" Version=\"10.0.0\" />\r\n        <PackageReference Include=\"nunit\" Version=\"3.13.3\" />\r\n        <PackageReference Include=\"NUnit3TestAdapter\" Version=\"4.2.1\" />\r\n        <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.1.0\" />\r\n        <PackageReference Include=\"Shouldly\" Version=\"4.0.3\" />\r\n    </ItemGroup>\r\n\r\n    <ItemGroup>\r\n        <ProjectReference Include=\"../../src/Clave.Expressionify/Clave.Expressionify.csproj\" />\r\n        <ProjectReference Include=\"../../src/Clave.Expressionify.Generator/Clave.Expressionify.Generator.csproj\" OutputItemType=\"Analyzer\" ReferenceOutputAssembly=\"false\" />\r\n    </ItemGroup>\r\n\r\n</Project>\r\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/DbContextExtensions/TestDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\n\nnamespace Clave.Expressionify.Tests.DbContextExtensions\n{\n    public class TestDbContext : DbContext\n    {\n        public TestDbContext(DbContextOptions options) : base(options)\n        { }\n\n        public DbSet<TestEntity> TestEntities { get; set; } = null!;\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/DbContextExtensions/TestEntity.cs",
    "content": "﻿using System;\n\nnamespace Clave.Expressionify.Tests.DbContextExtensions\n{\n    public class TestEntity\n    {\n        public int Id { get; set; }\n        public string Name { get; set; } = \"\";\n        public DateTime Created { get; set; }\n    }\n\n    public class TestAddress\n    {\n        public string? City { get; set; }\n        public string? Street { get; set; }\n    }\n\n    public class TestView\n    {\n        public string? Name { get; set; }\n        public string? Street { get; set; }\n    }\n\n    public static class TestTimeProvider\n    {\n        public static DateTime UtcNow => new(2022, 3, 4, 5, 6, 7);\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/DbContextExtensions/TestEntityExtensions.cs",
    "content": "﻿namespace Clave.Expressionify.Tests.DbContextExtensions\n{\n    public static partial class TestEntityExtensions\n    {\n        [Expressionify]\n        public static string GetName(this TestEntity testEntity, string prefix) => prefix + \" \" + testEntity.Name;\n\n        [Expressionify]\n        public static bool NameEquals(this TestEntity testEntity, string name) => testEntity.Name == name;\n\n        [Expressionify]\n        public static bool IsJohnDoe(this TestEntity testEntity) => testEntity.Name == \"John Doe\";\n\n        [Expressionify]\n        public static bool IsSomething(this TestEntity testEntity) => testEntity.Name == Name;\n\n        [Expressionify]\n        public static bool IsRecent(this TestEntity testEntity) => testEntity.Created > TestTimeProvider.UtcNow.AddDays(-1);\n\n        [Expressionify]\n        public static TestView ToTestView(this TestEntity testEntity, TestAddress? address)\n            => new() { Name = testEntity.Name, Street = address == null ? null : address.Street };\n\n        public static string Name => \"Something\";\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/DbContextExtensions/Tests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.EntityFrameworkCore;\nusing NUnit.Framework;\nusing Shouldly;\n\nnamespace Clave.Expressionify.Tests.DbContextExtensions\n{\n    public class Tests\n    {\n        [Test]\n        public void UseExpressionifyInConfig_ExpandsExpression_CanTranslate()\n        {\n            using var dbContext = new TestDbContext(GetOptions());\n            var query = dbContext.TestEntities.Select(e => e.GetName(\"oh hi\"));\n\n            var sql = query.ToQueryString();\n            sql.ShouldStartWith(\"SELECT 'oh hi ' || \\\"t\\\".\\\"Name\\\"\");\n        }\n\n        [Test]\n        public void UseExpressionifyInQuery_ExpandsExpression_CanTranslate()\n        {\n            using var dbContext = new TestDbContext(GetOptions(useExpressionify: false));\n            var query = dbContext.TestEntities.Expressionify().Select(e => e.GetName(\"oh hi\"));\n\n            var sql = query.ToQueryString();\n            sql.ShouldStartWith(\"SELECT 'oh hi ' || \\\"t\\\".\\\"Name\\\"\");\n        }\n\n        [Test]\n        public void UseExpressionifyInQueryAndConfig_ExpandsExpression_CanTranslate()\n        {\n            using var dbContext = new TestDbContext(GetOptions());\n            var prefix = \"oh hi\";\n            var query = dbContext.TestEntities.Expressionify().Select(e => e.GetName(prefix));\n\n            var sql = query.ToQueryString();\n            sql.ShouldBe($\".param set @p 'oh hi '{Environment.NewLine}{Environment.NewLine}SELECT @p || \\\"t\\\".\\\"Name\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"\");\n        }\n\n        [Test]\n        public void DontUseExpressionify_EfSelectsWholeEntity()\n        {\n            // This is basically a self-test of the test setup. EF should select the whole entity here, instead of the \"optimized\" version where\n            // the concatenation is done in the statement and only the required name is selected\n            using var dbContext = new TestDbContext(GetOptions(useExpressionify: false));\n            var prefix = \"oh hi\";\n            var query = dbContext.TestEntities.Select(e => e.GetName(prefix));\n\n            var sql = query.ToQueryString();\n            sql.ShouldStartWith(\"SELECT \\\"t\\\".\\\"Id\\\", \\\"t\\\".\\\"Created\\\", \\\"t\\\".\\\"Name\\\"\");\n        }\n\n        [Test]\n        public void Expressionify_ShouldHandleWhereWithParameters_AfterExpansion()\n        {\n            using var dbContext = new TestDbContext(GetOptions());\n            var query = dbContext.TestEntities.Expressionify().Where(e => e.IsSomething());\n\n            query.ToQueryString().ShouldBe($\".param set @Name 'Something'{Environment.NewLine}{Environment.NewLine}SELECT \\\"t\\\".\\\"Id\\\", \\\"t\\\".\\\"Created\\\", \\\"t\\\".\\\"Name\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"{Environment.NewLine}WHERE \\\"t\\\".\\\"Name\\\" = @Name\");\n        }\n\n        [TestCase(ExpressionEvaluationMode.FullCompatibilityButSlow)]\n        [TestCase(ExpressionEvaluationMode.LimitedCompatibilityButCached)]\n        public void UseExpressionify_ShouldHandleConstants(ExpressionEvaluationMode mode)\n        {\n            using var dbContext = new TestDbContext(GetOptions(o => o.WithEvaluationMode(mode)));\n            var query = dbContext.TestEntities.Where(e => e.IsJohnDoe());\n\n            query.ToQueryString().ShouldBe($\"SELECT \\\"t\\\".\\\"Id\\\", \\\"t\\\".\\\"Created\\\", \\\"t\\\".\\\"Name\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"{Environment.NewLine}WHERE \\\"t\\\".\\\"Name\\\" = 'John Doe'\");\n        }\n\n        [TestCase(ExpressionEvaluationMode.FullCompatibilityButSlow)]\n        [TestCase(ExpressionEvaluationMode.LimitedCompatibilityButCached)]\n        public void UseExpressionify_ShouldHandleWhereWithParameters(ExpressionEvaluationMode mode)\n        {\n            var name = \"oh hi\";\n            using var dbContext = new TestDbContext(GetOptions(o => o.WithEvaluationMode(mode)));\n            var query = dbContext.TestEntities.Where(e => e.NameEquals(name));\n\n            query.ToQueryString().ShouldBe($\".param set @name 'oh hi'{Environment.NewLine}{Environment.NewLine}SELECT \\\"t\\\".\\\"Id\\\", \\\"t\\\".\\\"Created\\\", \\\"t\\\".\\\"Name\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"{Environment.NewLine}WHERE \\\"t\\\".\\\"Name\\\" = @name\");\n        }\n\n        [Test]\n        public void UseExpressionify_EvaluationModeAlways_ShouldHandleWhereWithNewParameters()\n        {\n            using var dbContext = new TestDbContext(GetOptions(optionsAction: o => o.WithEvaluationMode(ExpressionEvaluationMode.FullCompatibilityButSlow)));\n            var query = dbContext.TestEntities.Where(e => e.IsSomething());\n\n            query.ToQueryString().ShouldBe($\".param set @Name 'Something'{Environment.NewLine}{Environment.NewLine}SELECT \\\"t\\\".\\\"Id\\\", \\\"t\\\".\\\"Created\\\", \\\"t\\\".\\\"Name\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"{Environment.NewLine}WHERE \\\"t\\\".\\\"Name\\\" = @Name\");\n        }\n\n        [Test]\n        public void UseExpressionify_EvaluationModeAlways_ShouldHandleWhereWithExternalServices()\n        {\n            using var dbContext = new TestDbContext(GetOptions(optionsAction: o => o.WithEvaluationMode(ExpressionEvaluationMode.FullCompatibilityButSlow)));\n            var query = dbContext.TestEntities.Where(e => e.IsRecent());\n\n            query.ToQueryString().ShouldBe($\".param set @AddDays '2022-03-03 05:06:07'{Environment.NewLine}{Environment.NewLine}SELECT \\\"t\\\".\\\"Id\\\", \\\"t\\\".\\\"Created\\\", \\\"t\\\".\\\"Name\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"{Environment.NewLine}WHERE \\\"t\\\".\\\"Created\\\" > @AddDays\");\n        }\n\n        [Test]\n        public void UseExpressionify_EvaluationModeCached_CannotHandleNewParameters()\n        {\n            using var dbContext = new TestDbContext(GetOptions(optionsAction: o => o.WithEvaluationMode(ExpressionEvaluationMode.LimitedCompatibilityButCached)));\n            var query = dbContext.TestEntities.Where(e => e.IsSomething());\n\n            var exception = Should.Throw<InvalidOperationException>(() => query.ToQueryString());\n            exception.Message.ShouldBe(\"Adding parameters in a cached query context is not allowed. Explicitly call .Expressionify() on the query or use ExpressionEvaluationMode.FullCompatibilityButSlow.\");\n        }\n\n        [Test]\n        public void UseExpressionify_EvaluationModeCached_CannotHandleParametersFromExternalServices()\n        {\n            using var dbContext = new TestDbContext(GetOptions(optionsAction: o => o.WithEvaluationMode(ExpressionEvaluationMode.LimitedCompatibilityButCached)));\n            var query = dbContext.TestEntities.Where(e => e.IsSomething());\n\n            var exception = Should.Throw<InvalidOperationException>(() => query.ToQueryString());\n            exception.Message.ShouldBe(\"Adding parameters in a cached query context is not allowed. Explicitly call .Expressionify() on the query or use ExpressionEvaluationMode.FullCompatibilityButSlow.\");\n        }\n\n        [Test]\n        public void UseExpressionify_ShouldProduceSameOutputAsExpressionify()\n        {\n            using var dbContext = new TestDbContext(GetOptions());\n            var queryA = dbContext.TestEntities.Where(e => e.IsJohnDoe());\n            var queryB = dbContext.TestEntities.Expressionify().Where(e => e.IsJohnDoe());\n\n            queryA.ToQueryString().ShouldBe(queryB.ToQueryString());\n        }\n\n        [TestCase(ExpressionEvaluationMode.FullCompatibilityButSlow)]\n        [TestCase(ExpressionEvaluationMode.LimitedCompatibilityButCached)]\n        public void UseExpressionify_ShouldProduceSameOutputAsExpressionify_InAllModes(ExpressionEvaluationMode mode)\n        {\n            // Note: when not using the result of ParameterExtractingExpressionVisitor, the Cached mode returns another query with an additional concat (which would be unintended)\n\n            using var dbContext = new TestDbContext(GetOptions(o => o.WithEvaluationMode(mode)));\n            var queryA = dbContext.TestEntities.Select(e => e.GetName(\"oh hi\"));\n            var queryB = dbContext.TestEntities.Expressionify().Select(e => e.GetName(\"oh hi\"));\n\n            queryA.ToQueryString().ShouldBe(queryB.ToQueryString());\n        }\n\n        [Test]\n        public void UseExpressionify_EvaluationModeAlways_ShouldHandleEvaluatableExpressions()\n        {\n            using var dbContext = new TestDbContext(GetOptions(o => o.WithEvaluationMode(ExpressionEvaluationMode.FullCompatibilityButSlow)));\n            var query = dbContext.TestEntities.Select(e => e.ToTestView(null));\n\n            query.ToQueryString().ShouldBe($\"SELECT \\\"t\\\".\\\"Name\\\", NULL AS \\\"Street\\\"{Environment.NewLine}FROM \\\"TestEntities\\\" AS \\\"t\\\"\");\n        }\n\n        [TestCase(ExpressionEvaluationMode.FullCompatibilityButSlow)]\n        [TestCase(ExpressionEvaluationMode.LimitedCompatibilityButCached)]\n        public void UseExpressionify_WithEvaluationMode_SetsEvaluationMode(ExpressionEvaluationMode mode)\n        {\n            var options = GetOptions(o => o.WithEvaluationMode(mode));\n            var extension = options.FindExtension<ExpressionifyDbContextOptionsExtension>()!;\n\n            var debugInfo = new Dictionary<string, string>();\n            extension.Info.PopulateDebugInfo(debugInfo);\n\n            debugInfo[\"Expressionify:EvaluationMode\"].ShouldBe(mode.ToString());\n        }\n\n        [Test]\n        public void UseExpressionify_EvaluationMode_DefaultsToLimitedCompatibilityButCached()\n        {\n            var options = GetOptions();\n            var extension = options.FindExtension<ExpressionifyDbContextOptionsExtension>()!;\n\n            var debugInfo = new Dictionary<string, string>();\n            extension.Info.PopulateDebugInfo(debugInfo);\n\n            debugInfo[\"Expressionify:EvaluationMode\"].ShouldBe(ExpressionEvaluationMode.LimitedCompatibilityButCached.ToString());\n        }\n\n        private DbContextOptions GetOptions(Action<ExpressionifyDbContextOptionsBuilder>? optionsAction = null, bool useExpressionify = true)\n        {\n            var builder = new DbContextOptionsBuilder<TestDbContext>().UseSqlite(\"DataSource=:memory:\");\n\n            if (useExpressionify)\n                builder.UseExpressionify(optionsAction);\n\n            return builder.Options;\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/First/ExtensionMethods.cs",
    "content": "using System;\nusing Clave.Expressionify.Tests.Samples;\n\nnamespace Clave.Expressionify.Tests.First\n{\n    public static partial class ExtensionMethods\n    {\n        [Expressionify]\n        public static int ToInt(this string value) => Convert.ToInt32(value);\n\n        [Expressionify]\n        public static double ToDouble(this string value) => Convert.ToDouble(value);\n\n        [Expressionify]\n        public static int Pluss(this string a, string b) => a.ToInt() + b.ToInt();\n\n        [Expressionify]\n        public static int Squared(this int a) => a * a;\n\n        [Expressionify]\n        public static double Squared(this double a) => a * a;\n\n        [Expressionify]\n        public static string GetName<T>(this T thing) where T : IThing => thing.Name;\n    }\n}"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/Class1.cs",
    "content": "﻿namespace Clave.Expressionify.Tests.Samples\n{\n    public static partial class Class1\n    {\n        [Expressionify]\n        public static int Foo(int x) => 8;\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/Class2.cs",
    "content": "namespace Clave.Expressionify.Tests.Samples\n{\n    public static partial class Class2\n    {\n        public static int Bar(int x) => 0;\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/Class3.cs",
    "content": "namespace Clave.Expressionify.Tests.Samples;\n\npublic static partial class Class3\n{\n    [Expressionify]\n    public static int Foo(int x) => 8;\n\n    [Expressionify]\n    public static int Foo(string x) => 0;\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/Class4.cs",
    "content": "using System.Collections.Generic;\nusing System.Linq;\n\nnamespace Clave.Expressionify.Tests.Samples\n{\n    public static partial class Class4\n    {\n        [Expressionify]\n        private static int Foo(string x) => 8;\n\n        [Expressionify]\n        public static int Something(IEnumerable<string> x) => x.Select(Foo).Sum();\n\n        public static partial class NestedClass1\n        {\n            [Expressionify]\n            public static string Bar(int x) => $\"={8+x}\";\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/GenericClass.cs",
    "content": "﻿namespace Clave.Expressionify.Tests.Samples\n{\n    public partial class GenericClass<T>\n    {\n        [Expressionify]\n        public static int Foo(string x) => 8;\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/IThing.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace Clave.Expressionify.Tests.Samples\n{\n    public interface IThing\n    {\n        string Name { get; }\n    }\n\n    public class Thing1 : IThing\n    {\n        public string Name => \"Thing1\";\n    }\n\n    public class Thing2 : IThing\n    {\n        public string Name => \"Thing2\";\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Samples/Record1.cs",
    "content": "﻿namespace Clave.Expressionify.Tests.Samples\n{\n    public partial record Record1(string Name)\n    {\n        [Expressionify]\n        public static Record1 Create(string name) => new Record1(name);\n    }\n}\n"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Second/ExtensionMethods.cs",
    "content": "using System;\n\nnamespace Clave.Expressionify.Tests.Second\n{\n    public static partial class ExtensionMethods\n    {\n        [Expressionify]\n        public static int ToInt(this string value, bool extra) => Convert.ToInt32(value);\n\n        [Expressionify]\n        public static double ToDouble(this string value) => Convert.ToDouble(value);\n\n        [Expressionify]\n        public static int Pluss(this string a, string b) => a.ToInt(false) + b.ToInt(false);\n\n        [Expressionify]\n        public static int Squared(this int a) => a * a;\n    }\n}"
  },
  {
    "path": "tests/Clave.Expressionify.Tests/Tests.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Linq.Expressions;\nusing System.Reflection;\nusing Clave.Expressionify.Tests.First;\nusing Clave.Expressionify.Tests.Samples;\nusing NUnit.Framework;\nusing Shouldly;\n\nnamespace Clave.Expressionify.Tests\n{\n    public class Tests\n    {\n        [Test]\n        public void TestClass()\n        {\n            var prop = typeof(Class1).GetMethod(\"Foo_Expressionify_0\", BindingFlags.NonPublic | BindingFlags.Static);\n            prop.ShouldNotBeNull();\n            var expr = prop.Invoke(null, Array.Empty<object>()) as Expression<Func<int, int>>;\n            expr.ShouldNotBeNull();\n            expr.Compile().Invoke(1).ShouldBe(8);\n        }\n\n        [Test]\n        public void TestRecord()\n        {\n            var prop = typeof(Record1).GetMethod(\"Create_Expressionify_0\", BindingFlags.NonPublic | BindingFlags.Static);\n            prop.ShouldNotBeNull();\n            var expr = prop.Invoke(null, Array.Empty<object>()) as Expression<Func<string, Record1>>;\n            expr.ShouldNotBeNull();\n            expr.Compile().Invoke(\"test\").ShouldBe(new Record1(\"test\"));\n        }\n\n        [Test]\n        public void TestNonExpressionify()\n        {\n            typeof(Class2).GetProperties().ShouldBeEmpty();\n        }\n\n        [Test]\n        public void TestOverload()\n        {\n            typeof(Class3).GetMethods(BindingFlags.NonPublic|BindingFlags.Static).ShouldNotBeEmpty();\n\n            var prop0 = typeof(Class3).GetMethod(\"Foo_Expressionify_0\", BindingFlags.NonPublic | BindingFlags.Static);\n            prop0.ShouldNotBeNull();\n            var expr0 = prop0.Invoke(null, Array.Empty<object>()) as Expression<Func<int, int>>;\n            expr0.ShouldNotBeNull();\n            expr0.Compile().Invoke(1).ShouldBe(8);\n\n            var prop1 = typeof(Class3).GetMethod(\"Foo_Expressionify_1\", BindingFlags.NonPublic | BindingFlags.Static);\n            prop1.ShouldNotBeNull();\n            var expr1 = prop1.Invoke(null, Array.Empty<object>()) as Expression<Func<string, int>>;\n            expr1.ShouldNotBeNull();\n            expr1.Compile().Invoke(\"test\").ShouldBe(0);\n        }\n\n        [Test]\n        public void TestMethodGroup()\n        {\n            typeof(Class4).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).ShouldNotBeEmpty();\n\n            var prop0 = typeof(Class4).GetMethod(\"Foo_Expressionify_0\", BindingFlags.NonPublic | BindingFlags.Static);\n            prop0.ShouldNotBeNull();\n            var expr0 = prop0.Invoke(null, Array.Empty<object>()) as Expression<Func<string, int>>;\n            expr0.ShouldNotBeNull();\n            expr0.Compile().Invoke(\"1\").ShouldBe(8);\n\n            var prop1 = typeof(Class4).GetMethod(\"Something_Expressionify_0\", BindingFlags.NonPublic | BindingFlags.Static);\n            prop1.ShouldNotBeNull();\n            var expr1 = prop1.Invoke(null, Array.Empty<object>()) as Expression<Func<IEnumerable<string>, int>>;\n            expr1.ShouldNotBeNull();\n            expr1.Compile().Invoke(new[] { \"test\" }).ShouldBe(8);\n        }\n\n        [Test]\n        public void TestExpressionifyClass()\n        {\n            var data = new[]{\n                \"1\",\n                \"2\",\n                \"3\"\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.ToInt())\n                .ToList();\n\n            result.ShouldBe(new[] { 1, 2, 3 });\n        }\n\n        [Test]\n        public void TestExpressionifyNestedClass()\n        {\n            var data = new[]{\n                1,\n                2,\n                3\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => Class4.NestedClass1.Bar(x))\n                .ToList();\n\n            result.ShouldBe(new[] { \"=9\", \"=10\", \"=11\" });\n        }\n\n        [Test]\n        public void TestExpressionifyRecord()\n        {\n            var data = new[]{\n                \"1\",\n                \"2\",\n                \"3\"\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => Record1.Create(x))\n                .ToList();\n\n            result.ShouldBe(new Record1[] { new(\"1\"), new(\"2\"), new(\"3\") });\n        }\n\n        [Test]\n        public void TestMethodParameterUsedTwice()\n        {\n            var data = new[]{\n                1,\n                2,\n                3\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.Squared())\n                .ToList();\n\n            result.ShouldBe(new[] { 1, 4, 9 });\n        }\n\n        [Test]\n        public void TestMethodParameterUsedTwiceWithOverload()\n        {\n            var data = new[]{\n                1.0,\n                2.0,\n                3.0\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.Squared())\n                .ToList();\n\n            result.ShouldBe(new[] { 1.0, 4.0, 9.0 });\n        }\n\n        [Test]\n        public void TestMethodWithMultipleArguments()\n        {\n            var data = new[]{\n                new {a = \"1\", b = \"5\"},\n                new {a = \"3\", b = \"5\"},\n                new {a = \"2\", b = \"5\"},\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.a.Pluss(x.b))\n                .ToList();\n\n            result.ShouldBe(new[] { 6, 8, 7 });\n        }\n\n        [Test]\n        public void TestMethodCalledMultipleTimes()\n        {\n            var data = new[]{\n                new {a = \"1\", b = \"5\"},\n                new {a = \"2\", b = \"5\"},\n                new {a = \"3\", b = \"5\"}\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.a.ToInt() + x.b.ToInt())\n                .ToList();\n\n            result.ShouldBe(new[] { 6, 7, 8 });\n        }\n\n        [Test]\n        public void TestExpressionifiedTwice()\n        {\n            var data = new[]{\n                \"1\",\n                \"2\",\n                \"3\"\n            };\n\n            var sw = Stopwatch.StartNew();\n            data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.ToDouble())\n                .ToList();\n            var firstTime = sw.Elapsed;\n\n            sw.Restart();\n            data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.ToDouble())\n                .ToList();\n            var secondTime = sw.Elapsed;\n\n            secondTime.ShouldBeLessThan(firstTime);\n        }\n\n        [Test]\n        public void TestGenericExpression()\n        {\n            var data = new IThing[]\n            {\n                new Thing1(),\n                new Thing2(),\n                new Thing1()\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => x.GetName())\n                .ToList();\n\n            result.ShouldBe(new[] { \"Thing1\", \"Thing2\", \"Thing1\" });\n        }\n\n        [Test]\n        public void TestGenericType()\n        {\n            var data = new string[]\n            {\n                \"1\",\n                \"2\",\n                \"3\"\n            };\n\n            var result = data.AsQueryable()\n                .Expressionify()\n                .Select(x => GenericClass<string>.Foo(x))\n                .ToList();\n\n            result.ShouldBe(new[] { 8, 8, 8 });\n        }\n    }\n}\n"
  }
]