diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fc10450 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,168 @@ +# EditorConfig helps developers maintain consistent coding styles across editors and IDEs +# Docs: https://editorconfig.org/ + +root = true + +# ======================================== +# XML, MSBuild files +# ======================================== +[*.xml] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +[*.resx] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +[*.config] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +[*.csproj] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +[*.*proj] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +[*.props] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +[*.targets] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +# ======================================== +# JSON files +# ======================================== +[*.json] +indent_style = space +indent_size = 2 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +# ======================================== +# Markdown +# ======================================== +[*.md] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true + +# ======================================== +# YAML +# ======================================== +[*.yml] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true + +[*.yaml] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true + +# ======================================== +# C# files +# ======================================== +[*.cs] +indent_style = space +indent_size = 4 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +# ======================================== +# Solution files +# ======================================== +[*.sln] +indent_style = tab +tab_width = 4 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = true + +# ======================================== +# Windows Batch and Command files +# ======================================== +[*.bat] +indent_style = space +indent_size = 4 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = false +end_of_line = crlf + +[*.cmd] +indent_style = space +indent_size = 4 +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +trim_trailing_whitespace = false +end_of_line = crlf + +# ======================================== +# Powershell +# ======================================== +[*.ps1] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true + +[*.psd1] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true + +# ======================================== +# Bash +# ======================================== +[*.sh] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +end_of_line = lf + +[eng/git-hooks/*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false +insert_final_newline = true +resharper_enforce_empty_line_at_end_of_file = true +end_of_line = lf \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4947c44 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Let Git handle line endings automatically: +# - Checkout as CRLF on Windows, LF elsewhere +* text=auto + +# Explicit overrides for certain file types + +# C# code and projects +*.cs text eol=crlf diff=csharp +*.sln text merge=union eol=crlf +*.csproj text merge=union eol=lf +*.vbproj text merge=union eol=lf +*.fsproj text merge=union eol=lf + +# XML-based config/resources (store as LF in repo) +*.xml text eol=lf +*.resx text eol=lf +*.config text eol=lf +*.props text eol=lf +*.targets text eol=lf + +# JSON, YAML, Markdown +*.json text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.md text eol=lf + +# Git metadata +.gitattributes text eol=lf +.gitignore text eol=lf + +# Ensure shell scripts always use LF +*.sh text eol=lf +eng/git-hooks/* text eol=lf + +# Ensure batch scripts always use CRLF +*.bat eol=crlf +*.cmd eol=crlf diff --git a/.github/.github.folderproj b/.github/.github.folderproj new file mode 100644 index 0000000..8933ebd --- /dev/null +++ b/.github/.github.folderproj @@ -0,0 +1,25 @@ + + + + + + diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..3f9a332 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +changelog: + exclude: + labels: + - notes:ignore + authors: + - dependabot + categories: + - title: 💥 Breaking Changes + labels: + - notes:breaking-change + - title: 🎉 New Features + labels: + - notes:new-feature + - title: 🐞 Bug Fixes + labels: + - notes:bug-fix + - title: 🚀 Performance Improvements + labels: + - notes:performance-improvement + - title: 🏆 Improvements + labels: + - notes:improvement + - title: 📄 Website and API Documentation + labels: + - notes:website-or-documentation + - title: 💪 Other Changes + labels: + - "*" diff --git a/.github/workflows/Lucene-Net-CodeAnalysis-Dev-Tests.yml b/.github/workflows/Lucene-Net-CodeAnalysis-Dev-Tests.yml deleted file mode 100644 index 3478bbc..0000000 --- a/.github/workflows/Lucene-Net-CodeAnalysis-Dev-Tests.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow will build a .NET project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net - -name: Lucene.Net.CodeAnalysis.Dev - -on: - workflow_dispatch: - pull_request: - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8fff969 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,440 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: CI + +on: + workflow_dispatch: + push: + branches: + - main + - 'release/v*.*' # matches release/v1.2 + - 'release/v*.*.*' # matches release/v1.2.3 + - 'release-workflow*' # special branch names for testing release workflow (this file) from a PR + tags: + - 'v*' + pull_request: + release: + types: [published] + +jobs: + build: + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'pull_request' || + (github.event_name == 'push' && + (startsWith(github.ref, 'refs/heads/main') || + startsWith(github.ref, 'refs/heads/releases/v') || + github.ref_type == 'tag')) + runs-on: ubuntu-latest + env: + DIST_DIR: ${{ github.workspace }}/dist + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + steps: + - name: Checkout Source + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + + - name: Setup .NET 8.0 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 + with: + dotnet-version: 8.0.x + + - name: Cache NuGet Packages + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + with: + # '**/*.*proj' includes .csproj, .vbproj, .fsproj, msbuildproj, etc. + # '**/*.props' includes Directory.Packages.props and Directory.Build.props + # '**/*.targets' includes Directory.Build.targets + # '**/*.sln' and '*.sln' ensure root solution files are included (minimatch glitch for file extension .sln) + # 'global.json' included for SDK version changes + key: nuget-v1-${{ runner.os }}-${{ hashFiles('**/*.*proj', '**/*.props', '**/*.targets', '**/*.sln', '*.sln', 'global.json') }} + path: ${{ env.NUGET_PACKAGES }} + + - name: Restore + run: dotnet restore + + - name: Build + shell: pwsh + run: | + $nugetOut = Join-Path $env:DIST_DIR 'nuget' + dotnet build -c Release --no-restore -p:PackageOutputPath=$nugetOut + + - name: Publish Test Assemblies + shell: pwsh + run: | + $testsOut = Join-Path $env:DIST_DIR 'testBinaries' + New-Item -ItemType Directory -Force -Path $testsOut | Out-Null + + # Find all csproj files where a segment is exactly 'Tests' + Get-ChildItem -Recurse -Filter *.csproj | ForEach-Object { + Write-Host "Found project: $($_.FullName)" + $segments = $_.BaseName -split '\.' + if ($segments -contains 'Tests') { + $projName = $_.BaseName + $outDir = Join-Path $testsOut $projName + New-Item -ItemType Directory -Force -Path $outDir | Out-Null + Write-Host "Publishing $($_.FullName) -> $outDir" + dotnet publish $_.FullName -c Release --no-build -o $outDir --verbosity normal + if ($LASTEXITCODE -ne 0) { throw "Publish failed for $($_.FullName)" } + } + } + + - name: Upload NuGet packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: nuget-packages + path: ${{ env.DIST_DIR }}/nuget + + - name: Upload Test Assemblies + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: test-assemblies + path: ${{ env.DIST_DIR }}/testBinaries + + test: + needs: build + strategy: + matrix: + # macos-13 is specifically for running x64, macos-latest for arm64 + os: [windows-latest, ubuntu-latest, macos-13, macos-latest] + arch: [x64, arm64] + tfm: [net8.0] + exclude: + - arch: arm64 + os: macos-13 + - arch: arm64 + os: windows-latest + - arch: arm64 + os: ubuntu-latest + - arch: x64 + os: macos-latest + fail-fast: false + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout Source + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + # Set up working directories/paths consistently + - name: Set Paths (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $dir = "C:\w" + $testResultsRootDir = "$dir\work\test-results\${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.tfm }}" + $coverageReportDir = "$testResultsRootDir\coverage-report" + New-Item -ItemType Directory -Force -Path $dir + New-Item -ItemType Directory -Force -Path "$dir\dotnet" + New-Item -ItemType Directory -Force -Path "$dir\work" + New-Item -ItemType Directory -Force -Path "$testResultsRootDir" + New-Item -ItemType Directory -Force -Path "$coverageReportDir" + Add-Content $env:GITHUB_ENV "`nDOTNET_INSTALL_DIR=$dir\dotnet" + Add-Content $env:GITHUB_ENV "`nWORKPATH=$dir\work" + Add-Content $env:GITHUB_ENV "`nTEST_RESULTS_ROOT_DIR=$testResultsRootDir" + Add-Content $env:GITHUB_ENV "`nCOVERAGE_REPORT_DIR=$coverageReportDir" + Add-Content $env:GITHUB_PATH "`n$($env:USERPROFILE)\.dotnet\tools" + + - name: Set Paths (Linux/macOS) + if: runner.os == 'Linux' || runner.os == 'macOS' + shell: pwsh + run: | + $dir = "$env:RUNNER_TEMP/w" + $testResultsRootDir = "$dir/work/test-results/${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.tfm }}" + $coverageReportDir = "$testResultsRootDir/coverage-report" + New-Item -ItemType Directory -Force -Path $dir + New-Item -ItemType Directory -Force -Path "$dir/dotnet" + New-Item -ItemType Directory -Force -Path "$dir/work" + New-Item -ItemType Directory -Force -Path "$testResultsRootDir" + New-Item -ItemType Directory -Force -Path "$coverageReportDir" + Add-Content $env:GITHUB_ENV "`nDOTNET_INSTALL_DIR=$dir/dotnet" + Add-Content $env:GITHUB_ENV "`nWORKPATH=$dir/work" + Add-Content $env:GITHUB_ENV "`nTEST_RESULTS_ROOT_DIR=$testResultsRootDir" + Add-Content $env:GITHUB_ENV "`nCOVERAGE_REPORT_DIR=$coverageReportDir" + Add-Content $env:GITHUB_PATH "`n$($env:HOME)/.dotnet/tools" + + # Install the .NET SDK + - name: Setup .NET 8.0 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 + with: + dotnet-version: 8.0.x + + - name: Download Test Binaries + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + with: + name: test-assemblies + path: ${{ env.WORKPATH }} + + - name: Install Coverage + shell: pwsh + run: dotnet tool install --global dotnet-coverage + + - name: Install ReportGenerator + shell: pwsh + run: dotnet tool install --global dotnet-reportgenerator-globaltool + + - name: Run Tests + shell: pwsh + run: | + # Imports + . (Join-Path $env:GITHUB_WORKSPACE 'eng' 'build' 'Markdown-Formatting.ps1') + + $includesCoverage = $false + $results = @() + + Get-ChildItem -Directory $env:WORKPATH | ForEach-Object { + $dllPath = Join-Path $_.FullName ($_.Name + ".dll") + $resultsDirectory = Join-Path $env:TEST_RESULTS_ROOT_DIR $_.Name + New-Item -ItemType Directory -Force -Path $resultsDirectory | Out-Null + + if (Test-Path $dllPath) { + Write-Host "Running tests in $dllPath..." + + # Build as array of arguments (excluding the "dotnet" prefix and -- arguments) + $testCmd = @( + "test", $dllPath, + "--framework", "${{ matrix.tfm }}", + "--logger:console;verbosity=normal", + "--logger:trx;LogFileName=TestResults.trx", + "--results-directory", $resultsDirectory, + "--blame-hang-timeout", "10m", + "--blame-hang-dump-type", "mini", + "--blame-crash" + ) + + if ("${{ matrix.arch }}" -eq 'arm64' -and ($env:RUNNER_OS -eq 'macOS' -or $env:RUNNER_OS -eq 'Linux')) { + # Run tests without coverage + dotnet @testCmd -- RunConfiguration.TargetPlatform=${{ matrix.arch }} + } else { + # Ensure a coverage folder per test project output + $coverageDir = Join-Path $resultsDirectory "coverage" + New-Item -ItemType Directory -Force -Path $coverageDir | Out-Null + $coverageFile = Join-Path $coverageDir "coverage.xml" + + # Collect coverage in Cobertura format; TRX is produced by the inner 'dotnet test' + dotnet-coverage collect "dotnet $($testCmd -join ' ') -- RunConfiguration.TargetPlatform=${{ matrix.arch }}" ` + --output-format cobertura ` + --output "$coverageFile" ` + --settings (Join-Path "$env:GITHUB_WORKSPACE" 'eng' 'build' 'coverage.runsettings') + + if (Test-Path $coverageFile) { $includesCoverage = $true } + } + + # TRX inspection / status computation + $trxFile = Join-Path $resultsDirectory "TestResults.trx" + if (Test-Path $trxFile) { + $parsed = & "$env:GITHUB_WORKSPACE/eng/build/Parse-Test-Results.ps1" -Path $trxFile + $parsed | Add-Member -NotePropertyName "SuiteName" -NotePropertyValue $_.Name + $results += $parsed + } + } + } + + # Write summary + Format-Test-Results $results | Add-Content $env:GITHUB_STEP_SUMMARY + + if ($includesCoverage) { + # Generate a per-job coverage summary + HTML reports from all coverage.xml files created above + $reportsGlob = Join-Path $env:TEST_RESULTS_ROOT_DIR "**/coverage/coverage.xml" + reportgenerator ` + "-reports:$reportsGlob" ` + "-targetdir:$env:COVERAGE_REPORT_DIR" ` + "-reporttypes:MarkdownSummaryGithub;HtmlInline_AzurePipelines;HtmlChart" ` + "-verbosity:Warning" + + # Append collapsible coverage section to the job summary + $md = Join-Path $env:COVERAGE_REPORT_DIR "SummaryGithub.md" + if (Test-Path $md) { + Add-Content $env:GITHUB_STEP_SUMMARY "`n
Coverage`n" + Get-Content $md | Add-Content $env:GITHUB_STEP_SUMMARY + Add-Content $env:GITHUB_STEP_SUMMARY "`n
`n" + } else { + Write-Host "Coverage summary not generated (no coverage.xml found)." + } + } + + # Report test failure + if ($results | Where-Object { $_.FailedCount -gt 0 -or $_.Crashed }) { + exit 1 + } + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: test-results-${{ matrix.os }}-${{ matrix.arch }} + path: ${{ env.TEST_RESULTS_ROOT_DIR }} + + coverage: + name: aggregate-coverage + needs: test + runs-on: ubuntu-latest + steps: + - name: Download all test result artifacts + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + with: + pattern: test-results-* + path: all-results + merge-multiple: true + + # Required for ReportGenerator + - name: Setup .NET SDK + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 + with: + dotnet-version: 8.0.x + + - name: Install ReportGenerator + shell: pwsh + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool + Add-Content $env:GITHUB_PATH "`n$HOME/.dotnet/tools" + + - name: Build Aggregate Report + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path "coverage/aggregate" | Out-Null + reportgenerator ` + "-reports:all-results/**/coverage/coverage.xml" ` + "-targetdir:coverage/aggregate" ` + "-reporttypes:MarkdownSummaryGithub;HtmlInline_AzurePipelines;HtmlChart" ` + "-verbosity:Warning" + + $md = "coverage/aggregate/SummaryGithub.md" + if (Test-Path $md) { + Add-Content $env:GITHUB_STEP_SUMMARY "## Aggregate Coverage`n" + Get-Content $md | Add-Content $env:GITHUB_STEP_SUMMARY + } else { + Add-Content $env:GITHUB_STEP_SUMMARY "## Aggregate Coverage`n_No coverage files found._" + } + + - name: Upload Aggregate Coverage HTML + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: coverage-aggregate-html + path: coverage/aggregate + + release: + name: release + if: github.event_name == 'push' && github.ref_type == 'tag' + runs-on: ubuntu-latest + needs: + - build + - test + steps: + - name: Checkout Source + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + + - name: Get Tag Version + id: tagversion + shell: pwsh + run: | + $tag = '${{ github.ref_name }}' -replace '^v', '' + Add-Content $env:GITHUB_OUTPUT "version=$tag" + + # prerelease = true if contains a hyphen (semver prerelease part) + $prerelease = if ($tag -match '-') { 'true' } else { 'false' } + Add-Content $env:GITHUB_OUTPUT "`nprerelease=$prerelease" + + - name: Get Version from NBGV + id: nbgv + shell: pwsh + run: | + $nbgvOutput = & nbgv get-version + $nugetVersion = ($nbgvOutput | Where-Object { $_ -match '^NuGetPackageVersion:' }) -replace '^NuGetPackageVersion:\s+', '' + Add-Content $env:GITHUB_OUTPUT "version=$nugetVersion" + + - name: Download NuGet Packages + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + with: + name: nuget-packages + path: dist/nuget + - name: Verify Versions Match + shell: pwsh + run: | + $tagVersion = '${{ steps.tagversion.outputs.version }}' + $repoVersion = '${{ steps.nbgv.outputs.version }}' + + if ($tagVersion -ne $repoVersion) { + throw "Tag version $tagVersion does not match repository version $repoVersion. Make sure there are no typos in the Git tag name." + } + + # Also check that all .nupkg files contain the version + Get-ChildItem dist/nuget/*.nupkg | ForEach-Object { + if (-not $_.Name.Contains($tagVersion)) { + throw "Artifact version mismatch: $($_.Name) vs tag version $tagVersion" + } + } + - name: Create Draft GitHub Release + uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2 + with: + draft: true + prerelease: ${{ steps.tagversion.outputs.prerelease }} + files: dist/nuget/*.nupkg,dist/nuget/*.snupkg + generate_release_notes: true + token: ${{ secrets.GITHUB_TOKEN }} + + publish: + name: publish + if: github.event_name == 'release' && github.event.action == 'published' + runs-on: ubuntu-latest + steps: + - name: Download Release Assets + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require("fs"); + const path = require("path"); + + const release = context.payload.release; + if (!release) { + core.setFailed("No release payload found."); + return; + } + + const assets = release.assets || []; + const dir = "dist/nuget"; + fs.mkdirSync(dir, { recursive: true }); + + for (const asset of assets) { + if (!asset.name.endsWith(".nupkg") && !asset.name.endsWith(".snupkg")) { + continue; + } + core.info(`Downloading ${asset.name}...`); + const response = await github.request("GET /repos/{owner}/{repo}/releases/assets/{asset_id}", { + owner: context.repo.owner, + repo: context.repo.repo, + asset_id: asset.id, + headers: { Accept: "application/octet-stream" }, + }); + const filePath = path.join(dir, asset.name); + fs.writeFileSync(filePath, Buffer.from(response.data)); + core.info(`Saved to ${filePath}`); + } + - name: Push To NuGet + shell: pwsh + run: | + $files = Get-ChildItem "dist/nuget/*.nupkg", "dist/nuget/*.snupkg" -ErrorAction Ignore + foreach ($file in $files) { + dotnet nuget push $file.FullName --source $env:NUGET_SOURCE_URL --api-key $env:NUGET_API_KEY --skip-duplicate + } + env: + NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json' + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} diff --git a/.github/workflows/powershell-tests.yml b/.github/workflows/powershell-tests.yml new file mode 100644 index 0000000..2e53b36 --- /dev/null +++ b/.github/workflows/powershell-tests.yml @@ -0,0 +1,105 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Test Powershell Scripts + +on: + workflow_dispatch: + push: + branches: + - main + - 'release/v*.*' # matches release/v1.2 + - 'release/v*.*.*' # matches release/v1.2.3 + - 'release-workflow*' # special branch names for testing release workflow (this file) from a PR + paths: + - '**/*.ps1' + - '**/*.psm1' + - '**/*.ps1xml' + - '**/*.pssc' + - '**/*.cdxml' + - '**/*.psrc' + - '**/*.psc1' + pull_request: + paths: + - '**/*.ps1' + - '**/*.psm1' + - '**/*.ps1xml' + - '**/*.pssc' + - '**/*.cdxml' + - '**/*.psrc' + - '**/*.psc1' + +# De-duplicate runs +concurrency: + group: test-powershell-${{ github.ref }} + cancel-in-progress: true + +jobs: + pester: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + - name: Restore Powershell Dependencies + shell: pwsh + run: ./eng/Ensure-Powershell-Dependencies.ps1 + + - name: Run Pester Tests + shell: pwsh + run: | + # Imports + . (Join-Path $env:GITHUB_WORKSPACE 'eng' 'build' 'Markdown-Formatting.ps1') + + $ErrorActionPreference = 'Continue' + try { + $testResults = Invoke-Pester -Output Detailed -CI -PassThru + } catch { + Write-Warning "Invoke-Pester threw an exception: $_" + } + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version Latest + + $failed = $false + $results = @() + + if ($testResults -and $testResults.Containers -and $testResults.Containers.Count -gt 0) { + foreach ($container in $testResults.Containers) { + $suiteName = $container.Item.Name + $passedCount = $container.PassedCount + $failedCount = $container.FailedCount + $skippedCount = $container.SkippedCount + + $results += [PSCustomObject]@{ + SuiteName = $suiteName + PassedCount = [int]$passedCount + FailedCount = [int]$failedCount + IgnoredCount = [int]$skippedCount + Crashed = [bool]$false + } + + if ($failedCount -gt 0) { + $failed = $true + } + } + } + + # Write summary + Format-Test-Results $results | Add-Content $env:GITHUB_STEP_SUMMARY + + if ($failed) { + exit 1 + } diff --git a/.github/workflows/renovate-dependencies.yml b/.github/workflows/renovate-dependencies.yml new file mode 100644 index 0000000..d0e00a4 --- /dev/null +++ b/.github/workflows/renovate-dependencies.yml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Renovate + +on: + workflow_dispatch: # allows manual runs + schedule: + - cron: "0 3 * * 1" # 3 AM Monday UTC (same as ASF-friendly schedule) + +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + - name: Run Renovate + uses: renovatebot/github-action@f8af9272cd94a4637c29f60dea8731afd3134473 # v43.0.12 + with: + token: ${{ secrets.RENOVATE_TOKEN }} + env: + RENOVATE_REPOSITORIES: ${{ github.repository }} + LOG_LEVEL: debug diff --git a/.gitignore b/.gitignore index a255f99..1f78d93 100644 --- a/.gitignore +++ b/.gitignore @@ -396,4 +396,24 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +########################################### +# Custom +########################################### + +# BeyondCompare .orig files (when doing conflict resolution) +*.orig + +# Build artifacts +_artifacts/ + +# Rider / ReSharper .idea/ +*.sln.iml + +# Rider - allow shared inspection + code style profiles +!.idea/.idea.SPDX.CodeAnalysis/.idea/inspectionProfiles/ +!.idea/.idea.SPDX.CodeAnalysis/.idea/codeStyles/ + +# Custom Tools +.tools/* diff --git a/.rat-excludes b/.rat-excludes new file mode 100644 index 0000000..41fdbac --- /dev/null +++ b/.rat-excludes @@ -0,0 +1,17 @@ +# Note: these patterns are applied to single files or directories, not full paths +# coverage/* will ignore any coverage dir, but airflow/www/static/coverage/* will match nothing + +.git/* +.rat-excludes +.gitignore +.gitattributes + +# Exclude build assets +lib/* +obj/* +bin/* +_artifacts/* +_site/* + +# Exclude auto-generated designers +.*\.Designer\.cs diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..25245ae --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,93 @@ + + + + + $(MSBuildThisFileDirectory) + 12.0 + apache + lucenenet-codeanalysis-dev + + + + full + + + + false + + + + false + + + + false + + + + $(RepositoryRoot)eng/Lucene.Net.snk + 002400000480000094000000060200000024000052534131000400000100010075a07ce602f88ef263c7db8cb342c58ebd49ecdcc210fac874260b0213fb929ac3dcaf4f5b39744b800f99073eca72aebfac5f7284e1d5f2c82012a804a140f06d7d043d83e830cdb606a04da2ad5374cc92c0a49508437802fb4f8fb80a05e59f80afb99f4ccd0dfe44065743543c4b053b669509d29d332cd32a0cb1e97e84 + true + + + + false + true + + + + Lucene.Net + The Apache Software Foundation + $([System.DateTime]::UtcNow.Year.ToString()) + 2023 + $(BeginCopyrightYear) - $(CurrentYear) + $(CurrentYear) + Copyright © $(CopyrightYearRange) $(Company) + + + + true + true + + + + true + + + + + netstandard2.0 + + + $(MSBuildThisFileDirectory)..\_artifacts\noop\$(MSBuildProjectName)\bin\ + $(MSBuildThisFileDirectory)..\_artifacts\noop\$(MSBuildProjectName)\obj\ + $(BaseOutputPath) + $(BaseIntermediateOutputPath) + + + None + false + true + true + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..4d2df08 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,47 @@ + + + + + + + <_Parameter1>%(InternalsVisibleTo.Identity) + <_Parameter1 Condition=" '$(SignAssembly)' == 'true' And '$(PublicKey)' != '' ">%(InternalsVisibleTo.Identity), PublicKey=$(PublicKey) + + + + + + + true + $(TargetFramework) + + $(TargetFrameworks) + none + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..84f2cd3 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,50 @@ + + + + true + true + + + + 4.14.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE.txt b/LICENSE.txt index 93b51b1..a8a2434 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2023 Shad Storhaug + Copyright 2023-2025 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings b/Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings new file mode 100644 index 0000000..42cff23 --- /dev/null +++ b/Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index 170a989..31531c0 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -1,11 +1,42 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. VisualStudioVersion = 17.5.33627.172 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = ".github", ".github\.github.folderproj", "{873A3BE7-3364-F423-9686-FFB58C0896C4}" +EndProject +Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "docs", "docs\docs.folderproj", "{F1CC6D09-070B-440A-BDA2-C7A8037076ED}" +EndProject +Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "eng", "eng\eng.folderproj", "{9A1C280A-5010-45E9-9EC8-B09F0F9517A2}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1A48DD8E-1D71-43AE-B15D-977A87972623}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + src\Directory.Build.targets = src\Directory.Build.targets + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8170F744-3AE0-41EE-8986-611BA2C20425}" + ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props + tests\Directory.Build.targets = tests\Directory.Build.targets + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev.Vsix", "src\Lucene.Net.CodeAnalysis.Dev.Vsix\Lucene.Net.CodeAnalysis.Dev.Vsix.csproj", "{B9116527-2486-4A4C-90F8-378DF26E39AF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lucene.Net.CodeAnalysis.Dev", "src\Lucene.Net.CodeAnalysis.Dev\Lucene.Net.CodeAnalysis.Dev.csproj", "{0611A6A8-372D-4E03-BAAF-731FF4844D6C}" EndProject @@ -13,12 +44,54 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lucene.Net.CodeAnalysis.Dev EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev.Sample", "src\Lucene.Net.CodeAnalysis.Dev.Sample\Lucene.Net.CodeAnalysis.Dev.Sample.csproj", "{62BE25C9-72F2-4348-96C4-7329252E9DE8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev.Package", "src\Lucene.Net.CodeAnalysis.Dev.Package\Lucene.Net.CodeAnalysis.Dev.Package.csproj", "{A476A043-926E-488B-A825-02EB0B410CFD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + DiagnosticCategoryAndIdRanges.txt = DiagnosticCategoryAndIdRanges.txt + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.Packages.props = Directory.Packages.props + global.json = global.json + LICENSE.txt = LICENSE.txt + Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings = Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings + NOTICE.txt = NOTICE.txt + README.md = README.md + renovate.json = renovate.json + version.json = version.json + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev.CodeFixes", "src\Lucene.Net.CodeAnalysis.Dev.CodeFixes\Lucene.Net.CodeAnalysis.Dev.CodeFixes.csproj", "{94EE9776-30FC-4976-8C1D-AFE6A71BF8F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests", "tests\Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests\Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj", "{2ADD8F4F-8360-4994-9451-B6741842AC58}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev.TestUtilities", "tests\Lucene.Net.CodeAnalysis.Dev.TestUtilities\Lucene.Net.CodeAnalysis.Dev.TestUtilities.csproj", "{D7D95A33-0FB1-4F2A-A5CD-27719995026C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {873A3BE7-3364-F423-9686-FFB58C0896C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {873A3BE7-3364-F423-9686-FFB58C0896C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {873A3BE7-3364-F423-9686-FFB58C0896C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {873A3BE7-3364-F423-9686-FFB58C0896C4}.Release|Any CPU.Build.0 = Release|Any CPU + {F1CC6D09-070B-440A-BDA2-C7A8037076ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1CC6D09-070B-440A-BDA2-C7A8037076ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1CC6D09-070B-440A-BDA2-C7A8037076ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1CC6D09-070B-440A-BDA2-C7A8037076ED}.Release|Any CPU.Build.0 = Release|Any CPU + {9A1C280A-5010-45E9-9EC8-B09F0F9517A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A1C280A-5010-45E9-9EC8-B09F0F9517A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A1C280A-5010-45E9-9EC8-B09F0F9517A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A1C280A-5010-45E9-9EC8-B09F0F9517A2}.Release|Any CPU.Build.0 = Release|Any CPU + {B9116527-2486-4A4C-90F8-378DF26E39AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9116527-2486-4A4C-90F8-378DF26E39AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9116527-2486-4A4C-90F8-378DF26E39AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9116527-2486-4A4C-90F8-378DF26E39AF}.Release|Any CPU.Build.0 = Release|Any CPU {0611A6A8-372D-4E03-BAAF-731FF4844D6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0611A6A8-372D-4E03-BAAF-731FF4844D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0611A6A8-372D-4E03-BAAF-731FF4844D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -31,14 +104,35 @@ Global {62BE25C9-72F2-4348-96C4-7329252E9DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU {62BE25C9-72F2-4348-96C4-7329252E9DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU {62BE25C9-72F2-4348-96C4-7329252E9DE8}.Release|Any CPU.Build.0 = Release|Any CPU + {A476A043-926E-488B-A825-02EB0B410CFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A476A043-926E-488B-A825-02EB0B410CFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A476A043-926E-488B-A825-02EB0B410CFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A476A043-926E-488B-A825-02EB0B410CFD}.Release|Any CPU.Build.0 = Release|Any CPU + {94EE9776-30FC-4976-8C1D-AFE6A71BF8F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94EE9776-30FC-4976-8C1D-AFE6A71BF8F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94EE9776-30FC-4976-8C1D-AFE6A71BF8F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94EE9776-30FC-4976-8C1D-AFE6A71BF8F5}.Release|Any CPU.Build.0 = Release|Any CPU + {2ADD8F4F-8360-4994-9451-B6741842AC58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2ADD8F4F-8360-4994-9451-B6741842AC58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2ADD8F4F-8360-4994-9451-B6741842AC58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2ADD8F4F-8360-4994-9451-B6741842AC58}.Release|Any CPU.Build.0 = Release|Any CPU + {D7D95A33-0FB1-4F2A-A5CD-27719995026C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7D95A33-0FB1-4F2A-A5CD-27719995026C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7D95A33-0FB1-4F2A-A5CD-27719995026C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7D95A33-0FB1-4F2A-A5CD-27719995026C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {B9116527-2486-4A4C-90F8-378DF26E39AF} = {1A48DD8E-1D71-43AE-B15D-977A87972623} {0611A6A8-372D-4E03-BAAF-731FF4844D6C} = {1A48DD8E-1D71-43AE-B15D-977A87972623} {007DD065-3DF1-4AC1-9403-FE11E4654AF4} = {8170F744-3AE0-41EE-8986-611BA2C20425} {62BE25C9-72F2-4348-96C4-7329252E9DE8} = {1A48DD8E-1D71-43AE-B15D-977A87972623} + {A476A043-926E-488B-A825-02EB0B410CFD} = {1A48DD8E-1D71-43AE-B15D-977A87972623} + {94EE9776-30FC-4976-8C1D-AFE6A71BF8F5} = {1A48DD8E-1D71-43AE-B15D-977A87972623} + {2ADD8F4F-8360-4994-9451-B6741842AC58} = {8170F744-3AE0-41EE-8986-611BA2C20425} + {D7D95A33-0FB1-4F2A-A5CD-27719995026C} = {8170F744-3AE0-41EE-8986-611BA2C20425} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B261893F-67D2-4098-B66F-9191951DB5BB} diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..3a76bc6 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Lucene.Net +Copyright 2023-2025 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index d69f22a..18002cd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ + + # Apache Lucene.NET Dev Analyzers This repo contains custom [Roslyn analyzers](https://learn.microsoft.com/en-us/visualstudio/code-quality/roslyn-analyzers-overview?view=vs-2022) that are used by the [Apache Lucene.NET](https://lucenenet.apache.org/) project to enforce code quality and consistency, as well as provide automated code fixes. diff --git a/branding/lucene-net-icon-128x128.png b/branding/lucene-net-icon-128x128.png new file mode 100644 index 0000000..85cbdee Binary files /dev/null and b/branding/lucene-net-icon-128x128.png differ diff --git a/docs/building-and-testing.md b/docs/building-and-testing.md new file mode 100644 index 0000000..a984a7a --- /dev/null +++ b/docs/building-and-testing.md @@ -0,0 +1,80 @@ + + +# Building and Testing + +## Command Line + +### Prerequisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) + +### Building + +> **NOTE:** If the project is open in Visual Studio, its background restore may interfere with these commands. It is recommended to close all instances of Visual Studio that have this project open before executing. + +To build the source, clone or download and unzip the repository. From the repository or distribution root, execute the [**dotnet build**](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) command from a command prompt and include the desired options. + +#### Example +```console +dotnet build -c Release +``` + +> [!NOTE] +> NuGet packages are output by the build to the `/_artifacts/NuGetPackages/` directory. + +You can setup Visual Studio to read the NuGet packages like any NuGet feed by following these steps: + +1. In Visual Studio, right-click the solution in Solution Explorer, and choose "Manage NuGet Packages for Solution" +2. Click the gear icon next to the Package sources drop-down. +3. Click the `+` icon (for add) +4. Give the source a name such as `Lucene.Net.CodeAnalysis.Dev Local Packages` +5. Click the `...` button next to the Source field, and choose the `/_artifacts/NuGetPackages` folder on your local system. +6. Click OK + +Then all you need to do is choose the `Lucene.Net.CodeAnalysis.Dev Local Packages` feed from the dropdown (in the NuGet Package Manager) and you can search for, install, and update the NuGet packages just as you can with any Internet-based feed. + +### Testing + +Similarly to the build command, run [**dotnet test**](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) with the desired options. + +#### Example +```console +dotnet test -c Release --logger:"console;verbosity=normal" +``` + +## Visual Studio + +### Prerequisites + +1. Visual Studio 2022 or higher +2. [.NET 8.0 SDK](https://dotnet.microsoft.com/download/visual-studio-sdks) or higher + +### Execution + +1. Open `Lucene.Net.CodeAnalysis.Dev.sln` in Visual Studio. +2. Build a project or the entire solution, and wait for Visual Studio to discover the tests. +3. Run or debug the tests in Test Explorer, optionally using the desired filters. + +> [!TIP] +> When running tests in Visual Studio, [set the default processor architecture to x86, x64, or ARM64](https://stackoverflow.com/a/45946727) as applicable to your operating system. +> +> ![Test Explorer Architecture Settings](images/vs-test-architecture.png) diff --git a/docs/docs.folderproj b/docs/docs.folderproj new file mode 100644 index 0000000..8933ebd --- /dev/null +++ b/docs/docs.folderproj @@ -0,0 +1,25 @@ + + + + + + diff --git a/docs/images/release-build-outcomes.md b/docs/images/release-build-outcomes.md new file mode 100644 index 0000000..9fcc163 --- /dev/null +++ b/docs/images/release-build-outcomes.md @@ -0,0 +1,16 @@ +This markup can be edited and converted to .svg or .png here: +https://www.mermaidchart.com/app/projects/95759d78-db93-499c-ad66-0e3f698ba88c/diagrams/31dbd6bc-7ec8-4583-a456-55e3fe3f6cfc/version/v0.1/edit + +```mermaid +%%{ init: { "themeVariables": { "fontSize": "24px" } } }%% +flowchart TD + A["Tag + Push release branch"] --> B{"Draft Release generated?"} + B -- Yes --> C["Review release notes"] + C --> D["Check release artifacts"] + D --> E["Publish Release to NuGet.org"] + B -- No --> F["Check GitHub Actions logs"] + F --> G["Fix problems"] + G --> H["Delete failed tag"] + H --> I["Reset AnalyzerReleases.Shipped.md
header to {{vnext}} if needed"] + I --> A +``` diff --git a/docs/images/release-build-outcomes.svg b/docs/images/release-build-outcomes.svg new file mode 100644 index 0000000..c9c6b07 --- /dev/null +++ b/docs/images/release-build-outcomes.svg @@ -0,0 +1,102 @@ +

Yes

No

Tag + Push release branch

Draft Release generated?

Review release notes

Check release artifacts

Publish Release to NuGet.org

Check GitHub Actions logs

Fix problems

Delete failed tag

Reset AnalyzerReleases.Shipped.md
header to {{vnext}} if needed

\ No newline at end of file diff --git a/docs/images/release-workflow.md b/docs/images/release-workflow.md new file mode 100644 index 0000000..4f1728d --- /dev/null +++ b/docs/images/release-workflow.md @@ -0,0 +1,16 @@ +This markup can be edited and converted to .svg or .png here: +https://www.mermaidchart.com/app/projects/95759d78-db93-499c-ad66-0e3f698ba88c/diagrams/35faa26e-5ccf-4433-962e-32f20496471c/version/v0.1/edit + +```mermaid +flowchart LR + main[Main Branch] + release[Release Branch] + tag[Git Tag] + draft[Draft Release] + publish[Publish Release] + + main -->|Prepare Release| release + release -->|Tag Version| tag + tag --> draft + draft -->|Manual Review| publish +``` diff --git a/docs/images/release-workflow.svg b/docs/images/release-workflow.svg new file mode 100644 index 0000000..113591c --- /dev/null +++ b/docs/images/release-workflow.svg @@ -0,0 +1,122 @@ + + + +

Prepare Release

Tag Version

Manual Review

Main Branch

Release Branch

Git Tag

Draft Release

Publish Release

diff --git a/docs/images/vs-child-process-debugging-settings.png b/docs/images/vs-child-process-debugging-settings.png new file mode 100644 index 0000000..75e3cd8 Binary files /dev/null and b/docs/images/vs-child-process-debugging-settings.png differ diff --git a/docs/images/vs-native-debugging-setting.png b/docs/images/vs-native-debugging-setting.png new file mode 100644 index 0000000..895789e Binary files /dev/null and b/docs/images/vs-native-debugging-setting.png differ diff --git a/docs/images/vs-test-architecture.png b/docs/images/vs-test-architecture.png new file mode 100644 index 0000000..29857ac Binary files /dev/null and b/docs/images/vs-test-architecture.png differ diff --git a/docs/make-release.md b/docs/make-release.md new file mode 100644 index 0000000..c9fdef4 --- /dev/null +++ b/docs/make-release.md @@ -0,0 +1,434 @@ + + +# Making a Release + +> [!NOTE] +> All commands should be executed from the root of the repository unless otherwise stated. + +## Prerequisites + +- [PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell) 6.0 or higher (see [this question](http://stackoverflow.com/questions/1825585/determine-installed-powershell-version) to check your PowerShell version) +- [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) +- [nbgv tool](https://www.nuget.org/packages/nbgv/) (the version must match the one defined in [Directory.Packages.props](../Directory.Packages.props)) +- [Java 8](https://adoptium.net/temurin/releases) or higher (either a JRE or JDK) +- Bash (Installed automatically with [Git for Windows](https://gitforwindows.org/) on Windows). + +### Installing the NBGV Tool + +Perform a one-time install of the nbgv tool using the following dotnet CLI command: + +> [!NOTE] +> The version should match the one used in [Directory.Packages.props](../Directory.Packages.props). + +```console +dotnet tool install -g nbgv --version +``` + +### Configure the Git Commit Hooks + +To synchronize the `AnalyzerReleases.Shipped.md` release version with the latest commit, there is a Git commit hook that ensures that the version in the HEAD commit is the same version that is committed to the file. + +Check whether the Git `core.hooksPath` is correctly set: + +```console +git config core.hooksPath +``` + +If the command outputs a path, confirm that the path is `./eng/git-hooks`. In all other cases, run the following command to set it appropriately. + +```console +git config core.hooksPath ./eng/git-hooks +``` + +Repeat the first command to confirm that it is set. + +--------------------------------------------- + +## Prior to Release + +This project uses Nerdbank.GitVersioning to assist with creating version numbers based on the current branch and commit. This tool handles making pre-release and production releases on release branches. + +### Release Workflow Overview + +![Release Workflow](images/release-workflow.svg) + +### Prepare the Main Branch + +1. Ensure all of the features that will be included have been merged to the `main` branch. +2. Check whether the `AnalyzerReleases.Unshipped.md` and `AnalyzerReleases.Shipped.md` are set up consistently and align with the features that have been merged since the prior release. Do not yet move any rules from `AnalyzerReleases.Unshipped.md` to `AnalyzerReleases.Shipped.md`. That task will be performed in a later step. +3. Check whether the `README`, `LICENSE`, `NOTICE` and other documentation files are up to date. + +If any changes are required, it is recommended to use feature branch(es) and pull request(s) to update the `main` branch as appropriate before creating a release branch. + +### Decide on a Release Version + +The version that will be released next is controlled by the `version.json` file. We must choose the release version and commit it to the `main` branch prior to creating a release branch. + +> [!NOTE] +> If you are not familiar with these terms, these are covered in the [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html) document. + +For the purposes of this project: + +- **Major (Advanced)** - Released only when a new port of Lucene.NET is started (primarily to show a relationship between Lucene.NET and these analyzers) +- **Minor** - A typical release with one or more new features +- **Patch** - A release that only contains patches to existing features and/or updates to documentation +- **Prerelease** - A release that requires stabilization or is a one-off release for a specific purpose + +> [!NOTE] +> This project doesn't have any public API that users consume, so the type of release is strictly informational in nature, not functional. + +Now is the time to decide which of these strategies to use for the current version. For the next version (a future release version), we should always assume a patch. This is primarily so we never have to downgrade a version even if a patch is rarely done in practice. + +With that in mind, open `version.json` and look at the "version" property, which will determine next version that will be released. + +#### Example Version + +```json + "version": "2.0.0-alpha.{height}" +``` + +The above example shows that the next version that will be released from a release branch is 2.0.0 or 2.0.0-beta.x (where x is an auto-incrementing number). The actual version in the file (alpha) will be used only if the `main` branch is released directly (something that is rare and not covered here). + +If we are releasing new features and want the next Minor version (2.1.0), we need to update the `version.json` file to reflect that version. + +```json + "version": "2.1.0-alpha.{height}" +``` + +Or, if the next version will be a patch, then leave the file unchanged. Commit any changes to the `main` branch and push them upstream before proceeding. + +Prereleases should rarely need to change the `version.json` file and will later choose the [Requires Stabilization](#requires-stabilization) option when creating a release branch. + +> [!IMPORTANT] +> Release version numbers must always use all 3 version components when specified in `version.json`. + +## Create a Release Branch + +There are 2 supported scenarios for the release workflow: + +1. [Ready to Release](#ready-to-release) - No additional stabilization is required +2. [Requires Stabilization](#requires-stabilization) - A beta will be released, which will be marked as a pre-release to consumers + +> [!NOTE] +> In both cases, `main` is advanced to the specified `--nextVersion`. This number should always be a **patch** bump and it should always use all 3 version components (major.minor.patch). +> +> The release branch name is always based on the version being released (e.g., `release/v2.0.0`). + +### Ready to Release + +When the changes in the main branch are ready to release, create a release branch using the following nbgv tool command as specified in the [documentation](https://dotnet.github.io/Nerdbank.GitVersioning/docs/nbgv-cli.html#preparing-a-release). + +For example, assume the `version.json` file on the main branch is currently set up as `2.0.0-alpha.{height}`. We want to go from this version to a release of `2.0.0` and set the next version on the main branch as `2.0.1-alpha.{height}`. + +```console +nbgv prepare-release --nextVersion 2.0.1 +``` + +The command should respond with: + +```console +release/v2.0.0 branch now tracks v2.0.0 stabilization and release. +main branch now tracks v2.0.1-alpha.{height} development. +``` + +The tool created a release branch named `release/v2.0.0`. Every build from this branch will be versioned 2.0.0, regardless of how many commits are added. + +### Requires Stabilization + +When creating a release that may require a few iterations to become stable, it is better to create a beta branch (more about that decision can be found [here](https://dotnet.github.io/Nerdbank.GitVersioning/docs/nbgv-cli.html#preparing-a-release)). Starting from the same point as the [Ready to Release](#ready-to-release) scenario, run the following command. + +```console +nbgv prepare-release beta --nextVersion 2.0.1 +``` + +The command should respond with: + +```console +release/v2.0.0 branch now tracks v2.0.0-beta.{height} stabilization and release. +main branch now tracks v2.0.1-alpha.{height} development. +``` + +The tool created a release branch named `release/v2.0.0`. Every commit to this branch will be given a unique pre-release version starting with 2.0.0-beta and ending in a dot followed by one or more digits (i.e. `2.0.0-beta.123`). + +### Checkout the Release Branch + +After the release branch is created, the rest of the commits will be added to the release branch, so use the git checkout command to switch to that branch. + +```console +git checkout +``` + +--------------------------------------------- + +## Run the Apache Release Audit Tool + +> [!IMPORTANT] +> This command depends on Powershell and Java. + +The Release Audit Tool will ensure that all source code files and most other non-generated text files contain a license header. + +```console +pwsh ./rat.ps1 +``` + +The tool will apply the updates directly to the local working directory. Review and commit the changes to your local Git clone, adding exclusions to `.rat-excludes` and re-running as necessary. + +- Exclude files that already include license headers +- Exclude files that are automatically generated +- Exclude files that cannot contain license headers (such as test data) + +> [!NOTE] +> These extra commits will automatically bump the version number from what was specified when [Creating a Release Branch](creating-a-release-branch). It is normal and expected that we may have extra gaps between release version numbers. + + +## Updating the AnalyzerReleases Files + +Roslyn analyzers use two release tracking files to manage analyzer rule metadata: + +- **`AnalyzerReleases.Unshipped.md`** + Tracks analyzer rules that have been added or modified since the last release but are not yet published in a shipped package. + +- **`AnalyzerReleases.Shipped.md`** + Tracks analyzer rules that have been released in one or more shipped packages. This is the authoritative record of rules shipped at specific versions. + +Before tagging the release, you must ensure that these files are up to date. This ensures that the release metadata exactly matches the rules shipped in the NuGet package. + +> [!NOTE] +> If the release doesn't contain new or changed analyzer rules, this step can be skipped. For example, if the release only contains new code fixes and/or backward compatible patches to existing analyzers. + +### Release Version Token + +Since Nerdbank.GitVersioning calculates the release version, the `AnalyzerReleases.Shipped.md` file is expected to include a version token when it is committed. A version token must be included in the header of the new section being added to `AnalyzerReleases.Shipped.md`. + +#### Release Version Token Example + +```markdown +## Release {{vnext}} +``` + +### Standard Workflow + +> [!IMPORTANT] +> This change is expected to be the **final** commit prior to release. If there are any other changes you anticipate that need to be included in the release, they should be committed to the release branch prior to this step. + +> [!IMPORTANT] +> This step depends on the NBGV tool, Bash, and the setup of the Git commit hook as described in [Prerequisites](#prerequisites). + +1. **Locate pending unshipped rules** + Open `AnalyzerReleases.Unshipped.md`. This contains all rules added or modified since the last release. + +2. **Move unshipped rules into `AnalyzerReleases.Shipped.md`** + - Create a new section in `AnalyzerReleases.Shipped.md` with a heading for the release version, containing the version token. + - Copy the rules listed under `AnalyzerReleases.Unshipped.md` into this section. + - Keep the table formatting consistent with previous releases. + +3. **Clear `AnalyzerReleases.Unshipped.md`** + After the rules are copied over, `AnalyzerReleases.Unshipped.md` should either be empty or contain only rules that are not part of this release. + +4. **Commit the changes** + Commit the modifications before tagging the release. + +### Example: First and Second Releases with Version Token + +`AnalyzerReleases.Shipped.md` evolves by appending each release as a new section. Each release is marked with a `## Release ` header. + +```markdown +## Release 2.0.0-alpha.1 + +### New Rules + + Rule ID | Category | Severity | Notes +---------------|----------|----------|----------------------------------------- + LuceneDev1000 | Design | Warning | Floating point types should not be compared for exact equality + LuceneDev1001 | Design | Warning | Floating point types should be formatted with J2N methods + +## Release {{vnext}} + +### New Rules + + Rule ID | Category | Severity | Notes +---------------|----------|----------|----------------------------------------- + LuceneDev1002 | Design | Warning | Floating point type arithmetic needs to be checked + +### Removed Rules + + Rule ID | Notes +---------------|------------------------------------------------- + LuceneDev1001 | Replaced with LuceneDev1002 (better precision) +``` + +--------------------------------------------- + +## Creating a Release Build + +The release process is mostly automated. However, a manual review is required on the GitHub releases page. This allows you to: + +1. Manually review and edit the release notes +2. Re-generate the release notes after editing PR tags and titles +3. Manually check the release packages +4. Abort the release to try again +5. Publish the release to deploy the packages to NuGet.org + +

+ Release Build Outcomes +

+ +### Create a Draft Release + +Tagging the commit and pushing it to the GitHub repository will start the automated draft release. The progress of the release can be viewed in the [GitHub Actions UI](https://github.com/apache/lucenenet-codeanalysis-dev/actions). Select the run corresponding to the version tag that is pushed upstream to view the progress. + +#### Tag the HEAD Commit + +Run the following command to tag the HEAD commit of the release branch. + +```console +nbgv tag +``` + +> [!NOTE] +> The release build workflow always builds from the HEAD commit of the release branch. + +#### Push the Release Branch to the Upstream Repository + +The final step to begin the release build is to push the tag and any new commits to the upstream repository. + +```console +git push --follow-tags +``` + +> [!NOTE] +> If there are any local commits that have not yet been pushed, the above command will include them in the release. + +The push will start the automated draft release which will take a few minutes. When completed, there will be a new draft release in the [GitHub Releases](https://github.com/apache/lucenenet-codeanalysis-dev/releases) corresponding to the version you tagged. + +> [!NOTE] +> If the release doesn't appear, check the [GitHub Actions UI](https://github.com/apache/lucenenet-codeanalysis-dev/actions). Select the run corresponding to the version tag that is pushed upstream to view the progress. + +There are 2 possible outcomes for the release workflow: + +1. [Successful Draft Release](#successful-draft-release) - Proceed normally +2. [Failed Draft Release](#failed-draft-release) - Fix the problem that caused the release failure and reset the release branch for release + +--------------------------------------------- + +### Successful Draft Release + +#### Release Notes + +Review the draft release notes and edit or regenerate them if necessary. Release notes are generated based on PR titles and categorized by their labels. If something is amiss, they can be corrected by editing the PR titles and labels, deleting the previously generated release notes, and clicking the Generate Release Notes button. + +##### Labels that Apply to the Release Notes + +The following labels are recognized by the release notes generator. + +| GitHub Label | Action | +|--------------------------------|----------------------------------------------------------| +| notes:ignore | Removes the PR from the release notes | +| notes:breaking-change | Categorizes the PR under "Breaking Changes" | +| notes:new-feature | Categorizes the PR under "New Features" | +| notes:bug-fix | Categorizes the PR under "Bug Fixes" | +| notes:performance-improvement | Categorizes the PR under "Performance Improvements" | +| notes:improvement | Categorizes the PR under "Improvements" | +| notes:website-or-documentation | Categorizes the PR under "Website and API Documentation" | +| \ | Categorizes the PR under "Other Changes" | + +> [!NOTE] +> Using multiple labels from the above list is not supported and the first category in the above list will be used if more than one is applied to a GitHub pull request. + +#### Release Artifacts + +The release will also attach the NuGet packages that will be released to NuGet. Download the packages and run some basic checks: + +1. Put the `.nupkg` files into a local directory, and add a reference to the directory from Visual Studio. See [this answer](https://stackoverflow.com/a/10240180) for the steps. Verify that the NuGet packages can be referenced by a new project and that the project compiles. +2. Check the version information in [JetBrains dotPeek](https://www.jetbrains.com/decompiler/) to ensure the assembly version, file version, and informational version are consistent with what was specified in `version.json`. +3. Open the `.nupkg` files in [NuGet Package Explorer](https://www.microsoft.com/en-us/p/nuget-package-explorer/9wzdncrdmdm3#activetab=pivot:overviewtab) and check that files in the packages are present and that the XML config is up to date. + +#### Publish the Release + +Once everything is in order, the release can be published, which will deploy the packages to NuGet.org automatically. + +> [!NOTE] +> While the deployment will probably succeed, note that there is currently no automation if it fails to deploy on the first try. The GitHub API key must be regenerated once per year. If you are uncertain that it is still valid, check the expiry date in the NuGet.org portal now and regenerate, if needed. Update the `NUGET_API_KEY` in [GitHub Secrets](https://github.com/apache/lucenenet-codeanalysis-dev/settings/secrets/actions) with the new key. + +At the bottom of the draft release page, click on **Publish release**. + +--------------------------------------------- + +### Failed Draft Release + +If the build failed in any way, the release can be restarted by deleting the tag and trying again. First check to see the reason why the build failed in the [GitHub Actions UI](https://github.com/apache/lucenenet-codeanalysis-dev/actions) and correct any problems that were reported. + +#### Restarting the Draft Release + +##### Delete the Failed Tag + +Since the tag did not result in a release, it is important to delete it to avoid a confusing release history. + +```console +git tag -d v +git push --delete v +``` + +##### Resetting the Version in `AnalyzerReleases.Shipped.md` + +If you previously added a new section to `AnalyzerReleases.Shipped.md`, it may contain a version number that no longer corresponds to the release. Change the release header to include the replacement token, once again. + +```markdown +## Release {{vnext}} +``` + +Then commit the change to the release branch. + +Next, follow the same procedure starting at [Tag the HEAD Commit](#tag-the-head-commit) to restart the draft release. + +--------------------------------------------- + +## Post Release Steps + +### Merge the Release Branch + +Finally, merge the release branch back into the main branch and push the changes to the upstream repository. + +> [!IMPORTANT] +> Release branches start with `release/v`. + +```console +git checkout main +git merge +git push main +``` + +### Delete the Release Branch + +From this point, the release will be tracked historically using the Git tag, so there is no reason to keep the release branch once it has been merged. You may wish to delay the deletion for a few days in case it is needed for some reason, but when you are ready, the commands to delete the local and remote branches are: + +> [!IMPORTANT] +> Release branches start with `release/v`. Take care not to delete the tag, which starts with a `v`. + +```console +git branch -d +git push --delete +``` + +### Update Lucene.NET + +The Lucene.NET project is the only consumer of this package. If the release was intended for general use (not just a one-off scan), update the version in `Dependencies.props` to reflect the new release and submit a pull request to [the Lucene.NET repository](https://github.com/apache/lucenenet). diff --git a/docs/visual-studio-debugging.md b/docs/visual-studio-debugging.md new file mode 100644 index 0000000..346d410 --- /dev/null +++ b/docs/visual-studio-debugging.md @@ -0,0 +1,47 @@ + + +# Visual Studio Debugging + +Debugging the project in Visual Studio 2022 requires the [Microsoft Child Process Debugger Power Tool 2022](https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool2022). This is a requirement because starting the debugger session creates multiple processes and we need to inform the debugger which one we want to attach to. The extension makes this automatic, since the settings will load from the `Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings` file in the solution directory. + +### Setting up Debugging Manually + +This is how the settings should be configured to debug a Roslyn analyzer when using Visual Studio 2022. + +1. Install the [Microsoft Child Process Debugger Power Tool 2022](https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool2022) extension. +2. In the VSIX project, choose Properties and then select the Debug tab, ensure that native debugging is enabled. + + ![VS Native Debugging Settings](images/vs-native-debugging-setting.png) + +3. In the VS extension configuration available via Debug > Other Debug Targets > Child Process Debugging Settings..., set up the settings like the following. + + ![VS Child Process Debugging Settings](images/vs-child-process-debugging-settings.png) + +> **NOTE:** Since these settings are already configured in the repository, there should be no need to change them. + +### Starting Debugging + +Since the `Lucene.Net.CodeAnalysis.Dev.Vsix` project appears first in the solution file, it should be highlighted as the default project. If not, select it, right-click, and choose Select as Startup Project. + +Click on the play button or F5 to begin the debugging session. This will take a while for the first start, as it needs to download all native symbols first. However, this is a one-time startup cost. + +This launches a test instance of Visual Studio 2022. From there, you can create a new solution and use it to trigger various analyzer violations and walk through how the UX works for the user. You can also step into code, set and hit breakpoints. diff --git a/eng/Ensure-Powershell-Dependencies.ps1 b/eng/Ensure-Powershell-Dependencies.ps1 new file mode 100644 index 0000000..05c86fa --- /dev/null +++ b/eng/Ensure-Powershell-Dependencies.ps1 @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +param( + [string] $PesterVersion = "5.5.0" +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +# Ensure NuGet provider exists +if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) { + Install-PackageProvider -Name NuGet -Force -Scope CurrentUser | Out-Null +} + +# Ensure PSGallery is registered +$repo = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue +if (-not $repo) { + Register-PSRepository -Name PSGallery -SourceLocation "https://www.powershellgallery.com/api/v2" -InstallationPolicy Untrusted + $repo = Get-PSRepository -Name PSGallery +} + +# Track original InstallationPolicy +$originalPolicy = $repo.InstallationPolicy +$restorePolicy = $false + +try { + if ($originalPolicy -ne 'Trusted') { + # Temporarily trust PSGallery + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + $restorePolicy = $true + } + + # Check if correct Pester version is installed + $module = Get-Module -ListAvailable -Name Pester | Sort-Object Version -Descending | Select-Object -First 1 + if (-not $module -or $module.Version -ne [version]$PesterVersion) { + Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck -RequiredVersion $PesterVersion + } +} +finally { + if ($restorePolicy) { + # Restore original policy + Set-PSRepository -Name PSGallery -InstallationPolicy $originalPolicy + } +} diff --git a/Lucene.Net.snk b/eng/Lucene.Net.snk similarity index 100% rename from Lucene.Net.snk rename to eng/Lucene.Net.snk diff --git a/eng/WildcardVersionSupport.targets b/eng/WildcardVersionSupport.targets new file mode 100644 index 0000000..2fac11d --- /dev/null +++ b/eng/WildcardVersionSupport.targets @@ -0,0 +1,88 @@ + + + + + + + $(FileVersion) + + + false + + + $(AssemblyVersion) + + + false + + + + + + + + + <_Parameter1>$(GeneratedFileVersion) + + + + + + + + + + + <_Parameter1>$(GeneratedAssemblyVersion) + + + + + + + + + + + + + 0 && int.TryParse(parts[0], out var m) ? m : 1; + int minor = parts.Length > 1 && int.TryParse(parts[1], out var n) ? n : 0; + int buildPart = parts.Length > 2 && parts[2] != "*" ? int.Parse(parts[2]) : build; + int revPart = parts.Length > 3 && parts[3] != "*" ? int.Parse(parts[3]) : revision; + + OutputVersion = $"{major}.{minor}.{buildPart}.{revPart}"; + ]]> + + + + diff --git a/eng/build/Markdown-Formatting.Tests.ps1 b/eng/build/Markdown-Formatting.Tests.ps1 new file mode 100644 index 0000000..d815613 --- /dev/null +++ b/eng/build/Markdown-Formatting.Tests.ps1 @@ -0,0 +1,78 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +BeforeAll { + . $PSCommandPath.Replace('.Tests.ps1','.ps1') +} + +Describe "Format-Test-Results" { + $testCases = @( + @{ SuiteName="Alpha"; Passed=5; Failed=0; Ignored=16; Crashed=$false; Expected="✅ Passed" }, + @{ SuiteName="Beta"; Passed=1; Failed=2; Ignored=0; Crashed=$false; Expected="❌ Failed" }, + @{ SuiteName="Gamma"; Passed=0; Failed=0; Ignored=1; Crashed=$true; Expected="⚠️ Crashed" } + ) + + It "produces expected status lines" -ForEach $testCases { + $obj = [PSCustomObject]@{ + SuiteName = $_.SuiteName + PassedCount = $_.Passed + FailedCount = $_.Failed + IgnoredCount = $_.Ignored + Crashed = $_.Crashed + } + + $output = Format-Test-Results $obj + Write-Host $output -ForegroundColor Green + $output | Should -Match $_.Expected + $output | Should -Match "\*\*$($_.SuiteName)\*\*" + $output | Should -Match "Passed=$($_.PassedCount)" + $output | Should -Match "Failed=$($_.FailedCount)" + $output | Should -Match "Ignored=$($_.IgnoredCount)" + } + + Context "respects custom status text/icons" { + It "respects Crashed" { + $obj = [PSCustomObject]@{ + SuiteName="Delta"; PassedCount=0; FailedCount=0; IgnoredCount=0; Crashed=$true + } + + $output = Format-Test-Results $obj ` + -IconCrashed 'XX' -TextCrashed 'Boom' + $output | Should -Match "XX Boom" + } + + It "respects Passed" { + $obj = [PSCustomObject]@{ + SuiteName="Delta"; PassedCount=30; FailedCount=0; IgnoredCount=0; Crashed=$false + } + + $output = Format-Test-Results $obj ` + -IconPassed 'YY' -TextPassed 'MePassed' + $output | Should -Match "YY MePassed" + } + + It "respects Failed" { + $obj = [PSCustomObject]@{ + SuiteName="Delta"; PassedCount=30; FailedCount=2; IgnoredCount=0; Crashed=$false + } + + $output = Format-Test-Results $obj ` + -IconFailed 'ZZ' -TextFailed 'MeFailed' + $output | Should -Match "ZZ MeFailed" + } + } +} diff --git a/eng/build/Markdown-Formatting.ps1 b/eng/build/Markdown-Formatting.ps1 new file mode 100644 index 0000000..651e85d --- /dev/null +++ b/eng/build/Markdown-Formatting.ps1 @@ -0,0 +1,141 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +<# +.SYNOPSIS + Formats test results into a Markdown summary with icons and status text. + +.DESCRIPTION + The Format-Test-Results function takes one or more test result objects + (typically PSCustomObjects with fields such as SuiteName, PassedCount, + FailedCount, IgnoredCount, and Crashed) and produces a Markdown string + summarizing the results. Each test suite is displayed with an icon, + status text, and counts of passed, failed, and ignored tests. + + By default, the output includes a "## Test Results" heading and a bullet + point for each suite. + +.PARAMETER Results + One or more PSCustomObjects representing test results. Each object should + include the following properties: + - SuiteName [string] + - PassedCount [int] + - FailedCount [int] + - IgnoredCount [int] + - Crashed [bool] + + This parameter is mandatory and accepts input from the pipeline. + +.PARAMETER IconPassed + The icon to display for suites where all tests passed (default: ✅). + +.PARAMETER TextPassed + The label to display for passing suites (default: "Passed"). + +.PARAMETER IconFailed + The icon to display for suites with at least one failed test (default: ❌). + +.PARAMETER TextFailed + The label to display for failing suites (default: "Failed"). + +.PARAMETER IconCrashed + The icon to display for suites that crashed (default: ⚠️). + +.PARAMETER TextCrashed + The label to display for crashed suites (default: "Crashed"). + +.EXAMPLE + $results = @( + [pscustomobject]@{ SuiteName = "UnitTests"; PassedCount=10; FailedCount=0; IgnoredCount=0; Crashed=$false }, + [pscustomobject]@{ SuiteName = "IntegrationTests"; PassedCount=8; FailedCount=2; IgnoredCount=1; Crashed=$false }, + [pscustomobject]@{ SuiteName = "UITests"; PassedCount=0; FailedCount=0; IgnoredCount=0; Crashed=$true } + ) + + $results | Format-Test-Results + + Produces output similar to: + + ## Test Results + + - ✅ Passed - **UnitTests** | Passed=10, Failed=0, Ignored=0 + - ❌ Failed - **IntegrationTests** | Passed=8, Failed=2, Ignored=1 + - ⚠️ Crashed - **UITests** | Passed=0, Failed=0, Ignored=0 + +.EXAMPLE + $results | Format-Test-Results -IconFailed "💥" -TextFailed "Broken" + + Overrides the failed suite indicator with a custom icon and text. + +.OUTPUTS + System.String + Returns a Markdown-formatted string suitable for console output, + saving to a file, or inclusion in CI/CD summaries (e.g., GitHub Actions). + +.NOTES + The Markdown output is designed for human-readable summaries, + not for machine parsing. +#> + +function Format-Test-Results { + [CmdletBinding()] + param( + [Parameter(Mandatory, ValueFromPipeline, Position=0)] + [ValidateNotNullOrEmpty()] + [PSCustomObject[]] $Results, + + # Icons/texts as parameters (with defaults) + [string] $IconPassed = '✅', + [string] $TextPassed = 'Passed', + + [string] $IconFailed = '❌', + [string] $TextFailed = 'Failed', + + [string] $IconCrashed = '⚠️', + [string] $TextCrashed = 'Crashed' + ) + + begin { + $sb = [System.Text.StringBuilder]::new() + [void]$sb.AppendLine("## Test Results`n") + } + + process { + foreach ($r in $Results) { + if ($r.Crashed) { + $statusIcon = $IconCrashed + $statusText = $TextCrashed + } + elseif ($r.FailedCount -gt 0) { + $statusIcon = $IconFailed + $statusText = $TextFailed + } + else { + $statusIcon = $IconPassed + $statusText = $TextPassed + } + + $line = "- $statusIcon $statusText - **$($r.SuiteName)** " + + "| Passed=$($r.PassedCount), Failed=$($r.FailedCount), Ignored=$($r.IgnoredCount)" + + [void]$sb.AppendLine($line) + } + } + + end { + return $sb.ToString() + } +} diff --git a/eng/build/Parse-Test-Results.Tests.ps1 b/eng/build/Parse-Test-Results.Tests.ps1 new file mode 100644 index 0000000..1489fcf --- /dev/null +++ b/eng/build/Parse-Test-Results.Tests.ps1 @@ -0,0 +1,233 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +$global:TempTestDir = $null + +Describe "Parse-Test-Results" { + BeforeAll { + # Create a single temp directory for all tests + $global:TempTestDir = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid()) + New-Item -ItemType Directory -Path $global:TempTestDir | Out-Null + + # Helper function to invoke the script under test + function Parse-Test-Results { + param( + [Parameter(Position = 0)] + [string]$Path + ) + & $PSCommandPath.Replace('.Tests.ps1','.ps1') $Path + } + + # Helper function to create a TRX file in the temp folder + function New-TrxFile { + param( + [string]$Content, + [string]$FileName = "$(New-Guid).trx" + ) + + $filePath = Join-Path $global:TempTestDir $FileName + $Content | Set-Content -Path $filePath -Encoding UTF8 + return $filePath + } + } + + It "parses a passed run" { + # Arrange + $trxContent = @" + + + + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'passed.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 3 + $result.FailedCount | Should -Be 0 + $result.IgnoredCount | Should -Be 0 + $result.Crashed | Should -Be $false + } + + It "parses a failed run" { + # Arrange + $trxContent = @" + + + + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'failed.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 2 + $result.FailedCount | Should -Be 1 + $result.IgnoredCount | Should -Be 0 + $result.Crashed | Should -Be $false + } + + It "calculates ignored test count" { + # Arrange + $trxContent = @" + + + + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'failed.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 2 + $result.FailedCount | Should -Be 1 + $result.IgnoredCount | Should -Be 4 + $result.Crashed | Should -Be $false + } + + Context "detects a crash" { + It "could not find dotnet" { + # Arrange + $trxContent = @" + + + + + + + + Could not find 'dotnet.exe' host + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'crashed-could-not-find-dotnet.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 0 + $result.FailedCount | Should -Be 0 + $result.IgnoredCount | Should -Be 0 + $result.Crashed | Should -Be $true + } + + It "could not load assembly" { + # Arrange + $trxContent = @" + + + + + + + + Could not load file or assembly 'foo.dll' + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'crashed-could-not-load-assembly.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 0 + $result.FailedCount | Should -Be 0 + $result.IgnoredCount | Should -Be 0 + $result.Crashed | Should -Be $true + } + + It "exited with error" { + # Arrange + $trxContent = @" + + + + + + + + The program exited with error 1234. + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'crashed-exited-with-error.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 0 + $result.FailedCount | Should -Be 0 + $result.IgnoredCount | Should -Be 0 + $result.Crashed | Should -Be $true + } + + It "no test is available" { + # Arrange + $trxContent = @" + + + + + + + + No test is available in the assembly + + + +"@ + $trxPath = New-TrxFile -Content $trxContent -FileName 'crashed-no-test-is-available.trx' + + # Act + $result = Parse-Test-Results -Path $trxPath + + # Assert + $result.PassedCount | Should -Be 0 + $result.FailedCount | Should -Be 0 + $result.IgnoredCount | Should -Be 0 + $result.Crashed | Should -Be $true + } + } + + AfterAll { + # Clean up temp directory + if ($global:TempTestDir -and (Test-Path $global:TempTestDir)) { + Remove-Item -Path $global:TempTestDir -Recurse -Force + } + + # Perform cleanup based on the variable's value + $global:TempTestDir = $null # Reset for subsequent runs if needed + } +} diff --git a/eng/build/Parse-Test-Results.ps1 b/eng/build/Parse-Test-Results.ps1 new file mode 100644 index 0000000..67af2ca --- /dev/null +++ b/eng/build/Parse-Test-Results.ps1 @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +<# +.SYNOPSIS + Parses a Visual Studio TRX test results file and summarizes the test outcomes. + +.DESCRIPTION + This script reads a TRX (Test Result XML) file produced by Visual Studio or + `dotnet test` and extracts key information about the test run. It calculates + the number of passed, failed, and ignored tests, and detects if the test + run crashed based on specific error messages in the TRX file. + +.PARAMETER Path + The path to the results file to parse. The script throws an error if the file + does not exist. Position 0. + +.EXAMPLE + $result = .\Parse-Test-Results.ps1 -Path "C:\temp\testresults.trx" + + Returns a PSCustomObject with properties: + Passed - Number of passed tests + Failed - Number of failed tests + Ignored - Number of ignored/skipped tests + Crashed - Boolean indicating if the test run crashed + +.NOTES + - Requires PowerShell 5.x or later. + - Stops execution on any errors and uses strict mode for variable usage. + - Designed to be used in CI/CD pipelines or automated test scripts. + +.OUTPUTS + PSCustomObject with properties: + - Passed [int] + - Failed [int] + - Ignored [int] + - Crashed [bool] + +#> +param( + [Parameter(Position = 0)] + [string]$Path +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +if (-not (Test-Path $Path)) { + throw "File not found: $Path" +} + +$reader = [System.Xml.XmlReader]::Create($Path) +try { + [bool]$countersFound = $false + [bool]$inRunInfos = $false + [bool]$crashed = $false + [int]$failedCount = 0 + [int]$passedCount = 0 + [int]$ignoredCount = 0 + + while ($reader.Read()) { + if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element) { + if (!$countersFound -and $reader.Name -eq 'Counters') { + $failedCount = [int]$reader.GetAttribute('failed') + $passedCount = [int]$reader.GetAttribute('passed') + $ignoredCount = [int]$reader.GetAttribute('total') - [int]$reader.GetAttribute('executed') + $countersFound = $true + } + if ($reader.Name -eq 'RunInfos') { $inRunInfos = $true } + if ($inRunInfos -and !$crashed -and $reader.Name -eq 'Text') { + $innerXml = $reader.ReadInnerXml() + if ($innerXml -and ( + $innerXml.Contains('Test host process crashed') -or + $innerXml.Contains('Could not load file or assembly') -or + $innerXml.Contains("Could not find `'dotnet.exe`' host") -or + $innerXml.Contains('No test is available') -or + $innerXml.Contains('exited with error') + )) { + $crashed = $true + } + } + } + if ($reader.NodeType -eq [System.Xml.XmlNodeType]::EndElement -and $reader.Name -eq 'RunInfos') { + $inRunInfos = $false + } + } +} +finally { + $reader.Dispose() +} + +[PSCustomObject]@{ + PassedCount = $passedCount + FailedCount = $failedCount + IgnoredCount = $ignoredCount + Crashed = $crashed +} diff --git a/eng/build/coverage.runsettings b/eng/build/coverage.runsettings new file mode 100644 index 0000000..cdd999f --- /dev/null +++ b/eng/build/coverage.runsettings @@ -0,0 +1,100 @@ + + + + + + + + + .*\.dll$ + .*\.exe$ + + + + .*\.Tests(\.|$).* + + + .*NUnit3\.TestAdapter.* + + + .*Microsoft\.CodeAnalysis\.Analyzer\.Testing.* + + + + + + + + ^System\.Diagnostics\.DebuggerHiddenAttribute$ + ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ + ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ + ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ + + + + + + + .*[Mm]icrosoft.* + + + + + + + + + ^B77A5C561934E089$ + ^b77a5c561934e089$ + + + ^B03F5F7F11D50A3A$ + ^b03f5f7f11d50a3a$ + + + ^31BF3856AD364E35$ + ^31bf3856ad364e35$ + + + ^89845DCD8080CC91$ + ^89845dcd8080cc91$ + + + ^71E9BCE111E9429C$ + ^71e9bce111e9429c$ + + + ^8F50407C4E9E73B6$ + ^8f50407c4e9e73b6$ + + + ^E361AF139669C375$ + ^e361af139669c375$ + + + + + + + False + + True + False + False + + False + False + diff --git a/eng/eng.folderproj b/eng/eng.folderproj new file mode 100644 index 0000000..b5711dc --- /dev/null +++ b/eng/eng.folderproj @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/eng/git-hooks/post-commit b/eng/git-hooks/post-commit new file mode 100755 index 0000000..5918708 --- /dev/null +++ b/eng/git-hooks/post-commit @@ -0,0 +1,71 @@ +#!/bin/sh +# eng/git-hooks/post-commit +# Replaces {{vnext}} in "## Release {{vnext}}" with the NuGetPackageVersion +# only if the file contains the token. Safe from infinite loops. + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +file="src/Lucene.Net.CodeAnalysis.Dev/AnalyzerReleases.Shipped.md" +token="{{vnext}}" + +# Bail if already running to prevent infinite loop +if [ -n "$POST_COMMIT_RUNNING" ]; then + exit 0 +fi + +# Bail if the file doesn't exist +[ ! -f "$file" ] && exit 0 + +# Bail if the token doesn't exist in the file +if ! grep -q "## Release $token" "$file"; then + exit 0 +fi + +# Check if nbgv tool is installed +if ! command -v nbgv >/dev/null 2>&1; then + cat < + + + true + The Apache Software Foundation + https://lucenenet.apache.org + lucene-net-icon-128x128.png + Apache-2.0 + readme.md + NOTICE.txt + https://github.com/$(GitHubOrganization)/$(GitHubProject)/releases/tag/v$(PackageVersion) + $(ReleaseNotesUrl) + + + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..8d6478c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "msbuild-sdks": { + "Microsoft.Build.NoTargets": "3.7.56" + }, + "sources": [ "src" ] +} diff --git a/rat.ps1 b/rat.ps1 new file mode 100644 index 0000000..15dd4c6 --- /dev/null +++ b/rat.ps1 @@ -0,0 +1,107 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +<# +.SYNOPSIS + Helper script to download and run the Apache Release Audit Tool (RAT). + +.DESCRIPTION + This script automates use of the Apache RAT tool during release preparation. + It ensures that the RAT JAR is available locally (downloading it if needed) + and then runs it against the source tree to check for missing license headers + and other compliance issues. + + By default, the tool is downloaded into a `.tools/rat` directory located next + to this script. The script always runs RAT with the repository root (same + directory as this script) as the target directory. + +.PARAMETER Version + The version of Apache RAT to use (default: 0.13). + +.PARAMETER ExcludeFileName + Name of an exclude file containing path patterns that RAT should ignore. + This file should be located next to the script. Default: rat-exclude.txt + +.REQUIREMENTS + - Java 8+ must be installed and available on the PATH. + - Internet connection (for first run, to download RAT). + +.EXAMPLE + pwsh ./rat.ps1 + + Runs Apache RAT (default version 0.13) with exclusions from `rat-exclude.txt`. + +.EXAMPLE + pwsh ./rat.ps1 -Version 0.13 -ExcludeFileName custom-exclude.txt + + Runs Apache RAT version 0.13 using the specified exclude file. + +.NOTES + This script is intended for use by release managers when preparing official + ASF releases. It is not normally required for day-to-day development. +#> +param( + [string]$Version = "0.13", + [string]$ExcludeFileName = ".rat-excludes" +) + +# Script directory (works in PowerShell Core and Windows PowerShell) +$scriptDir = $PSScriptRoot + +# Tool paths (kept under the script dir) +$ratDir = Join-Path $scriptDir ".tools\rat" +$ratJar = Join-Path $ratDir "apache-rat-$Version.jar" +$ratUrl = "https://repo1.maven.org/maven2/org/apache/rat/apache-rat/$Version/apache-rat-$Version.jar" + +# Exclude file path (resolved relative to script dir) +$ratExcludeFile = Join-Path $scriptDir $ExcludeFileName + +# Ensure tool folder exists and jar is present (download if missing) +if (-not (Test-Path $ratDir)) { + New-Item -ItemType Directory -Path $ratDir | Out-Null +} + +if (-not (Test-Path $ratJar)) { + Write-Host "Downloading Apache RAT $Version to $ratJar ..." + Invoke-WebRequest -Uri $ratUrl -OutFile $ratJar +} + +# If exclude file is optional, optionally warn if missing: +if (-not (Test-Path $ratExcludeFile)) { + Write-Host "Warning: exclude file '$ratExcludeFile' not found. Continuing without --exclude-file." + $useExclude = $false +} else { + $useExclude = $true +} + +$argsList = @( + "-jar", $ratJar, + "--dir", "`"$scriptDir`"", + "--addLicense", + "--force" +) + +if ($useExclude) { + $argsList += @("--exclude-file", "`"$ratExcludeFile`"") +} + +# Call java with argument list. Use & to invoke program. +& java @argsList + +if ($LASTEXITCODE -ne 0) { + throw "RAT exited with code $LASTEXITCODE" +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..b35268e --- /dev/null +++ b/renovate.json @@ -0,0 +1,171 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":dependencyDashboard", + ":prConcurrentLimit10", + ":prHourlyLimitNone" + ], + "enabledManagers": [ + "nuget", + "github-actions", + "custom.regex" + ], + "rangeStrategy": "bump", + "schedule": [], + "commitMessagePrefix": "chore: ", + "customManagers": [ + { + "customType": "regex", + "description": "Roslyn Analyzer package version property", + "managerFilePatterns": [ + "/(^|.*/)Directory\\.Packages\\.props$/" + ], + "matchStrings": [ + "<\\s*RoslynAnalyzerPackageVersion\\s*>(?[^<]+)<\\/\\s*RoslynAnalyzerPackageVersion\\s*>" + ], + "depNameTemplate": "Microsoft.CodeAnalysis.Common", + "datasourceTemplate": "nuget", + "versioningTemplate": "nuget" + } + ], + "packageRules": [ + { + "groupName": "Test Dependencies", + "groupSlug": "test-dependencies", + "matchManagers": [ + "nuget" + ], + "matchPackageNames": [ + "NUnit", + "NUnit3TestAdapter", + "Microsoft.NET.Test.Sdk" + ], + "labels": [ + "dependencies", + "test-deps", + "notes:ignore" + ] + }, + { + "groupName": "Roslyn Compiler Packages", + "groupSlug": "roslyn-compiler", + "matchManagers": [ + "custom.regex" + ], + "matchPackageNames": [ + "Microsoft.CodeAnalysis.Common", + "Microsoft.CodeAnalysis.CSharp", + "Microsoft.CodeAnalysis.CSharp.Features", + "Microsoft.CodeAnalysis.CSharp.Workspaces", + "Microsoft.CodeAnalysis.VisualBasic", + "Microsoft.CodeAnalysis.VisualBasic.Features", + "Microsoft.CodeAnalysis.VisualBasic.Workspaces", + "Microsoft.CodeAnalysis.Workspaces.Common" + ], + "matchUpdateTypes": [ + "minor", + "patch" + ], + "labels": [ + "dependencies", + "roslyn", + "notes:ignore" + ] + }, + { + "groupName": "Roslyn Analyzer Testing", + "groupSlug": "roslyn-analyzer-testing", + "matchManagers": [ + "nuget" + ], + "matchPackageNames": [ + "Microsoft.CodeAnalysis.Analyzer.Testing" + ], + "labels": [ + "dependencies", + "roslyn", + "notes:ignore" + ] + }, + { + "groupName": "System.Memory Dependencies", + "matchPackageNames": [ + "System.Memory", + "System.Buffers", + "System.Runtime.CompilerServices.Unsafe", + "System.Numerics.Vectors" + ], + "matchManagers": [ + "nuget" + ], + "enabled": false, + "labels": [ + "dependencies", + "manual-upgrade", + "notes:ignore" + ] + }, + { + "groupName": "Visual Studio SDK Build Tools", + "matchPackageNames": [ + "Microsoft.VSSDK.BuildTools" + ], + "matchManagers": [ + "nuget" + ], + "enabled": false, + "labels": [ + "dependencies", + "manual-upgrade", + "notes:ignore" + ] + }, + { + "groupName": "Analyzer Dependencies", + "groupSlug": "roslyn-analyzers", + "matchManagers": [ + "nuget" + ], + "matchDepNames": [ + "/.*Analyzers$/" + ], + "matchUpdateTypes": [ + "major", + "minor", + "patch" + ], + "labels": [ + "dependencies", + "analyzers", + "notes:ignore" + ] + }, + { + "groupName": "GitHub Actions", + "groupSlug": "github-actions-all", + "matchManagers": [ + "github-actions" + ], + "pinDigests": true, + "rangeStrategy": "replace", + "prCreation": "immediate", + "semanticCommits": "enabled", + "commitMessageTopic": "GitHub Action {{depName}}", + "labels": [ + "dependencies", + "ci", + "notes:ignore" + ] + }, + { + "matchFileNames": [ + "global.json" + ], + "enabled": false + } + ], + "automerge": false, + "recreateWhen": "always", + "dependencyDashboard": true +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..67c7a9d --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,23 @@ + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..ebdaddf --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,29 @@ + + + + + + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + $(NoWarn);CS1591 + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx new file mode 100644 index 0000000..d4db943 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Use {0} + Title for code fix; {0} is the code element to utilize. + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Lucene.Net.CodeAnalysis.Dev.CodeFixes.csproj b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Lucene.Net.CodeAnalysis.Dev.CodeFixes.csproj new file mode 100644 index 0000000..f38ca60 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Lucene.Net.CodeAnalysis.Dev.CodeFixes.csproj @@ -0,0 +1,66 @@ + + + + + + + netstandard2.0 + enable + + + false + + + + PrepareResources;$(CompileDependsOn) + true + en-US + + + + + + MSBuild:Compile + $(IntermediateOutputPath)CodeFixResources.designer.cs + CSharp + Lucene.Net.CodeAnalysis.Dev.CodeFixes + CodeFixResources + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs new file mode 100644 index 0000000..7d278ee --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LuceneDev1001_FloatingPointFormattingCSCodeFixProvider)), Shared] + public class LuceneDev1001_FloatingPointFormattingCSCodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => + [Descriptors.LuceneDev1001_FloatingPointFormatting.Id]; + + public override FixAllProvider GetFixAllProvider() => + WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Diagnostic? diagnostic = context.Diagnostics.FirstOrDefault(); + if (diagnostic is null) + return; + + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root is null) + return; + + // the diagnostic in the analyzer is reported on the member access (e.g. "x.ToString") + // but we need the whole invocation (e.g. "x.ToString(...)"). So find the invocation + // by walking ancestors if needed. + SyntaxNode? node = root.FindNode(diagnostic.Location.SourceSpan); + if (node is null) + return; + + if (node is not ExpressionSyntax exprNode) + return; + + SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return; + + if (!TryGetJ2NTypeAndMember(semanticModel, exprNode, out var j2nTypeName, out var memberAccess)) + return; + + string codeElement = $"J2N.Numerics.{j2nTypeName}.ToString(...)"; + + context.RegisterCodeFix( + CodeActionHelper.CreateFromResource( + CodeFixResources.UseX, + c => ReplaceWithJ2NToStringAsync(context.Document, memberAccess, c), + "UseJ2NToString", + codeElement), + diagnostic); + } + + private async Task ReplaceWithJ2NToStringAsync( + Document document, + MemberAccessExpressionSyntax memberAccess, + CancellationToken cancellationToken) + { + SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return document; + + if (!TryGetJ2NTypeAndMember(semanticModel, memberAccess, out var j2nTypeName, out _)) + return document; + + // Build J2N.Numerics.Single/Double.ToString + MemberAccessExpressionSyntax j2nToStringAccess = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("J2N"), + SyntaxFactory.IdentifierName("Numerics")), + SyntaxFactory.IdentifierName(j2nTypeName)) + .WithAdditionalAnnotations(Formatter.Annotation); + + MemberAccessExpressionSyntax fullAccess = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + j2nToStringAccess, + SyntaxFactory.IdentifierName("ToString")); + + // Build invocation: J2N.Numerics..ToString(, ) + if (memberAccess.Parent is not InvocationExpressionSyntax invocation) + return document; + + var newArgs = new List { SyntaxFactory.Argument(memberAccess.Expression) }; + if (invocation.ArgumentList != null) + newArgs.AddRange(invocation.ArgumentList.Arguments); + + InvocationExpressionSyntax newInvocation = SyntaxFactory.InvocationExpression( + fullAccess, + SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArgs))) + .WithTriviaFrom(invocation) // safe now + .WithAdditionalAnnotations(Formatter.Annotation); + + DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode(memberAccess.Parent, newInvocation); + + return editor.GetChangedDocument(); + } + + private static bool TryGetJ2NTypeAndMember( + SemanticModel semanticModel, + ExpressionSyntax expr, + out string j2nTypeName, + out MemberAccessExpressionSyntax memberAccess) + { + memberAccess = expr as MemberAccessExpressionSyntax + ?? expr.AncestorsAndSelf().OfType().FirstOrDefault(); + + if (memberAccess is null) + { + j2nTypeName = null!; // we always return false when the value is null, so we can ignore it here. + return false; + } + + var typeInfo = semanticModel.GetTypeInfo(memberAccess.Expression); + var type = typeInfo.Type; + + j2nTypeName = type?.SpecialType switch + { + SpecialType.System_Single => "Single", + SpecialType.System_Double => "Double", + _ => null! // we always return false when the value is null, so we can ignore it here. + }; + + if (j2nTypeName is null) + return false; + + return true; + } + } +} diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/CodeActionHelper.cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/CodeActionHelper.cs new file mode 100644 index 0000000..e9a68c2 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Utility/CodeActionHelper.cs @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes.Utility +{ + internal static class CodeActionHelper + { + /// + /// Create a CodeAction using a resource string and formatting arguments. + /// + public static CodeAction CreateFromResource( + string resourceValue, + Func> createChangedDocument, + string equivalenceKey, + params object[] args) + { + var title = string.Format(resourceValue, args); + return CodeAction.Create(title, createChangedDocument, equivalenceKey); + } + } +} diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Package/Lucene.Net.CodeAnalysis.Dev.Package.csproj b/src/Lucene.Net.CodeAnalysis.Dev.Package/Lucene.Net.CodeAnalysis.Dev.Package.csproj new file mode 100644 index 0000000..3354019 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Package/Lucene.Net.CodeAnalysis.Dev.Package.csproj @@ -0,0 +1,126 @@ + + + + + + + + + + netstandard2.0 + true + false + false + + true + true + + + + Lucene.Net.CodeAnalysis.Dev + Analyzers and code fixes for Lucene.NET development. + $(PackageTags);code analysis;code maintenance;roslyn + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\_artifacts\NuGetPackages\$(Configuration)')) + <_PackageVersionPropsFile>$(PackageOutputPath)\Lucene.Net.CodeAnalysis.Dev.Version.props + + + + <_CodeAnalysisCSAssemblyFile>$(RepositoryRoot)src\Lucene.Net.CodeAnalysis.Dev\bin\$(Configuration)\$(TargetFramework)\Lucene.Net.CodeAnalysis.Dev.dll + <_CodeAnalysisCodeFixesCSAssemblyFile>$(RepositoryRoot)src\Lucene.Net.CodeAnalysis.Dev.CodeFixes\bin\$(Configuration)\$(TargetFramework)\Lucene.Net.CodeAnalysis.Dev.CodeFixes.dll + + + + <_CodeAnalyisisResources Include="$(RepositoryRoot)src\Lucene.Net.CodeAnalysis.Dev\bin\$(Configuration)\$(TargetFramework)\**\*.resources.dll" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(PackageVersion)-debug-$([System.DateTime]::UtcNow.DayOfYear)-$([System.Math]::Floor($([System.DateTime]::UtcNow.TimeOfDay.TotalSeconds))) + + + + + + + + + + + + + <__NuGetFilePaths Include="$(PackageOutputPath)\*.nupkg" /> + + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/Lucene.Net.CodeAnalysis.Dev.Sample.csproj b/src/Lucene.Net.CodeAnalysis.Dev.Sample/Lucene.Net.CodeAnalysis.Dev.Sample.csproj index 27c92c2..7ac3045 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Sample/Lucene.Net.CodeAnalysis.Dev.Sample.csproj +++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/Lucene.Net.CodeAnalysis.Dev.Sample.csproj @@ -1,14 +1,114 @@ - - - - net8.0 - enable - enable - - - - - + + + + + + + net8.0 + enable + enable + + + + <_ArtifactsDirectory>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\_artifacts')) + <_NuGetPackageOutputPath>$(_ArtifactsDirectory)\NuGetPackages\$(Configuration) + <_PackageVersionPropsFilePath>$(_NuGetPackageOutputPath)\Lucene.Net.CodeAnalysis.Dev.Version.props + + obj\LocalNuGetPackages + <_RestorePackagesPath>$(RestorePackagesPath)\lucene.net.codeanalsis.dev + + + + $(RestoreSources);$(_NuGetPackageOutputPath) + + + + $(RestoreSources);https://api.nuget.org/v3/index.json + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1000Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1000Sample.cs index 21aeeca..7cbdda5 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1000Sample.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1000Sample.cs @@ -1,4 +1,23 @@ -namespace Lucene.Net.CodeAnalysis.Dev.Sample; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +namespace Lucene.Net.CodeAnalysis.Dev.Sample; public class LuceneDev1000Sample { diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1001Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1001Sample.cs index c0660fd..8a0978f 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1001Sample.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1001Sample.cs @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + using System.Globalization; namespace Lucene.Net.CodeAnalysis.Dev.Sample; diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1002Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1002Sample.cs index 30dd241..e5bd594 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1002Sample.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1002Sample.cs @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + namespace Lucene.Net.CodeAnalysis.Dev.Sample; public class LuceneDev1002Sample diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1003Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1003Sample.cs index c283005..187fbdd 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1003Sample.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1003Sample.cs @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + namespace Lucene.Net.CodeAnalysis.Dev.Sample; public class LuceneDev1003Sample diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1004Sample.cs b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1004Sample.cs index 333cb18..8b8d1c2 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1004Sample.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev.Sample/LuceneDev1004Sample.cs @@ -1,3 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + namespace Lucene.Net.CodeAnalysis.Dev.Sample; public class LuceneDev1004Sample diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/Lucene.Net.CodeAnalysis.Dev.Vsix.csproj b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/Lucene.Net.CodeAnalysis.Dev.Vsix.csproj new file mode 100644 index 0000000..cc5cc81 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/Lucene.Net.CodeAnalysis.Dev.Vsix.csproj @@ -0,0 +1,73 @@ + + + + + + + net472 + Lucene.Net.CodeAnalysis.Dev.Vsix + Lucene.Net.CodeAnalysis.Dev.Vsix + + + false + + + + + false + false + false + false + false + false + Roslyn + + + $(VsSDKInstall)\Microsoft.VsSDK.targets + + + + + + + + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) + + + + + + + + + + + + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets new file mode 100644 index 0000000..6ca9c6d --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets @@ -0,0 +1,42 @@ + + + + + $(GetVsixSourceItemsDependsOn);IncludeResourcesInVsix + + + + + + %(RecursiveDir) + + Microsoft.VisualStudio.Analyzer + + + %(RecursiveDir) + + Microsoft.VisualStudio.Analyzer + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/source.extension.vsixmanifest b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/source.extension.vsixmanifest new file mode 100644 index 0000000..4ae95c7 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/source.extension.vsixmanifest @@ -0,0 +1,26 @@ + + + + + Lucene.Net.CodeAnalysis.Dev + This is a debugging project for Lucene.Net.CodeAnalysis.Dev analyzers and code fixes. + + + + amd64 + + + + + + + + + + + + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj index a30a1a8..eb4a28b 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj +++ b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj @@ -1,30 +1,62 @@ + + + netstandard2.0 enable - latest + + + false + true true - true - false - true - true - true + + + + *$(MSBuildProjectFile)* - 1.0.0 + true + true PrepareResources;$(CompileDependsOn) + + + true + 1.0.0 + 1.0.* + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -44,14 +76,4 @@ - - $(SolutionDir)Lucene.Net.snk - 002400000480000094000000060200000024000052534131000400000100010075a07ce602f88ef263c7db8cb342c58ebd49ecdcc210fac874260b0213fb929ac3dcaf4f5b39744b800f99073eca72aebfac5f7284e1d5f2c82012a804a140f06d7d043d83e830cdb606a04da2ad5374cc92c0a49508437802fb4f8fb80a05e59f80afb99f4ccd0dfe44065743543c4b053b669509d29d332cd32a0cb1e97e84 - true - - - - - - diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs deleted file mode 100644 index 88a7010..0000000 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; - -namespace Lucene.Net.CodeAnalysis.Dev -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer : DiagnosticAnalyzer - { - public const string DiagnosticId = "LuceneDev1001"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LuceneDev1001_AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LuceneDev1001_AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.LuceneDev1001_AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Design"; - - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); - context.EnableConcurrentExecution(); - - // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information - //context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); - context.RegisterSyntaxNodeAction(AnalyzeNodeCS, SyntaxKind.SimpleMemberAccessExpression); - //context.RegisterSyntaxNodeAction(AnalyzeEqualsMethodNode, SyntaxKind.InvocationExpression); - } - - private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) - { - if (context.Node is Microsoft.CodeAnalysis.CSharp.Syntax.MemberAccessExpressionSyntax memberAccessExpression) - { - if (!(memberAccessExpression.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax)) - return; - - if (memberAccessExpression.Name.Identifier.ValueText != "ToString") - return; - - var leftSymbolInfo = Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetSymbolInfo(context.SemanticModel, memberAccessExpression.Expression); - - if (!FloatingPoint.IsFloatingPointType(leftSymbolInfo)) - return; // Check passed - - context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), memberAccessExpression.ToString())); - } - } - } -} diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs similarity index 59% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs index 1ddd54c..d70ae00 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -1,31 +1,35 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Threading; namespace Lucene.Net.CodeAnalysis.Dev { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "LuceneDev1000"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LuceneDev1000_AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LuceneDev1000_AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.LuceneDev1000_AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Design"; - - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + public override ImmutableArray SupportedDiagnostics => [Descriptors.LuceneDev1000_FloatingPointEquality]; public override void Initialize(AnalysisContext context) { @@ -47,7 +51,7 @@ public override void Initialize(AnalysisContext context) private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) { - if (context.Node is Microsoft.CodeAnalysis.CSharp.Syntax.BinaryExpressionSyntax binaryExpression) + if (context.Node is BinaryExpressionSyntax binaryExpression) { var leftSymbolInfo = Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetSymbolInfo(context.SemanticModel, binaryExpression.Left); var rightSymbolInfo = Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetSymbolInfo(context.SemanticModel, binaryExpression.Right); @@ -58,11 +62,11 @@ private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) if (!FloatingPoint.IsFloatingPointType(leftSymbolInfo) && !FloatingPoint.IsFloatingPointType(rightSymbolInfo)) return; // Check passed - context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), binaryExpression.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1000_FloatingPointEquality, context.Node.GetLocation(), binaryExpression.ToString())); } - else if (context.Node is Microsoft.CodeAnalysis.CSharp.Syntax.MemberAccessExpressionSyntax memberAccessExpression) + else if (context.Node is MemberAccessExpressionSyntax memberAccessExpression) { - if (!(memberAccessExpression.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax)) + if (!(memberAccessExpression.Parent is InvocationExpressionSyntax)) return; if (memberAccessExpression.Name.Identifier.ValueText != "Equals") @@ -73,7 +77,7 @@ private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) if (!FloatingPoint.IsFloatingPointType(leftSymbolInfo)) return; // Check passed - context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), memberAccessExpression.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1000_FloatingPointEquality, context.Node.GetLocation(), memberAccessExpression.ToString())); } } } diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs new file mode 100644 index 0000000..86f2af4 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace Lucene.Net.CodeAnalysis.Dev +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => [Descriptors.LuceneDev1001_FloatingPointFormatting]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); + context.EnableConcurrentExecution(); + + // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information + //context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + context.RegisterSyntaxNodeAction(AnalyzeNodeCS, SyntaxKind.SimpleMemberAccessExpression); + //context.RegisterSyntaxNodeAction(AnalyzeEqualsMethodNode, SyntaxKind.InvocationExpression); + } + + private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) + { + if (context.Node is MemberAccessExpressionSyntax memberAccessExpression) + { + if (!(memberAccessExpression.Parent is InvocationExpressionSyntax)) + return; + + if (memberAccessExpression.Name.Identifier.ValueText != "ToString") + return; + + var leftSymbolInfo = Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetSymbolInfo(context.SemanticModel, memberAccessExpression.Expression); + + if (!FloatingPoint.IsFloatingPointType(leftSymbolInfo)) + return; // Check passed + + context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1001_FloatingPointFormatting, context.Node.GetLocation(), memberAccessExpression.ToString())); + } + } + } +} diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs similarity index 62% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs index 31c2313..4b38638 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -1,31 +1,34 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Threading; namespace Lucene.Net.CodeAnalysis.Dev { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "LuceneDev1002"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LuceneDev1002_AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LuceneDev1002_AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.LuceneDev1002_AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Design"; - - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + public override ImmutableArray SupportedDiagnostics => [Descriptors.LuceneDev1002_FloatingPointArithmetic]; public override void Initialize(AnalysisContext context) { @@ -70,7 +73,7 @@ private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) // } - context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), context.Node.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1002_FloatingPointArithmetic, context.Node.GetLocation(), context.Node.ToString())); } } } diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs similarity index 57% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs index 56ef9af..5c4f6a0 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs @@ -1,4 +1,24 @@ -using Microsoft.CodeAnalysis; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -9,18 +29,7 @@ namespace Lucene.Net.CodeAnalysis.Dev [DiagnosticAnalyzer(LanguageNames.CSharp)] public class LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "LuceneDev1003"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LuceneDev1003_AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LuceneDev1003_AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.LuceneDev1003_AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Design"; - - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + public override ImmutableArray SupportedDiagnostics => [Descriptors.LuceneDev1003_ArrayMethodParameter]; public override void Initialize(AnalysisContext context) { @@ -43,7 +52,7 @@ private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) if (arrayTypeSyntax.ElementType is PredefinedTypeSyntax predefinedTypeSyntax) { if (predefinedTypeSyntax.Keyword.ValueText != "char") - context.ReportDiagnostic(Diagnostic.Create(Rule, parameter.GetLocation(), parameter.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1003_ArrayMethodParameter, parameter.GetLocation(), parameter.ToString())); } } } diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs similarity index 52% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs index c3e1a76..f660905 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -1,32 +1,35 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Reflection.Metadata; -using System.Threading; namespace Lucene.Net.CodeAnalysis.Dev { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "LuceneDev1004"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.LuceneDev1004_AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.LuceneDev1004_AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.LuceneDev1004_AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Design"; - - private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + public override ImmutableArray SupportedDiagnostics => [Descriptors.LuceneDev1004_ArrayMethodReturnValue]; public override void Initialize(AnalysisContext context) { @@ -47,7 +50,7 @@ private static void AnalyzeNodeCS(SyntaxNodeAnalysisContext context) if (arrayTypeSyntax.ElementType is PredefinedTypeSyntax predefinedTypeSyntax) { if (predefinedTypeSyntax.Keyword.ValueText != "char") - context.ReportDiagnostic(Diagnostic.Create(Rule, arrayTypeSyntax.GetLocation(), arrayTypeSyntax.ToString())); + context.ReportDiagnostic(Diagnostic.Create(Descriptors.LuceneDev1004_ArrayMethodReturnValue, arrayTypeSyntax.GetLocation(), arrayTypeSyntax.ToString())); } } } diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx index 9e476de..d61ab13 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx +++ b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx @@ -1,17 +1,37 @@ + + + - + --> diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Category.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Category.cs new file mode 100644 index 0000000..21b6be6 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Category.cs @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lucene.Net.CodeAnalysis.Dev.Utility +{ + public enum Category + { + Design, + Globalization, + Mobility, + Performance, + Security, + Usage, + Naming, + Interoperability, + Maintainability, + Reliability, + Documentation, + } +} diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs new file mode 100644 index 0000000..471a667 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Microsoft.CodeAnalysis; +using static Microsoft.CodeAnalysis.DiagnosticSeverity; +using static Lucene.Net.CodeAnalysis.Dev.Utility.Category; + +namespace Lucene.Net.CodeAnalysis.Dev.Utility +{ + public static partial class Descriptors + { + // IMPORTANT: Do not make these into properties! The AnalyzerReleases release management + // analyzers do not recognize them and will report RS2002 warnings if it cannot read the + // DignosticDescriptor instance through a field. + + public static readonly DiagnosticDescriptor LuceneDev1000_FloatingPointEquality = + Diagnostic( + "LuceneDev1000", + Design, + Warning + ); + + public static readonly DiagnosticDescriptor LuceneDev1001_FloatingPointFormatting = + Diagnostic( + "LuceneDev1001", + Design, + Warning + ); + + public static readonly DiagnosticDescriptor LuceneDev1002_FloatingPointArithmetic = + Diagnostic( + "LuceneDev1002", + Design, + Warning + ); + + public static readonly DiagnosticDescriptor LuceneDev1003_ArrayMethodParameter = + Diagnostic( + "LuceneDev1003", + Design, + Warning + ); + + public static readonly DiagnosticDescriptor LuceneDev1004_ArrayMethodReturnValue = + Diagnostic( + "LuceneDev1004", + Design, + Warning + ); + } +} diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.cs new file mode 100644 index 0000000..629c56c --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.cs @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using Microsoft.CodeAnalysis; +using System.Collections.Concurrent; + +namespace Lucene.Net.CodeAnalysis.Dev.Utility +{ + public static partial class Descriptors + { + static readonly ConcurrentDictionary categoryMapping = new(); + + static DiagnosticDescriptor Diagnostic( + string id, + Category category, + DiagnosticSeverity defaultSeverity) + => Diagnostic(id, category, defaultSeverity, isEnabledByDefault: true); + + static DiagnosticDescriptor Diagnostic( + string id, + Category category, + DiagnosticSeverity defaultSeverity, + bool isEnabledByDefault) + { + //string? helpLink = null; + var categoryString = categoryMapping.GetOrAdd(category, c => c.ToString()); + + var title = new LocalizableResourceString($"{id}_AnalyzerTitle", Resources.ResourceManager, typeof(Resources)); + var messageFormat = new LocalizableResourceString($"{id}_AnalyzerMessageFormat", Resources.ResourceManager, typeof(Resources)); + var description = new LocalizableResourceString($"{id}_AnalyzerDescription", Resources.ResourceManager, typeof(Resources)); + + //return new DiagnosticDescriptor(id, title, messageFormat, categoryString, defaultSeverity, isEnabledByDefault: true, helpLinkUri: helpLink); + return new DiagnosticDescriptor(id, title, messageFormat, categoryString, defaultSeverity, isEnabledByDefault); + } + } +} diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Helpers/FloatingPoint.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/FloatingPoint.cs similarity index 59% rename from src/Lucene.Net.CodeAnalysis.Dev/Helpers/FloatingPoint.cs rename to src/Lucene.Net.CodeAnalysis.Dev/Utility/FloatingPoint.cs index 15038f0..f9fbe97 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Helpers/FloatingPoint.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/FloatingPoint.cs @@ -1,6 +1,25 @@ -using Microsoft.CodeAnalysis; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ -namespace Lucene.Net.CodeAnalysis.Dev.Helpers +using Microsoft.CodeAnalysis; + +namespace Lucene.Net.CodeAnalysis.Dev.Utility { internal static class FloatingPoint { diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..e4914c2 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,28 @@ + + + + + + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets new file mode 100644 index 0000000..4b8366d --- /dev/null +++ b/tests/Directory.Build.targets @@ -0,0 +1,35 @@ + + + + + + + + + + + + + true + + + diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj new file mode 100644 index 0000000..9a30a90 --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj @@ -0,0 +1,41 @@ + + + + + + + net8.0 + enable + enable + true + Lucene.Net.CodeAnalysis.Dev.CodeFixes + + + + + + + + + + + + + diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev1xxx/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev1xxx/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs new file mode 100644 index 0000000..bf1ddbb --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/LuceneDev1xxx/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.TestUtilities; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; + +namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes +{ + public class TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider + { + [Test] + public async Task TestFix_Float_ToString() + { + var testCode = @" +using System; +using System.Globalization; + +// Mock for J2N.Numerics so we don't need to reference it +namespace J2N.Numerics +{ + public static class Single + { + public static string ToString(float f, IFormatProvider provider) => string.Empty; + } + + public static class Double + { + public static string ToString(double d, IFormatProvider provider) => string.Empty; + } +} + +public class MyClass +{ + private readonly float float1 = 1f; + + public void MyMethod() + { + string result = float1.ToString(CultureInfo.InvariantCulture); + } +}"; + + var fixedCode = @" +using System; +using System.Globalization; + +// Mock for J2N.Numerics so we don't need to reference it +namespace J2N.Numerics +{ + public static class Single + { + public static string ToString(float f, IFormatProvider provider) => string.Empty; + } + + public static class Double + { + public static string ToString(double d, IFormatProvider provider) => string.Empty; + } +} + +public class MyClass +{ + private readonly float float1 = 1f; + + public void MyMethod() + { + string result = J2N.Numerics.Single.ToString(float1, CultureInfo.InvariantCulture); + } +}"; + + var expected = new DiagnosticResult(Descriptors.LuceneDev1001_FloatingPointFormatting) + .WithSeverity(DiagnosticSeverity.Warning) + .WithMessageFormat(Descriptors.LuceneDev1001_FloatingPointFormatting.MessageFormat) + .WithArguments("float1.ToString") + .WithLocation("/0/Test0.cs", line: 25, column: 25); + + var test = new InjectableCodeFixTest( + () => new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer(), + () => new LuceneDev1001_FloatingPointFormattingCSCodeFixProvider()) + { + TestCode = testCode, + FixedCode = fixedCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestFix_Double_ToString() + { + var testCode = @" +using System; +using System.Globalization; + +// Mock for J2N.Numerics so we don't need to reference it +namespace J2N.Numerics +{ + public static class Single + { + public static string ToString(float f, IFormatProvider provider) => string.Empty; + } + + public static class Double + { + public static string ToString(double d, IFormatProvider provider) => string.Empty; + } +} + +public class MyClass +{ + private readonly double double1 = 1.0; + + public void MyMethod() + { + string result = double1.ToString(CultureInfo.InvariantCulture); + } +}"; + + var fixedCode = @" +using System; +using System.Globalization; + +// Mock for J2N.Numerics so we don't need to reference it +namespace J2N.Numerics +{ + public static class Single + { + public static string ToString(float f, IFormatProvider provider) => string.Empty; + } + + public static class Double + { + public static string ToString(double d, IFormatProvider provider) => string.Empty; + } +} + +public class MyClass +{ + private readonly double double1 = 1.0; + + public void MyMethod() + { + string result = J2N.Numerics.Double.ToString(double1, CultureInfo.InvariantCulture); + } +}"; + + var expected = new DiagnosticResult(Descriptors.LuceneDev1001_FloatingPointFormatting) + .WithSeverity(DiagnosticSeverity.Warning) + .WithMessageFormat(Descriptors.LuceneDev1001_FloatingPointFormatting.MessageFormat) + .WithArguments("double1.ToString") + .WithLocation("/0/Test0.cs", line: 25, column: 25); + + var test = new InjectableCodeFixTest( + () => new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer(), + () => new LuceneDev1001_FloatingPointFormattingCSCodeFixProvider()) + { + TestCode = testCode, + FixedCode = fixedCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableAnalyzerTest.cs b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableAnalyzerTest.cs new file mode 100644 index 0000000..37ca8ef --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableAnalyzerTest.cs @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Lucene.Net.CodeAnalysis.Dev.TestUtilities +{ + public class InjectableCSharpAnalyzerTest : AnalyzerTest + { + private readonly Func analyzerFactory; + + public InjectableCSharpAnalyzerTest(Func analyzerFactory) + { + this.analyzerFactory = analyzerFactory ?? throw new ArgumentNullException(nameof(analyzerFactory)); + } + + public override string Language => LanguageNames.CSharp; + + protected override string DefaultFileExt => "cs"; + + protected override CompilationOptions CreateCompilationOptions() + { + return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + } + + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Diagnose); + } + + protected override IEnumerable GetDiagnosticAnalyzers() + { + yield return analyzerFactory(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableCodeFixTest.cs b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableCodeFixTest.cs new file mode 100644 index 0000000..8664b0c --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableCodeFixTest.cs @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Lucene.Net.CodeAnalysis.Dev.TestUtilities +{ + public class InjectableCodeFixTest : CodeFixTest + { + private readonly Func analyzerFactory; + private readonly Func codeFixFactory; + + public InjectableCodeFixTest(Func analyzerFactory, Func codeFixFactory) + { + this.analyzerFactory = analyzerFactory ?? throw new ArgumentNullException(nameof(analyzerFactory)); + this.codeFixFactory = codeFixFactory ?? throw new ArgumentNullException(nameof(codeFixFactory)); + } + + public override Type SyntaxKindType => typeof(SyntaxKind); + + public override string Language => LanguageNames.CSharp; + + protected override string DefaultFileExt => "cs"; + + protected override CompilationOptions CreateCompilationOptions() + { + return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + } + + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Diagnose); + } + + protected override IEnumerable GetCodeFixProviders() + { + yield return codeFixFactory(); + } + + protected override IEnumerable GetDiagnosticAnalyzers() + { + yield return analyzerFactory(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Lucene.Net.CodeAnalysis.Dev.TestUtilities.csproj b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Lucene.Net.CodeAnalysis.Dev.TestUtilities.csproj new file mode 100644 index 0000000..a9432c9 --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Lucene.Net.CodeAnalysis.Dev.TestUtilities.csproj @@ -0,0 +1,36 @@ + + + + + + + net8.0 + enable + enable + + + + + + + + + + diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Verifier.cs b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Verifier.cs new file mode 100644 index 0000000..7b45bcf --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Verifier.cs @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace Lucene.Net.CodeAnalysis.Dev.TestUtilities +{ + public class Verifier : IVerifier + { + public Verifier() + : this(ImmutableStack.Empty) + { + } + + public Verifier(ImmutableStack context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + protected ImmutableStack Context { get; } + + public virtual void Empty(string collectionName, IEnumerable collection) + { + Assert.That(collection, Is.Empty, CreateMessage($"Expected '{collectionName}' to be empty, contains '{collection?.Count()}' elements")); + } + + public virtual void Equal(T expected, T actual, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.That(actual, Is.EqualTo(expected)); + } + else + { + Assert.That(actual, Is.EqualTo(expected), CreateMessage(message!)); + } + } + + public virtual void True([DoesNotReturnIf(false)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.That(assert); + } + else + { + Assert.That(assert, CreateMessage(message!)); + } + } + + public virtual void False([DoesNotReturnIf(true)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.That(assert, Is.False); + } + else + { + Assert.That(assert, Is.False, CreateMessage(message!)); + } + } + + [DoesNotReturn] + public virtual void Fail(string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.Fail(); + } + else + { + Assert.Fail(CreateMessage(message!)); + } + + throw new InvalidOperationException("This program location is thought to be unreachable."); + } + + public virtual void LanguageIsSupported(string language) + { + Assert.That(language != LanguageNames.CSharp && language != LanguageNames.VisualBasic, Is.False, CreateMessage($"Unsupported Language: '{language}'")); + } + + public virtual void NotEmpty(string collectionName, IEnumerable collection) + { + Assert.That(collection, Is.Not.Empty, CreateMessage($"expected '{collectionName}' to be non-empty, contains")); + } + + public virtual void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null) + { + var comparer = new SequenceEqualEnumerableEqualityComparer(equalityComparer); + var areEqual = comparer.Equals(expected, actual); + if (!areEqual) + { + Assert.Fail(CreateMessage(message!)); + } + } + + public virtual IVerifier PushContext(string context) + { + Assert.That(GetType(), Is.EqualTo(typeof(Verifier))); + return new Verifier(Context.Push(context)); + } + + protected virtual string CreateMessage(string? message) + { + foreach (var frame in Context) + { + message = "Context: " + frame + Environment.NewLine + message; + } + + return message ?? string.Empty; + } + + private sealed class SequenceEqualEnumerableEqualityComparer : IEqualityComparer> + { + private readonly IEqualityComparer _itemEqualityComparer; + + public SequenceEqualEnumerableEqualityComparer(IEqualityComparer? itemEqualityComparer) + { + _itemEqualityComparer = itemEqualityComparer ?? EqualityComparer.Default; + } + + public bool Equals(IEnumerable? x, IEnumerable? y) + { + if (ReferenceEquals(x, y)) { return true; } + if (x is null || y is null) { return false; } + + return x.SequenceEqual(y, _itemEqualityComparer); + } + + public int GetHashCode(IEnumerable obj) + { + if (obj is null) + { + return 0; + } + + // From System.Tuple + // + // The suppression is required due to an invalid contract in IEqualityComparer + // https://github.com/dotnet/runtime/issues/30998 + return obj + .Select(item => _itemEqualityComparer.GetHashCode(item!)) + .Aggregate( + 0, + (aggHash, nextHash) => (aggHash << 5) + aggHash ^ nextHash); + } + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/CodeFixVerifier.Helper.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/CodeFixVerifier.Helper.cs deleted file mode 100644 index 328d485..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/CodeFixVerifier.Helper.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace TestHelper -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /// - /// Diagnostic Producer class with extra methods dealing with applying codefixes - /// All methods are static - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Apply the inputted CodeAction to the inputted document. - /// Meant to be used to apply codefixes. - /// - /// The Document to apply the fix on - /// A CodeAction that will be applied to the Document. - /// A Document with the changes from the CodeAction - private static Document ApplyFix(Document document, CodeAction codeAction) - { - var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result; - var solution = operations.OfType().Single().ChangedSolution; - return solution.GetDocument(document.Id); - } - - /// - /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection. - /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, - /// this method may not necessarily return the new one. - /// - /// The Diagnostics that existed in the code before the CodeFix was applied - /// The Diagnostics that exist in the code after the CodeFix was applied - /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied - private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) - { - var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - - int oldIndex = 0; - int newIndex = 0; - - while (newIndex < newArray.Length) - { - if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) - { - ++oldIndex; - ++newIndex; - } - else - { - yield return newArray[newIndex++]; - } - } - } - - /// - /// Get the existing compiler diagnostics on the inputted document. - /// - /// The Document to run the compiler diagnostic analyzers on - /// The compiler diagnostics that were found in the code - private static IEnumerable GetCompilerDiagnostics(Document document) - { - return document.GetSemanticModelAsync().Result.GetDiagnostics(); - } - - /// - /// Given a document, turn it into a string based on the syntax root - /// - /// The Document to be converted to a string - /// A string containing the syntax of the Document after formatting - private static string GetStringFromDocument(Document document) - { - var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result; - var root = simplifiedDoc.GetSyntaxRootAsync().Result; - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); - return root.GetText().ToString(); - } - } -} - diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticResult.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticResult.cs deleted file mode 100644 index 605e0ba..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticResult.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Microsoft.CodeAnalysis; -using System; -using System.Diagnostics.CodeAnalysis; - -namespace TestHelper -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. - /// - [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Used for testing")] - public struct DiagnosticResultLocation - { - public DiagnosticResultLocation(string path, int line, int column) - { - if (line < -1) - { - throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - } - - if (column < -1) - { - throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); - } - - this.Path = path; - this.Line = line; - this.Column = column; - } - - public string Path { get; } - public int Line { get; } - public int Column { get; } - } - - /// - /// Struct that stores information about a Diagnostic appearing in a source - /// - [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Used for testing")] - public struct DiagnosticResult - { - private DiagnosticResultLocation[] locations; - - [SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Used for testing")] - public DiagnosticResultLocation[] Locations - { - get - { - if (this.locations == null) - { - this.locations = Array.Empty(); - } - return this.locations; - } - set => this.locations = value; - } - - public DiagnosticSeverity Severity { get; set; } - - public string Id { get; set; } - - public string Message { get; set; } - - public string Path => this.Locations.Length > 0 ? this.Locations[0].Path : ""; - - public int Line => this.Locations.Length > 0 ? this.Locations[0].Line : -1; - - public int Column => this.Locations.Length > 0 ? this.Locations[0].Column : -1; - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticVerifier.Helper.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticVerifier.Helper.cs deleted file mode 100644 index 893c55b..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticVerifier.Helper.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; - -namespace TestHelper -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /// - /// Class for turning strings into documents and getting the diagnostics on them - /// All methods are static - /// - public abstract partial class DiagnosticVerifier - { - private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); - private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); - - internal static string DefaultFilePathPrefix = "Test"; - internal static string CSharpDefaultFileExt = "cs"; - internal static string VisualBasicDefaultExt = "vb"; - internal static string TestProjectName = "TestProject"; - - #region Get Diagnostics - - /// - /// Given classes in the form of strings, their language, and an IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string after converting it to a document. - /// - /// Classes in the form of strings - /// The language the source classes are in - /// The analyzer to be run on the sources - /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) - { - return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); - } - - /// - /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. - /// The returned diagnostics are then ordered by location in the source document. - /// - /// The analyzer to run on the documents - /// The Documents that the analyzer will be run on - /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) - { - var projects = new HashSet(); - foreach (var document in documents) - { - projects.Add(document.Project); - } - - var diagnostics = new List(); - foreach (var project in projects) - { - var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); - var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; - foreach (var diag in diags) - { - if (diag.Location == Location.None || diag.Location.IsInMetadata) - { - diagnostics.Add(diag); - } - else - { - for (int i = 0; i < documents.Length; i++) - { - var document = documents[i]; - var tree = document.GetSyntaxTreeAsync().Result; - if (tree == diag.Location.SourceTree) - { - diagnostics.Add(diag); - } - } - } - } - } - - var results = SortDiagnostics(diagnostics); - diagnostics.Clear(); - return results; - } - - /// - /// Sort diagnostics by location in source document - /// - /// The list of Diagnostics to be sorted - /// An IEnumerable containing the Diagnostics in order of Location - private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) - { - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - } - - #endregion - - #region Set up compilation and documents - /// - /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. - /// - /// Classes in the form of strings - /// The language the source code is in - /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant - private static Document[] GetDocuments(string[] sources, string language) - { - if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) - { - throw new ArgumentException("Unsupported Language"); - } - - var project = CreateProject(sources, language); - var documents = project.Documents.ToArray(); - - if (sources.Length != documents.Length) - { - throw new InvalidOperationException("Amount of sources did not match amount of Documents created"); - } - - return documents; - } - - /// - /// Create a Document from a string through creating a project that contains it. - /// - /// Classes in the form of a string - /// The language the source code is in - /// A Document created from the source string - protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) - { - return CreateProject(new[] { source }, language).Documents.First(); - } - - /// - /// Create a project using the inputted strings as sources. - /// - /// Classes in the form of strings - /// The language the source code is in - /// A Project created out of the Documents created from the source strings - private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) - { - string fileNamePrefix = DefaultFilePathPrefix; - string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); - - var solution = new AdhocWorkspace() - .CurrentSolution - .AddProject(projectId, TestProjectName, TestProjectName, language) - .AddMetadataReference(projectId, CorlibReference) - .AddMetadataReference(projectId, SystemCoreReference) - .AddMetadataReference(projectId, CSharpSymbolsReference) - .AddMetadataReference(projectId, CodeAnalysisReference); - - int count = 0; - foreach (var source in sources) - { - var newFileName = fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); - count++; - } - return solution.GetProject(projectId); - } - #endregion - } -} - diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Lucene.Net.CodeAnalysis.Dev.Tests.csproj b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Lucene.Net.CodeAnalysis.Dev.Tests.csproj index 77b6980..69570fd 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Lucene.Net.CodeAnalysis.Dev.Tests.csproj +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Lucene.Net.CodeAnalysis.Dev.Tests.csproj @@ -1,19 +1,40 @@ + + + net8.0 + enable true + Lucene.Net.CodeAnalysis.Dev - - - - + + + diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs new file mode 100644 index 0000000..99753cc --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.TestUtilities; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev +{ + public class TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer + { + //No diagnostics expected to show up + [Test] + public async Task TestEmptyFile() + { + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_LessThan() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + } + public void MyMethod(int n) + { + } + protected internal bool LessThan(float termA, float termB) + { + return termA < termB; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("termA < termB") + .WithLocation("/0/Test0.cs", line: 19, column: 24); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_EqualTo() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + } + public void MyMethod(int n) + { + } + protected internal bool LessThan(float termA, float termB) + { + return termA == termB; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("termA == termB") + .WithLocation("/0/Test0.cs", line: 19, column: 24); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_GreaterThan() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + } + public void MyMethod(int n) + { + } + protected internal bool LessThan(float termA, float termB) + { + return termA > termB; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("termA > termB") + .WithLocation("/0/Test0.cs", line: 19, column: 24); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_LessThanOrEqualTo() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + } + public void MyMethod(int n) + { + } + protected internal bool LessThan(float termA, float termB) + { + return termA <= termB; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("termA <= termB") + .WithLocation("/0/Test0.cs", line: 19, column: 24); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_GreaterThanOrEqualTo() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + } + public void MyMethod(int n) + { + } + protected internal bool LessThan(float termA, float termB) + { + return termA >= termB; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("termA >= termB") + .WithLocation("/0/Test0.cs", line: 19, column: 24); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_EqualTo_MemberVariable() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + private readonly float myFloat1 = 1f; + private readonly float myFloat2 = 1f; + + public void MyMethod() + { + var x = myFloat1 == myFloat2; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("myFloat1 == myFloat2") + .WithLocation("/0/Test0.cs", line: 16, column: 25); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_EqualTo_LocalVariable() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + float a = 1f; + float b = 1f; + var x = a == b; + } + } + "; + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("a == b") + .WithLocation("/0/Test0.cs", line: 15, column: 25); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_EqualTo_PropertyOfParameter() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod(Term a, Term b) + { + var x = a.Score == b.Score; + } + } + public class Term + { + public float Score { get; set; } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("a.Score == b.Score") + .WithLocation("/0/Test0.cs", line: 13, column: 25); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_Equals_LocalVariable() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public void MyMethod() + { + float a = 1f; + float b = 1f; + var x = a.Equals(b); + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1000_FloatingPointEquality.Id) + .WithMessageFormat(Descriptors.LuceneDev1000_FloatingPointEquality.MessageFormat) + .WithArguments("a.Equals") + .WithLocation("/0/Test0.cs", line: 15, column: 25); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs new file mode 100644 index 0000000..a786e0d --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.TestUtilities; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev +{ + public class TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer + { + //No diagnostics expected to show up + [Test] + public async Task TestEmptyFile() + { + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_Float_ToString() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Diagnostics; + + public class MyClass + { + private readonly float float1 = 1f; + + public void MyMethod() + { + string result = float1.ToString(CultureInfo.InvariantCulture); + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1001_FloatingPointFormatting.Id) + .WithMessageFormat(Descriptors.LuceneDev1001_FloatingPointFormatting.MessageFormat) + .WithArguments("float1.ToString") + .WithLocation("/0/Test0.cs", line: 15, column: 33); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs new file mode 100644 index 0000000..ed1ce2b --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.TestUtilities; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev +{ + public class TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer + { + + //No diagnostics expected to show up + [Test] + public async Task TestEmptyFile() + { + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_Float_ToString() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + private readonly float float1 = 1f; + private readonly float float2 = 3.14f; + + public void MyMethod() + { + long foo = 33; + var result = ((double)float1 * (double)float2) / foo; + } + } + "; + + var expected1 = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1002_FloatingPointArithmetic.Id) + .WithMessageFormat(Descriptors.LuceneDev1002_FloatingPointArithmetic.MessageFormat) + .WithArguments("((double)float1 * (double)float2) / foo") + .WithLocation("/0/Test0.cs", line: 17, column: 30); + + var expected2 = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1002_FloatingPointArithmetic.Id) + .WithMessageFormat(Descriptors.LuceneDev1002_FloatingPointArithmetic.MessageFormat) + .WithArguments("(double)float1 * (double)float2") + .WithLocation("/0/Test0.cs", line: 17, column: 31); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected1, expected2 } + }; + + await test.RunAsync(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs new file mode 100644 index 0000000..beb09d7 --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.TestUtilities; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev +{ + public class TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer + { + //No diagnostics expected to show up + [Test] + public async Task TestEmptyFile() + { + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_ParseChar_String_Int32Array_Char() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public static bool ParseChar(string id, int[] pos, char ch) + { + int start = pos[0]; + //pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); + if (pos[0] == id.Length || + id[pos[0]] != ch) + { + pos[0] = start; + return false; + } + ++pos[0]; + return true; + } + } + "; + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1003_ArrayMethodParameter.Id) + .WithMessageFormat(Descriptors.LuceneDev1003_ArrayMethodParameter.MessageFormat) + .WithArguments("int[] pos") + .WithLocation("/0/Test0.cs", line: 11, column: 53); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_ParseChar_String_CharArray_Char() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public static bool ParseChar(string id, char[] pos, char ch) + { + char start = pos[0]; + //pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); + if (pos[0] == id.Length || + id[pos[0]] != ch) + { + pos[0] = start; + return false; + } + ++pos[0]; + return true; + } + } + "; + // We shouldn't trigger a warning on char[] + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs new file mode 100644 index 0000000..93513b6 --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/LuceneDev1xxx/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Lucene.Net.CodeAnalysis.Dev.TestUtilities; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Lucene.Net.CodeAnalysis.Dev +{ + public class TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer + { + + //No diagnostics expected to show up + [Test] + public async Task TestEmptyFile() + { + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_GetVersionByteArrayFromCompactInt32_ByteArrayReturnType() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public static byte[] GetVersionByteArrayFromCompactInt32(int version) // ICU4N specific - Renamed from GetVersionByteArrayFromCompactInt + { + return new byte[] { + (byte)(version >> 24), + (byte)(version >> 16), + (byte)(version >> 8), + (byte)(version) + }; + } + } + "; + + + var expected = DiagnosticResult.CompilerWarning(Descriptors.LuceneDev1004_ArrayMethodReturnValue.Id) + .WithMessageFormat(Descriptors.LuceneDev1004_ArrayMethodReturnValue.MessageFormat) + .WithArguments("byte[]") + .WithLocation("/0/Test0.cs", line: 11, column: 27); + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer()) + { + TestCode = testCode, + ExpectedDiagnostics = { expected } + }; + + await test.RunAsync(); + } + + [Test] + public async Task TestDiagnostic_GetVersionCharArrayFromCompactInt32_CharArrayReturnType() + { + var testCode = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + public class MyClass + { + public static char[] GetVersionCharArrayFromCompactInt32(int version) + { + return new char[] { + (char)(version >> 24), + (char)(version >> 16), + (char)(version >> 8), + (char)(version) + }; + } + } + "; + + // We shouldn't trigger a warning on char[] + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs deleted file mode 100644 index d730e83..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ /dev/null @@ -1,372 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using TestHelper; - -namespace Lucene.Net.CodeAnalysis.Dev.Tests -{ - public class TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer : DiagnosticVerifier - { - - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer(); - } - - //No diagnostics expected to show up - [Test] - public void TestEmptyFile() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - [Test] - public void TestDiagnostic_LessThan() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - } - public void MyMethod(int n) - { - } - protected internal override bool LessThan(float termA, float termB) - { - return termA < termB; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "termA < termB"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 19, 24) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_EqualTo() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - } - public void MyMethod(int n) - { - } - protected internal override bool LessThan(float termA, float termB) - { - return termA == termB; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "termA == termB"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 19, 24) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_GreaterThan() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - } - public void MyMethod(int n) - { - } - protected internal override bool LessThan(float termA, float termB) - { - return termA > termB; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "termA > termB"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 19, 24) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_LessThanOrEqualTo() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - } - public void MyMethod(int n) - { - } - protected internal override bool LessThan(float termA, float termB) - { - return termA <= termB; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "termA <= termB"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 19, 24) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_GreaterThanOrEqualTo() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - } - public void MyMethod(int n) - { - } - protected internal override bool LessThan(float termA, float termB) - { - return termA >= termB; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "termA >= termB"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 19, 24) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_EqualTo_MemberVariable() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - private readonly float myFloat1 = 1f; - private readonly float myFloat2 = 1f; - - public void MyMethod() - { - var x = myFloat1 == myFloat2; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "myFloat1 == myFloat2"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 16, 25) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_EqualTo_LocalVariable() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - float a = 1f; - float b = 1f; - var x = a == b; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "a == b"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 15, 25) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_EqualTo_PropertyOfParameter() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod(Term a, Term b) - { - var x = a.Score == b.Score; - } - } - public class Term - { - public float Score { get; set; } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "a.Score == b.Score"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 13, 25) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_Equals_LocalVariable() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public void MyMethod() - { - float a = 1f; - float b = 1f; - var x = a.Equals(b); - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to JIT optimizations. Floating point types should not be compared for exact equality.", "a.Equals"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 15, 25) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs deleted file mode 100644 index 3d51da5..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using TestHelper; - -namespace Lucene.Net.CodeAnalysis.Dev.Tests -{ - public class TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer : DiagnosticVerifier - { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer(); - } - - //No diagnostics expected to show up - [Test] - public void TestEmptyFile() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - [Test] - public void TestDiagnostic_Float_ToString() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - private readonly float float1 = 1f; - - public void MyMethod() - { - string result = float1.ToString(CultureInfo.InvariantCulture); - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' may fail due to floating point precision issues on .NET Framework and .NET Core prior to version 3.0. Floating point types should be formatted with J2N.Numerics.Single.ToString() or J2N.Numerics.Double.ToString().", "float1.ToString"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 15, 33) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs deleted file mode 100644 index 7cc960a..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using TestHelper; - -namespace Lucene.Net.CodeAnalysis.Dev.Tests -{ - public class TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer : DiagnosticVerifier - { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer(); - } - - //No diagnostics expected to show up - [Test] - public void TestEmptyFile() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - [Test] - public void TestDiagnostic_Float_ToString() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - private readonly float float1 = 1f; - private readonly float float2 = 3.14f; - - public void MyMethod() - { - long foo = 33; - var result = ((double)float1 * (double)float2)) / foo; - } - } - "; - - var expected1 = new DiagnosticResult - { - Id = LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.DiagnosticId, - Message = string.Format( - "'{0}' may fail due to floating point precision issues on .NET Framework and .NET Core prior to version 3.0. Floating point type arithmetic needs to be checked on x86 in .NET Framework and may require extra casting.", - "(double)float1 * (double)float2"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] - { - new DiagnosticResultLocation("Test0.cs", 17, 31) - } - }; - - var expected2 = new DiagnosticResult - { - Id = LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.DiagnosticId, - Message = string.Format( - "'{0}' may fail due to floating point precision issues on .NET Framework and .NET Core prior to version 3.0. Floating point type arithmetic needs to be checked on x86 in .NET Framework and may require extra casting.", - "/ foo"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] - { - new DiagnosticResultLocation("Test0.cs", 17, 65) - } - }; - - VerifyCSharpDiagnostic(test, expected1, expected2); - } - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs deleted file mode 100644 index a16931f..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using TestHelper; - -namespace Lucene.Net.CodeAnalysis.Dev.Tests -{ - public class TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer : DiagnosticVerifier - { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer(); - } - - //No diagnostics expected to show up - [Test] - public void TestEmptyFile() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - [Test] - public void TestDiagnostic_ParseChar_String_Int32Array_Char() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public static bool ParseChar(string id, int[] pos, char ch) - { - int start = pos[0]; - pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); - if (pos[0] == id.Length || - id[pos[0]] != ch) - { - pos[0] = start; - return false; - } - ++pos[0]; - return true; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' needs to be analyzed to determine whether the array can be replaced with a ref or out parameter", "int[] pos"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 11, 53) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_ParseChar_String_CharArray_Char() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public static bool ParseChar(string id, char[] pos, char ch) - { - int start = pos[0]; - pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); - if (pos[0] == id.Length || - id[pos[0]] != ch) - { - pos[0] = start; - return false; - } - ++pos[0]; - return true; - } - } - "; - - // We shouldn't trigger a warning on char[] - VerifyCSharpDiagnostic(test); - } - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs deleted file mode 100644 index 71048a8..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using TestHelper; - -namespace Lucene.Net.CodeAnalysis.Dev.Tests -{ - public class TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer : DiagnosticVerifier - { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer(); - } - - //No diagnostics expected to show up - [Test] - public void TestEmptyFile() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - [Test] - public void TestDiagnostic_GetVersionByteArrayFromCompactInt32_ByteArrayReturnType() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public static byte[] GetVersionByteArrayFromCompactInt32(int version) // ICU4N specific - Renamed from GetVersionByteArrayFromCompactInt - { - return new byte[] { - (byte)(version >> 24), - (byte)(version >> 16), - (byte)(version >> 8), - (byte)(version) - }; - } - } - "; - - var expected = new DiagnosticResult - { - Id = LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.DiagnosticId, - Message = string.Format("'{0}' return type needs to be analyzed to determine whether the array return value can be replaced with one or more out parameters or a return ValueTuple instead of an array to avoid the heap allocation", "byte[]"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 11, 27) - } - }; - - VerifyCSharpDiagnostic(test, expected); - } - - [Test] - public void TestDiagnostic_GetVersionCharArrayFromCompactInt32_CharArrayReturnType() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - public class MyClass - { - public static char[] GetVersionCharArrayFromCompactInt32(int version) - { - return new char[] { - (char)(version >> 24), - (char)(version >> 16), - (char)(version >> 8), - (char)(version) - }; - } - } - "; - - // We shouldn't trigger a warning on char[] - VerifyCSharpDiagnostic(test); - } - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/CodeFixVerifier.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/CodeFixVerifier.cs deleted file mode 100644 index 7bfd9a5..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/CodeFixVerifier.cs +++ /dev/null @@ -1,144 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Formatting; -using NUnit.Framework; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace TestHelper -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /// - /// Superclass of all Unit tests made for diagnostics with codefixes. - /// Contains methods used to verify correctness of codefixes - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Returns the codefix being tested (C#) - to be implemented in non-abstract class - /// - /// The CodeFixProvider to be used for CSharp code - protected virtual CodeFixProvider GetCSharpCodeFixProvider() - { - return null; - } - - /// - /// Returns the codefix being tested (VB) - to be implemented in non-abstract class - /// - /// The CodeFixProvider to be used for VisualBasic code - protected virtual CodeFixProvider GetBasicCodeFixProvider() - { - return null; - } - - /// - /// Called to test a C# codefix when applied on the inputted string as a source - /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } - - /// - /// Called to test a VB codefix when applied on the inputted string as a source - /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } - - /// - /// General verifier for codefixes. - /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. - /// Then gets the string after the codefix is applied and compares it with the expected result. - /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. - /// - /// The language the source code is in - /// The analyzer to be applied to the source code - /// The codefix to be applied to the code wherever the relevant Diagnostic is found - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - private static void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) - { - var document = CreateDocument(oldSource, language); - var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); - var compilerDiagnostics = GetCompilerDiagnostics(document); - var attempts = analyzerDiagnostics.Length; - - for (int i = 0; i < attempts; ++i) - { - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); - codeFixProvider.RegisterCodeFixesAsync(context).Wait(); - - if (!actions.Any()) - { - break; - } - - if (codeFixIndex != null) - { - document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); - break; - } - - document = ApplyFix(document, actions.ElementAt(0)); - analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); - - var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); - - //check if applying the code fix introduced any new compiler diagnostics - if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) - { - // Format and get the compiler diagnostics again so that the locations make sense in the output - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); - - Assert.Fail(string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", - string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), - document.GetSyntaxRootAsync().Result.ToFullString())); - } - - //check if there are analyzer diagnostics left after the code fix - if (!analyzerDiagnostics.Any()) - { - break; - } - } - - //after applying all of the code fixes, compare the resulting string to the inputted one - var actual = GetStringFromDocument(document); - Assert.That(actual, Is.EqualTo(newSource)); - } - } -} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/DiagnosticVerifier.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/DiagnosticVerifier.cs deleted file mode 100644 index 60be575..0000000 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/DiagnosticVerifier.cs +++ /dev/null @@ -1,280 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using NUnit.Framework; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TestHelper -{ - /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - /// - /// Superclass of all Unit Tests for DiagnosticAnalyzers - /// - [TestFixture] - public abstract partial class DiagnosticVerifier - { - #region To be implemented by Test classes - /// - /// Get the CSharp analyzer being tested - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return null; - } - - /// - /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() - { - return null; - } - #endregion - - #region Verifier wrappers - - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } - - /// - /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, - /// then verifies each of them. - /// - /// An array of strings to create source documents from to run the analyzers on - /// The language of the classes represented by the source strings - /// The analyzer to be run on the source code - /// DiagnosticResults that should appear after the analyzer is run on the sources - private static void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) - { - var diagnostics = GetSortedDiagnostics(sources, language, analyzer); - VerifyDiagnosticResults(diagnostics, analyzer, expected); - } - - #endregion - - #region Actual comparisons and verifications - /// - /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. - /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. - /// - /// The Diagnostics found by the compiler after running the analyzer on the source code - /// The analyzer that was being run on the sources - /// Diagnostic Results that should have appeared in the code - private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) - { - int expectedCount = expectedResults.Count(); - int actualCount = actualResults.Count(); - - if (expectedCount != actualCount) - { - string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; - - Assert.Fail(string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); - } - - for (int i = 0; i < expectedResults.Length; i++) - { - var actual = actualResults.ElementAt(i); - var expected = expectedResults[i]; - - if (expected.Line == -1 && expected.Column == -1) - { - if (actual.Location != Location.None) - { - Assert.Fail(string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", - FormatDiagnostics(analyzer, actual))); - } - } - else - { - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); - var additionalLocations = actual.AdditionalLocations.ToArray(); - - if (additionalLocations.Length != expected.Locations.Length - 1) - { - Assert.Fail(string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", - expected.Locations.Length - 1, additionalLocations.Length, - FormatDiagnostics(analyzer, actual))); - } - - for (int j = 0; j < additionalLocations.Length; ++j) - { - VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); - } - } - - if (actual.Id != expected.Id) - { - Assert.Fail(string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); - } - - if (actual.Severity != expected.Severity) - { - Assert.Fail(string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); - } - - if (actual.GetMessage() != expected.Message) - { - Assert.Fail(string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); - } - } - } - - /// - /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. - /// - /// The analyzer that was being run on the sources - /// The diagnostic that was found in the code - /// The Location of the Diagnostic found in the code - /// The DiagnosticResultLocation that should have been found - private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) - { - var actualSpan = actual.GetLineSpan(); - - Assert.That(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), - string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); - - var actualLinePosition = actualSpan.StartLinePosition; - - // Only check line position if there is an actual line in the real diagnostic - if (actualLinePosition.Line > 0) - { - if (actualLinePosition.Line + 1 != expected.Line) - { - Assert.Fail(string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } - - // Only check column position if there is an actual column position in the real diagnostic - if (actualLinePosition.Character > 0) - { - if (actualLinePosition.Character + 1 != expected.Column) - { - Assert.Fail(string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } - } - #endregion - - #region Formatting Diagnostics - /// - /// Helper method to format a Diagnostic into an easily readable string - /// - /// The analyzer that this verifier tests - /// The Diagnostics to be formatted - /// The Diagnostics formatted as a string - private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) - { - var builder = new StringBuilder(); - for (int i = 0; i < diagnostics.Length; ++i) - { - builder.AppendLine("// " + diagnostics[i].ToString()); - - var analyzerType = analyzer.GetType(); - var rules = analyzer.SupportedDiagnostics; - - foreach (var rule in rules) - { - if (rule != null && rule.Id == diagnostics[i].Id) - { - var location = diagnostics[i].Location; - if (location == Location.None) - { - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); - } - else - { - Assert.That(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); - - string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; - var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - rule.Id); - } - - if (i != diagnostics.Length - 1) - { - builder.Append(','); - } - - builder.AppendLine(); - break; - } - } - } - return builder.ToString(); - } - #endregion - } -} diff --git a/version.json b/version.json new file mode 100644 index 0000000..489db69 --- /dev/null +++ b/version.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "1.0.0-alpha.{height}", + "assemblyVersion": { + "precision": "major" + }, + "nuGetPackageVersion": { + "semVer": 2.0 + }, + "publicReleaseRefSpec": [ + "^refs/heads/main$", + "^refs/heads/master$", + "^refs/heads/release/v\\d+\\.\\d+\\.\\d+$" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + }, + "release": { + "branchName": "release/v{version}" + } +}