From 1026e0f55ff0a9fbf4a472525f2ed95f66fbf44a Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 15:15:24 +0700 Subject: [PATCH 01/44] Added Dirctory.Build.props, Directory.Build.targets. and Directory.Packages.props files to share common settings and enable Central Package Management. --- Directory.Build.props | 73 +++++++++++++++++++ Directory.Build.targets | 27 +++++++ Directory.Packages.props | 29 ++++++++ src/Directory.Build.props | 3 + src/Directory.Build.targets | 8 ++ .../Lucene.Net.CodeAnalysis.Dev.csproj | 32 +++++--- tests/Directory.Build.props | 8 ++ tests/Directory.Build.targets | 10 +++ .../Lucene.Net.CodeAnalysis.Dev.Tests.csproj | 5 +- 9 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets create mode 100644 Directory.Packages.props create mode 100644 src/Directory.Build.props create mode 100644 src/Directory.Build.targets create mode 100644 tests/Directory.Build.props create mode 100644 tests/Directory.Build.targets diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..837fe5c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,73 @@ + + + + $(MSBuildThisFileDirectory) + 12.0 + apache + lucenenet-codeanalysis-dev + + + + full + + + + false + + + + false + + + + false + + + + $(RepositoryRoot)Lucene.Net.snk + 002400000480000094000000060200000024000052534131000400000100010075a07ce602f88ef263c7db8cb342c58ebd49ecdcc210fac874260b0213fb929ac3dcaf4f5b39744b800f99073eca72aebfac5f7284e1d5f2c82012a804a140f06d7d043d83e830cdb606a04da2ad5374cc92c0a49508437802fb4f8fb80a05e59f80afb99f4ccd0dfe44065743543c4b053b669509d29d332cd32a0cb1e97e84 + true + + + + false + true + + + + Lucene.Net + The Apache Software Foundation + $([System.DateTime]::UtcNow.Year.ToString()) + 2025 + $(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..bffdf7e --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,27 @@ + + + + + + <_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..ae6b11f --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,29 @@ + + + true + true + + + + 4.14.0 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..8370f78 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..daccd8d --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + + + \ No newline at end of file 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..1963ee9 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj +++ b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj @@ -3,12 +3,20 @@ netstandard2.0 enable - latest + + + false + true true - true - false - true + + + + *$(MSBuildProjectFile)* + + true + true + true true @@ -20,11 +28,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -44,14 +52,14 @@ - - $(SolutionDir)Lucene.Net.snk - 002400000480000094000000060200000024000052534131000400000100010075a07ce602f88ef263c7db8cb342c58ebd49ecdcc210fac874260b0213fb929ac3dcaf4f5b39744b800f99073eca72aebfac5f7284e1d5f2c82012a804a140f06d7d043d83e830cdb606a04da2ad5374cc92c0a49508437802fb4f8fb80a05e59f80afb99f4ccd0dfe44065743543c4b053b669509d29d332cd32a0cb1e97e84 - true - - + + + + + + diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..dc40b88 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets new file mode 100644 index 0000000..049dc8c --- /dev/null +++ b/tests/Directory.Build.targets @@ -0,0 +1,10 @@ + + + + + + + + + + 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..0f6c00c 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 @@ -6,10 +6,7 @@ - - - - + From ed3c30dd0a7eff0ffd1f56b548e1a1bb61ac7841 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 16:23:48 +0700 Subject: [PATCH 02/44] Added version.json file for Nerdbank.GitVersioning --- version.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 version.json diff --git a/version.json b/version.json new file mode 100644 index 0000000..396a311 --- /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+)?$" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + }, + "release": { + "branchName": "release/v{version}" + } +} \ No newline at end of file From ab9abfbce294a4a9d1b764d342147e190131bca3 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 17:08:10 +0700 Subject: [PATCH 03/44] Added Lucene.Net.CodeAnalysis.Dev.Package project to provide centralized packaging for analyzers, code fixes, and the sample project --- Directory.Build.props | 2 +- LICENSE.txt | 2 +- Lucene.Net.CodeAnalysis.Dev.sln | 7 ++ NOTICE.txt | 5 + branding/lucene-net-icon-128x128.png | Bin 0 -> 5436 bytes eng/WildcardVersionSupport.targets | 68 +++++++++++ eng/nuget.props | 16 +++ src/Directory.Build.targets | 1 + ...Lucene.Net.CodeAnalysis.Dev.Package.csproj | 106 ++++++++++++++++++ .../Lucene.Net.CodeAnalysis.Dev.Sample.csproj | 100 +++++++++++++++-- .../Lucene.Net.CodeAnalysis.Dev.csproj | 18 +-- 11 files changed, 304 insertions(+), 21 deletions(-) create mode 100644 NOTICE.txt create mode 100644 branding/lucene-net-icon-128x128.png create mode 100644 eng/WildcardVersionSupport.targets create mode 100644 eng/nuget.props create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.Package/Lucene.Net.CodeAnalysis.Dev.Package.csproj diff --git a/Directory.Build.props b/Directory.Build.props index 837fe5c..507402a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,7 +38,7 @@ Lucene.Net The Apache Software Foundation $([System.DateTime]::UtcNow.Year.ToString()) - 2025 + 2023 $(BeginCopyrightYear) - $(CurrentYear) $(CurrentYear) Copyright © $(CopyrightYearRange) $(Company) 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.sln b/Lucene.Net.CodeAnalysis.Dev.sln index 170a989..04edacb 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -13,6 +13,8 @@ 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,10 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -39,6 +45,7 @@ Global {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} 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/branding/lucene-net-icon-128x128.png b/branding/lucene-net-icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..85cbdee401bec841ad0a2a75807f8e929374e2b6 GIT binary patch literal 5436 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 + + + + + $(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/nuget.props b/eng/nuget.props new file mode 100644 index 0000000..6298e42 --- /dev/null +++ b/eng/nuget.props @@ -0,0 +1,16 @@ + + + true + The Apache Software Foundation + https://lucenenet.apache.org + lucene-net-icon-128x128.png + Apache-2.0 + https://github.com/$(GitHubOrganization)/$(GitHubProject)/releases/tag/v$(PackageVersion) + $(ReleaseNotesUrl) + readme.md + + + + + + \ No newline at end of file diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index daccd8d..58569e4 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -3,6 +3,7 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + $(NoWarn);CS1591 \ No newline at end of file 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..2a5d451 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Package/Lucene.Net.CodeAnalysis.Dev.Package.csproj @@ -0,0 +1,106 @@ + + + + + + + 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\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..22c80f3 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,94 @@  - - 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/Lucene.Net.CodeAnalysis.Dev.csproj b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj index 1963ee9..bf06ca0 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj +++ b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj @@ -16,17 +16,21 @@ true true - - true - true - - 1.0.0 PrepareResources;$(CompileDependsOn) + + + true + 1.0.0 + 1.0.* + + + + all @@ -51,10 +55,6 @@ Resources - - - - From 91c9799a9a7b4448485c5cee2ff7c7c91aee6aa3 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 17:34:22 +0700 Subject: [PATCH 04/44] Added Lucene.Net.CodeAnalysis.Dev.Vsix project for Visual Studio live debugging --- ...t.CodeAnalysis.Dev.ChildProcessDbgSettings | 6 +++ Lucene.Net.CodeAnalysis.Dev.sln | 7 +++ .../Lucene.Net.CodeAnalysis.Dev.Vsix.csproj | 53 +++++++++++++++++++ .../localized.resources.targets | 17 ++++++ .../source.extension.vsixmanifest | 26 +++++++++ 5 files changed, 109 insertions(+) create mode 100644 Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.Vsix/Lucene.Net.CodeAnalysis.Dev.Vsix.csproj create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.Vsix/source.extension.vsixmanifest 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 04edacb..1092bf0 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1A48DD8E-1D7 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8170F744-3AE0-41EE-8986-611BA2C20425}" 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 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lucene.Net.CodeAnalysis.Dev.Tests", "tests\Lucene.Net.CodeAnalysis.Dev.Tests\Lucene.Net.CodeAnalysis.Dev.Tests.csproj", "{007DD065-3DF1-4AC1-9403-FE11E4654AF4}" @@ -21,6 +23,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {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 @@ -42,6 +48,7 @@ Global 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} 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..64c7098 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/Lucene.Net.CodeAnalysis.Dev.Vsix.csproj @@ -0,0 +1,53 @@ + + + + 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..f6e72c1 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets @@ -0,0 +1,17 @@ + + + + $(GetVsixSourceItemsDependsOn);IncludeResourcesInVsix + + + + + + %(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..d0dc76f --- /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 + + + + + + + + + + + + + + + From 9a7a0e184dbd13aa5a371bc2d3efc6187d37b17c Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 17:58:09 +0700 Subject: [PATCH 05/44] Renamed Helpers namespace to Utility --- .../LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs | 2 +- .../LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs | 2 +- .../LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs | 2 +- .../LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs | 2 +- .../{Helpers => Utility}/FloatingPoint.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/Lucene.Net.CodeAnalysis.Dev/{Helpers => Utility}/FloatingPoint.cs (96%) diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs index 1ddd54c..0c7dcf7 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -1,4 +1,4 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs index 88a7010..daf1b22 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -1,4 +1,4 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs index 31c2313..de11cab 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -1,4 +1,4 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs index c3e1a76..3e003ba 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -1,4 +1,4 @@ -using Lucene.Net.CodeAnalysis.Dev.Helpers; +using Lucene.Net.CodeAnalysis.Dev.Utility; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Helpers/FloatingPoint.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/FloatingPoint.cs similarity index 96% rename from src/Lucene.Net.CodeAnalysis.Dev/Helpers/FloatingPoint.cs rename to src/Lucene.Net.CodeAnalysis.Dev/Utility/FloatingPoint.cs index 15038f0..3eabfad 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Helpers/FloatingPoint.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/FloatingPoint.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace Lucene.Net.CodeAnalysis.Dev.Helpers +namespace Lucene.Net.CodeAnalysis.Dev.Utility { internal static class FloatingPoint { From 07e2ae541cfbc28efd66cc109a031b3b1f8f2732 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 18:36:53 +0700 Subject: [PATCH 06/44] Migrated DiagnosticDescriptor declarations to their own Descriptors class --- ...000_FloatingPointEqualityCSCodeAnalyzer.cs | 17 ++----- ...1_FloatingPointFormattingCSCodeAnalyzer.cs | 15 +------ ...2_FloatingPointArithmeticCSCodeAnalyzer.cs | 15 +------ ...1003_ArrayMethodParameterCSCodeAnalyzer.cs | 18 ++------ ...04_ArrayMethodReturnValueCSCodeAnalyzer.cs | 15 +------ .../Utility/Category.cs | 21 +++++++++ .../Utility/Descriptors.LuceneDev1xxx.cs | 44 +++++++++++++++++++ .../Utility/Descriptors.cs | 33 ++++++++++++++ ...000_FloatingPointEqualityCSCodeAnalyzer.cs | 21 ++++----- ...1_FloatingPointFormattingCSCodeAnalyzer.cs | 5 ++- ...2_FloatingPointArithmeticCSCodeAnalyzer.cs | 7 +-- ...1003_ArrayMethodParameterCSCodeAnalyzer.cs | 5 ++- ...04_ArrayMethodReturnValueCSCodeAnalyzer.cs | 5 ++- 13 files changed, 135 insertions(+), 86 deletions(-) create mode 100644 src/Lucene.Net.CodeAnalysis.Dev/Utility/Category.cs create mode 100644 src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs create mode 100644 src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.cs diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs index 0c7dcf7..a84b568 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -14,18 +14,7 @@ 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) { @@ -58,7 +47,7 @@ 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) { @@ -73,7 +62,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/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs index daf1b22..1331c34 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -14,18 +14,7 @@ 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 ImmutableArray SupportedDiagnostics => [Descriptors.LuceneDev1001_FloatingPointFormatting]; public override void Initialize(AnalysisContext context) { @@ -54,7 +43,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.LuceneDev1001_FloatingPointFormatting, context.Node.GetLocation(), memberAccessExpression.ToString())); } } } diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs index de11cab..3d61a8e 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -14,18 +14,7 @@ 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 +59,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/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs index 56ef9af..00f59ad 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -9,18 +10,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 +33,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/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs index 3e003ba..1eac0bf 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -15,18 +15,7 @@ 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 +36,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/Utility/Category.cs b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Category.cs new file mode 100644 index 0000000..d626a6b --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Category.cs @@ -0,0 +1,21 @@ +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..c71323e --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.LuceneDev1xxx.cs @@ -0,0 +1,44 @@ +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 + { + public static DiagnosticDescriptor LuceneDev1000_FloatingPointEquality { get; } = + Diagnostic( + "LuceneDev1000", + Design, + Warning + ); + + public static DiagnosticDescriptor LuceneDev1001_FloatingPointFormatting { get; } = + Diagnostic( + "LuceneDev1001", + Design, + Warning + ); + + public static DiagnosticDescriptor LuceneDev1002_FloatingPointArithmetic { get; } = + Diagnostic( + "LuceneDev1002", + Design, + Warning + ); + + public static DiagnosticDescriptor LuceneDev1003_ArrayMethodParameter { get; } = + Diagnostic( + "LuceneDev1003", + Design, + Warning + ); + + public static DiagnosticDescriptor LuceneDev1004_ArrayMethodReturnValue { get; } = + 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..fe19bd4 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev/Utility/Descriptors.cs @@ -0,0 +1,33 @@ +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/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs index d730e83..7495e27 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; using TestHelper; @@ -50,7 +51,7 @@ protected internal override bool LessThan(float termA, float termB) var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -90,7 +91,7 @@ protected internal override bool LessThan(float termA, float termB) var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -130,7 +131,7 @@ protected internal override bool LessThan(float termA, float termB) var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -170,7 +171,7 @@ protected internal override bool LessThan(float termA, float termB) var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -210,7 +211,7 @@ protected internal override bool LessThan(float termA, float termB) var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -247,7 +248,7 @@ public void MyMethod() var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -283,7 +284,7 @@ public void MyMethod() var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -321,7 +322,7 @@ public class Term var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = @@ -357,7 +358,7 @@ public void MyMethod() var expected = new DiagnosticResult { - Id = LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, 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 = diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs index 3d51da5..5244e94 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; using TestHelper; @@ -45,7 +46,7 @@ public void MyMethod() var expected = new DiagnosticResult { - Id = LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1001_FloatingPointFormatting.Id, 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 = diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs index 7cc960a..bf4782a 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; using TestHelper; @@ -47,7 +48,7 @@ public void MyMethod() var expected1 = new DiagnosticResult { - Id = LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1002_FloatingPointArithmetic.Id, 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"), @@ -61,7 +62,7 @@ public void MyMethod() var expected2 = new DiagnosticResult { - Id = LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1002_FloatingPointArithmetic.Id, 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"), diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs index a16931f..151e617 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; using TestHelper; @@ -52,7 +53,7 @@ public static bool ParseChar(string id, int[] pos, char ch) var expected = new DiagnosticResult { - Id = LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1003_ArrayMethodParameter.Id, 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 = diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs index 71048a8..10dcfda 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; using TestHelper; @@ -48,7 +49,7 @@ public static byte[] GetVersionByteArrayFromCompactInt32(int version) // ICU4N s var expected = new DiagnosticResult { - Id = LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.DiagnosticId, + Id = Descriptors.LuceneDev1004_ArrayMethodReturnValue.Id, 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 = From 12d44e6ec223db42664bb3c5b747d63eb4d2ba15 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 21:35:15 +0700 Subject: [PATCH 07/44] SWEEP: Converted all tests to use Microsoft.CodeAnalysis.Analyzer.Testing --- .../Helpers/CodeFixVerifier.Helper.cs | 102 ------- .../Helpers/DiagnosticResult.cs | 86 ------ .../Helpers/DiagnosticVerifier.Helper.cs | 187 ------------ .../Lucene.Net.CodeAnalysis.Dev.Tests.csproj | 1 + ...000_FloatingPointEqualityCSCodeAnalyzer.cs | 251 ++++++++-------- ...1_FloatingPointFormattingCSCodeAnalyzer.cs | 67 +++-- ...2_FloatingPointArithmeticCSCodeAnalyzer.cs | 86 +++--- ...1003_ArrayMethodParameterCSCodeAnalyzer.cs | 83 ++++-- ...04_ArrayMethodReturnValueCSCodeAnalyzer.cs | 76 +++-- .../Utility/InjectableAnalyzerTest.cs | 55 ++++ .../Utility/Verifier.cs | 171 +++++++++++ .../Verifiers/CodeFixVerifier.cs | 144 --------- .../Verifiers/DiagnosticVerifier.cs | 280 ------------------ 13 files changed, 550 insertions(+), 1039 deletions(-) delete mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/CodeFixVerifier.Helper.cs delete mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticResult.cs delete mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Helpers/DiagnosticVerifier.Helper.cs create mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/InjectableAnalyzerTest.cs create mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/Verifier.cs delete mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/CodeFixVerifier.cs delete mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.Tests/Verifiers/DiagnosticVerifier.cs 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 0f6c00c..d2d048e 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 @@ -7,6 +7,7 @@ + diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs index 7495e27..c9ed130 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -1,32 +1,48 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +/* + * 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.Tests.Utility; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; -using TestHelper; +using System.Threading.Tasks; namespace Lucene.Net.CodeAnalysis.Dev.Tests { - public class TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer : DiagnosticVerifier + public class TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer { - - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer(); - } - //No diagnostics expected to show up [Test] - public void TestEmptyFile() + public async Task TestEmptyFile() { - var test = @""; + var testCode = @""; - VerifyCSharpDiagnostic(test); + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); } [Test] - public void TestDiagnostic_LessThan() + public async Task TestDiagnostic_LessThan() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -42,31 +58,31 @@ public void MyMethod() public void MyMethod(int n) { } - protected internal override bool LessThan(float termA, float termB) + protected internal bool LessThan(float termA, float termB) { return termA < termB; } } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_EqualTo() + public async Task TestDiagnostic_EqualTo() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -82,31 +98,31 @@ public void MyMethod() public void MyMethod(int n) { } - protected internal override bool LessThan(float termA, float termB) + protected internal bool LessThan(float termA, float termB) { return termA == termB; } } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_GreaterThan() + public async Task TestDiagnostic_GreaterThan() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -122,31 +138,31 @@ public void MyMethod() public void MyMethod(int n) { } - protected internal override bool LessThan(float termA, float termB) + protected internal bool LessThan(float termA, float termB) { return termA > termB; } } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_LessThanOrEqualTo() + public async Task TestDiagnostic_LessThanOrEqualTo() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -162,31 +178,31 @@ public void MyMethod() public void MyMethod(int n) { } - protected internal override bool LessThan(float termA, float termB) + protected internal bool LessThan(float termA, float termB) { return termA <= termB; } } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_GreaterThanOrEqualTo() + public async Task TestDiagnostic_GreaterThanOrEqualTo() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -202,31 +218,31 @@ public void MyMethod() public void MyMethod(int n) { } - protected internal override bool LessThan(float termA, float termB) + protected internal bool LessThan(float termA, float termB) { return termA >= termB; } } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_EqualTo_MemberVariable() + public async Task TestDiagnostic_EqualTo_MemberVariable() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -246,24 +262,24 @@ public void MyMethod() } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_EqualTo_LocalVariable() + public async Task TestDiagnostic_EqualTo_LocalVariable() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -281,25 +297,24 @@ public void MyMethod() } } "; + 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 expected = new DiagnosticResult + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_EqualTo_PropertyOfParameter() + public async Task TestDiagnostic_EqualTo_PropertyOfParameter() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -320,24 +335,24 @@ public class Term } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_Equals_LocalVariable() + public async Task TestDiagnostic_Equals_LocalVariable() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -356,18 +371,18 @@ public void MyMethod() } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1000_FloatingPointEquality.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } } } diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs index 5244e94..15c799a 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -1,36 +1,53 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +/* + * 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.Tests.Utility; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; -using TestHelper; +using System.Threading.Tasks; namespace Lucene.Net.CodeAnalysis.Dev.Tests { - public class TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer : DiagnosticVerifier + public class TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer(); - } - //No diagnostics expected to show up [Test] - public void TestEmptyFile() + public async Task TestEmptyFile() { - var test = @""; + var testCode = @""; - VerifyCSharpDiagnostic(test); + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); } [Test] - public void TestDiagnostic_Float_ToString() + public async Task TestDiagnostic_Float_ToString() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Text; - using System.Threading.Tasks; using System.Diagnostics; public class MyClass @@ -44,18 +61,18 @@ public void MyMethod() } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1001_FloatingPointFormatting.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } } } diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs index bf4782a..5c07c1f 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -1,31 +1,49 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +/* + * 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.Tests.Utility; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; -using TestHelper; +using System.Threading.Tasks; namespace Lucene.Net.CodeAnalysis.Dev.Tests { - public class TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer : DiagnosticVerifier + public class TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer(); - } //No diagnostics expected to show up [Test] - public void TestEmptyFile() + public async Task TestEmptyFile() { - var test = @""; + var testCode = @""; - VerifyCSharpDiagnostic(test); + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); } [Test] - public void TestDiagnostic_Float_ToString() + public async Task TestDiagnostic_Float_ToString() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -41,40 +59,28 @@ public class MyClass public void MyMethod() { long foo = 33; - var result = ((double)float1 * (double)float2)) / foo; + var result = ((double)float1 * (double)float2) / foo; } } "; - var expected1 = new DiagnosticResult - { - Id = Descriptors.LuceneDev1002_FloatingPointArithmetic.Id, - 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 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 expected2 = new DiagnosticResult + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer()) { - Id = Descriptors.LuceneDev1002_FloatingPointArithmetic.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected1, expected2 } }; - VerifyCSharpDiagnostic(test, expected1, expected2); + await test.RunAsync(); } } } diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs index 151e617..d224ccc 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs @@ -1,31 +1,48 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +/* + * 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.Tests.Utility; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; -using TestHelper; +using System.Threading.Tasks; namespace Lucene.Net.CodeAnalysis.Dev.Tests { - public class TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer : DiagnosticVerifier + public class TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer(); - } - //No diagnostics expected to show up [Test] - public void TestEmptyFile() + public async Task TestEmptyFile() { - var test = @""; + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer()) + { + TestCode = testCode + }; - VerifyCSharpDiagnostic(test); + await test.RunAsync(); } [Test] - public void TestDiagnostic_ParseChar_String_Int32Array_Char() + public async Task TestDiagnostic_ParseChar_String_Int32Array_Char() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -38,7 +55,7 @@ public class MyClass public static bool ParseChar(string id, int[] pos, char ch) { int start = pos[0]; - pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); + //pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); if (pos[0] == id.Length || id[pos[0]] != ch) { @@ -51,24 +68,24 @@ public static bool ParseChar(string id, int[] pos, char ch) } "; - var expected = new DiagnosticResult + 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()) { - Id = Descriptors.LuceneDev1003_ArrayMethodParameter.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_ParseChar_String_CharArray_Char() + public async Task TestDiagnostic_ParseChar_String_CharArray_Char() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -80,8 +97,8 @@ public class MyClass { public static bool ParseChar(string id, char[] pos, char ch) { - int start = pos[0]; - pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); + char start = pos[0]; + //pos[0] = PatternProps.SkipWhiteSpace(id, pos[0]); if (pos[0] == id.Length || id[pos[0]] != ch) { @@ -93,9 +110,13 @@ public static bool ParseChar(string id, char[] pos, char ch) } } "; - // We shouldn't trigger a warning on char[] - VerifyCSharpDiagnostic(test); + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); } } } diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs index 10dcfda..5f73823 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -1,31 +1,49 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; +/* + * 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.Tests.Utility; +using Lucene.Net.CodeAnalysis.Dev.Utility; +using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; -using TestHelper; +using System.Threading.Tasks; namespace Lucene.Net.CodeAnalysis.Dev.Tests { - public class TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer : DiagnosticVerifier + public class TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer { - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer(); - } //No diagnostics expected to show up [Test] - public void TestEmptyFile() + public async Task TestEmptyFile() { - var test = @""; + var testCode = @""; + + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer()) + { + TestCode = testCode + }; - VerifyCSharpDiagnostic(test); + await test.RunAsync(); } [Test] - public void TestDiagnostic_GetVersionByteArrayFromCompactInt32_ByteArrayReturnType() + public async Task TestDiagnostic_GetVersionByteArrayFromCompactInt32_ByteArrayReturnType() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -47,24 +65,25 @@ public static byte[] GetVersionByteArrayFromCompactInt32(int version) // ICU4N s } "; - var expected = new DiagnosticResult + + 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()) { - Id = Descriptors.LuceneDev1004_ArrayMethodReturnValue.Id, - 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) - } + TestCode = testCode, + ExpectedDiagnostics = { expected } }; - VerifyCSharpDiagnostic(test, expected); + await test.RunAsync(); } [Test] - public void TestDiagnostic_GetVersionCharArrayFromCompactInt32_CharArrayReturnType() + public async Task TestDiagnostic_GetVersionCharArrayFromCompactInt32_CharArrayReturnType() { - var test = @" + var testCode = @" using System; using System.Collections.Generic; using System.Linq; @@ -87,7 +106,12 @@ public static char[] GetVersionCharArrayFromCompactInt32(int version) "; // We shouldn't trigger a warning on char[] - VerifyCSharpDiagnostic(test); + var test = new InjectableCSharpAnalyzerTest(() => new LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer()) + { + TestCode = testCode + }; + + await test.RunAsync(); } } } diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/InjectableAnalyzerTest.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/InjectableAnalyzerTest.cs new file mode 100644 index 0000000..68bfed1 --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/InjectableAnalyzerTest.cs @@ -0,0 +1,55 @@ +/* + * 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; +using System; +using System.Collections.Generic; + +namespace Lucene.Net.CodeAnalysis.Dev.Tests.Utility +{ + public class InjectableCSharpAnalyzerTest : AnalyzerTest + { + private readonly Func createAnalyzer; + + public InjectableCSharpAnalyzerTest(Func createAnalyzer) + { + this.createAnalyzer = createAnalyzer ?? throw new ArgumentNullException(nameof(createAnalyzer)); + } + + 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() + { + return [createAnalyzer()]; + } + } +} diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/Verifier.cs b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/Verifier.cs new file mode 100644 index 0000000..154c4fb --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/Verifier.cs @@ -0,0 +1,171 @@ +/* + * 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Lucene.Net.CodeAnalysis.Dev.Tests.Utility +{ + 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/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 - } -} From 1e5e59a02ba67e1371df3d54d675bcb8b8de6a1b Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 21:50:48 +0700 Subject: [PATCH 08/44] Lucene.Net.CodeAnalysis.Dev.sln: Added Solution Items folder --- Lucene.Net.CodeAnalysis.Dev.sln | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index 1092bf0..f5e4093 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -17,6 +17,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev 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 + .gitignore = .gitignore + DiagnosticCategoryAndIdRanges.txt = DiagnosticCategoryAndIdRanges.txt + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.Packages.props = Directory.Packages.props + LICENSE.txt = LICENSE.txt + Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings = Lucene.Net.CodeAnalysis.Dev.ChildProcessDbgSettings + NOTICE.txt = NOTICE.txt + README.md = README.md + version.json = version.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From f85a0236e883131106f46dbc9ed99d7bd650ce6d Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 22:02:25 +0700 Subject: [PATCH 09/44] Lucene.Net.CodeAnalysis.Dev.sln: Added .github, docs, and eng folders as .folderproj (NoTargets) projects that allow the IDE to edit the underlying file structure --- .github/.github.folderproj | 5 +++++ Lucene.Net.CodeAnalysis.Dev.sln | 19 +++++++++++++++++++ docs/docs.folderproj | 5 +++++ eng/eng.folderproj | 5 +++++ global.json | 6 ++++++ 5 files changed, 40 insertions(+) create mode 100644 .github/.github.folderproj create mode 100644 docs/docs.folderproj create mode 100644 eng/eng.folderproj create mode 100644 global.json diff --git a/.github/.github.folderproj b/.github/.github.folderproj new file mode 100644 index 0000000..c6e64af --- /dev/null +++ b/.github/.github.folderproj @@ -0,0 +1,5 @@ + + + + + diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index f5e4093..8a2d6d4 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -3,6 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 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}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8170F744-3AE0-41EE-8986-611BA2C20425}" @@ -24,6 +30,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 @@ -37,6 +44,18 @@ Global 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 diff --git a/docs/docs.folderproj b/docs/docs.folderproj new file mode 100644 index 0000000..6ef2a5c --- /dev/null +++ b/docs/docs.folderproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/eng/eng.folderproj b/eng/eng.folderproj new file mode 100644 index 0000000..6ef2a5c --- /dev/null +++ b/eng/eng.folderproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file 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" ] +} From a13c5f31d794f72596f0bf75f585f88ad8dc172c Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 22:08:27 +0700 Subject: [PATCH 10/44] Moved Lucene.Net.snk file to the eng directory --- Directory.Build.props | 2 +- Lucene.Net.snk => eng/Lucene.Net.snk | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename Lucene.Net.snk => eng/Lucene.Net.snk (100%) diff --git a/Directory.Build.props b/Directory.Build.props index 507402a..1d03497 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -24,7 +24,7 @@ - $(RepositoryRoot)Lucene.Net.snk + $(RepositoryRoot)eng/Lucene.Net.snk 002400000480000094000000060200000024000052534131000400000100010075a07ce602f88ef263c7db8cb342c58ebd49ecdcc210fac874260b0213fb929ac3dcaf4f5b39744b800f99073eca72aebfac5f7284e1d5f2c82012a804a140f06d7d043d83e830cdb606a04da2ad5374cc92c0a49508437802fb4f8fb80a05e59f80afb99f4ccd0dfe44065743543c4b053b669509d29d332cd32a0cb1e97e84 true 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 From 66317348f5a8cde0c63e131553ba5240eda1d476 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 22:59:54 +0700 Subject: [PATCH 11/44] Lucene.Net.CodeAnalysis.Dev.sln: Added Directory.Build.targets and Directory.Build.props files to src and tests folders --- Lucene.Net.CodeAnalysis.Dev.sln | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index 8a2d6d4..d38b9a5 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -10,8 +10,16 @@ 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 From e9d5a3f24cb95f0a4dc3534c3e11337cd9497e9e Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 23:22:15 +0700 Subject: [PATCH 12/44] Added GitHub workflows for build/release automation --- .github/release.yml | 28 ++ .../Lucene-Net-CodeAnalysis-Dev-Tests.yml | 26 -- .github/workflows/ci.yml | 438 ++++++++++++++++++ .github/workflows/powershell-tests.yml | 88 ++++ .github/workflows/renovate-dependencies.yml | 21 + Lucene.Net.CodeAnalysis.Dev.sln | 1 + eng/Ensure-Powershell-Dependencies.ps1 | 42 ++ eng/build/Markdown-Formatting.Tests.ps1 | 62 +++ eng/build/Markdown-Formatting.ps1 | 124 +++++ eng/build/Parse-Test-Results.Tests.ps1 | 216 +++++++++ eng/build/Parse-Test-Results.ps1 | 94 ++++ eng/build/coverage.runsettings | 100 ++++ renovate.json | 171 +++++++ 13 files changed, 1385 insertions(+), 26 deletions(-) create mode 100644 .github/release.yml delete mode 100644 .github/workflows/Lucene-Net-CodeAnalysis-Dev-Tests.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/powershell-tests.yml create mode 100644 .github/workflows/renovate-dependencies.yml create mode 100644 eng/Ensure-Powershell-Dependencies.ps1 create mode 100644 eng/build/Markdown-Formatting.Tests.ps1 create mode 100644 eng/build/Markdown-Formatting.ps1 create mode 100644 eng/build/Parse-Test-Results.Tests.ps1 create mode 100644 eng/build/Parse-Test-Results.ps1 create mode 100644 eng/build/coverage.runsettings create mode 100644 renovate.json diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..3e38098 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,28 @@ +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: + - "*" \ No newline at end of file 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..6878472 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,438 @@ +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, x86, arm64] + tfm: [net8.0] + exclude: + - arch: x86 + os: ubuntu-latest + - arch: x86 + os: macos-latest + - arch: x86 + os: macos-13 + - 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 + if: matrix.arch != 'x86' + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 + with: + dotnet-version: 8.0.x + + - name: Setup .NET 8.0 (x86) + if: matrix.arch == 'x86' + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 + with: + dotnet-version: 8.0.x + env: + PROCESSOR_ARCHITECTURE: x86 + + - 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..a11cdac --- /dev/null +++ b/.github/workflows/powershell-tests.yml @@ -0,0 +1,88 @@ +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..e36d0d8 --- /dev/null +++ b/.github/workflows/renovate-dependencies.yml @@ -0,0 +1,21 @@ +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/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index d38b9a5..c209515 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -43,6 +43,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 diff --git a/eng/Ensure-Powershell-Dependencies.ps1 b/eng/Ensure-Powershell-Dependencies.ps1 new file mode 100644 index 0000000..730432f --- /dev/null +++ b/eng/Ensure-Powershell-Dependencies.ps1 @@ -0,0 +1,42 @@ +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/eng/build/Markdown-Formatting.Tests.ps1 b/eng/build/Markdown-Formatting.Tests.ps1 new file mode 100644 index 0000000..cd4e0e2 --- /dev/null +++ b/eng/build/Markdown-Formatting.Tests.ps1 @@ -0,0 +1,62 @@ +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)" + "" | Should -Be "" + } + + 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..0204163 --- /dev/null +++ b/eng/build/Markdown-Formatting.ps1 @@ -0,0 +1,124 @@ +<# +.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..844c9b4 --- /dev/null +++ b/eng/build/Parse-Test-Results.Tests.ps1 @@ -0,0 +1,216 @@ +$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..e33cd25 --- /dev/null +++ b/eng/build/Parse-Test-Results.ps1 @@ -0,0 +1,94 @@ +<# +.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/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 +} From 28d626d1fce2c3c2ada3a3789b65086544c48030 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Mon, 15 Sep 2025 23:35:31 +0700 Subject: [PATCH 13/44] docs: Added building-and-testing, make-release, and visual-studio-debugging documentation --- docs/building-and-testing.md | 59 +++++ docs/images/release-workflow.svg | 102 +++++++++ .../vs-child-process-debugging-settings.png | Bin 0 -> 27445 bytes docs/images/vs-native-debugging-setting.png | Bin 0 -> 26923 bytes docs/images/vs-test-architecture.png | Bin 0 -> 14580 bytes docs/make-release.md | 216 ++++++++++++++++++ docs/visual-studio-debugging.md | 26 +++ 7 files changed, 403 insertions(+) create mode 100644 docs/building-and-testing.md create mode 100644 docs/images/release-workflow.svg create mode 100644 docs/images/vs-child-process-debugging-settings.png create mode 100644 docs/images/vs-native-debugging-setting.png create mode 100644 docs/images/vs-test-architecture.png create mode 100644 docs/make-release.md create mode 100644 docs/visual-studio-debugging.md diff --git a/docs/building-and-testing.md b/docs/building-and-testing.md new file mode 100644 index 0000000..2c4db98 --- /dev/null +++ b/docs/building-and-testing.md @@ -0,0 +1,59 @@ +# 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/images/release-workflow.svg b/docs/images/release-workflow.svg new file mode 100644 index 0000000..f371675 --- /dev/null +++ b/docs/images/release-workflow.svg @@ -0,0 +1,102 @@ +

Prepare Release

Tag Version

Manual Review

Main Branch

Release Branch

Git Tag

Draft Release

Publish Release

\ No newline at end of file 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 0000000000000000000000000000000000000000..75e3cd8ba8633b8d3024a2f237a1a9a0f3df617d GIT binary patch literal 27445 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 $i33>akm|5{% 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 YL)B+NTFjSU%nzb{4R*EQ%FT!W1yq;B%>V!Z literal 0 HcmV?d00001 diff --git a/docs/images/vs-test-architecture.png b/docs/images/vs-test-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..29857acfb9ed541f021d799bd7f140bd190ff55e GIT binary patch literal 14580 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 P?kUQt$(Fu)8}fevB)5>) literal 0 HcmV?d00001 diff --git a/docs/make-release.md b/docs/make-release.md new file mode 100644 index 0000000..830792b --- /dev/null +++ b/docs/make-release.md @@ -0,0 +1,216 @@ +# Making a 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 builds on the main branch and production releases on release branches. + +## Prerequisites + +- [.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)) + +### Installing NBGV Tool + +Perform a one-time install of the nbgv tool using the following dotnet CLI command: + +```console +dotnet tool install -g nbgv --version +``` + +## Versioning Primer + +This project uses [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html) and strictly adheres to the guidelines about bumping the major, minor and build numbers of the version number. + +The assembly version should remain the same in all cases, except when the major version changes, so that it can be used as a drop-in replacement. + +## Creating a Release Branch + +### Release Workflow Overview + +![Release Workflow](images/release-workflow.svg) + +### 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://github.com/dotnet/Nerdbank.GitVersioning/blob/master/doc/nbgv-cli.md). + +For example, assume the `version.json` file on the main branch is currently setup 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 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`. Every build from this branch (regardless of how many commits are added) will be versioned 2.0.0. + +### 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://github.com/dotnet/Nerdbank.GitVersioning/blob/master/doc/nbgv-cli.md#preparing-a-release)). Starting from the same point as the [Ready to Release](#ready-to-release) scenario, we use the following command. + +```console +nbgv prepare-release beta --nextVersion 2.0.1 +``` + +The command should respond with: + +```console +release/v2.0 branch now tracks v2.0.0-beta.{height} stabilization and release. +main branch now tracks v2.0.0-alpha.{height} development. +``` + +The tool created a release branch named `release/v2.0`. Every build from 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. + +### Bumping the Version Manually + +When releasing a version that does not directly follow the current release version, manually update the `version` (and `assemblyVersion` if this is a major version bump) in `version.json` before creating the release branch. See the [version.json schema](https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json) to determine valid options. + +## Correcting the Release Version Height + +Nerdbank.GitVersioning is designed in a way that it doesn't produce the same version number twice. This is done by using a "git height", which counts the number of commits since the last version update. This works great for CI, but is less than ideal when we don't want to skip over versions for the release. + +Since Nerdbank.GitVersioning considers each commit a new "version," the `versionHeightOffset` can be adjusted on the release branch to ensure the release uses the correct version number. This can be done by using the following command to see what version we are currently on, and then adjusting the value accordingly. + +```console +nbgv get-version +``` + +> [!NOTE] +> Officially, it is not recommended to use `versionHeightOffset` and in general that is true. However, using it in a narrow scope, such as on a release branch should be okay. In practice, users will not build from these branches, they will build from a release tag. + +Then open the `version.json` file at the repository root, and set the `versionHeightOffset` using the formula `versionHeightOffset - ((versionHeight - desiredHeight) + 1)`. For example, if the current version is 2.0.1-beta.14 and we want to release 2.0.1-beta.5 (because the last version released was 2.0.1-beta.4), and the `versionHeightOffset` is set to -21: + +###### Calculating versionHeightOffset +``` +-21 - ((14 - 5) + 1) = -31 +``` + +So, we must set `versionHeightOffset` to -31 and commit the change. + +Note that the + 1 is because we are creating a new commit that will increment the number by 1. The change must be committed to see the change to the version number. Run the command again to check that the version will be correct. + +```console +nbgv get-version +``` + +## 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 + +## 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. + +### Tagging the Commit + +If you don't already know the version that corresponds to the HEAD commit, check it now. + +```console +nbgv get-version +``` + +The reply will show a table of version information. + +```console +Version: 2.0.0 +AssemblyVersion: 2.0.0.0 +AssemblyInformationalVersion: 2.0.0-beta.5+a54c015802 +NuGetPackageVersion: 2.0.0-beta.5 +NpmPackageVersion: 2.0.0-beta.5 +``` + +Tag the commit with `v` followed by the NuGetPackageVersion. + +```console +git tag -a v -m "v" +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. + +### 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 + +| 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. Check to ensure the NuGet packages can be referenced by a new project and the project will compile. +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 didn't make it to release, it is important to delete it to avoid a confusing release history. It is required to be removed if the next attempt will be for the same version number. + +```console +git tag -d v +git push --delete v +``` + +##### Push a New Version Tag + +> [!NOTE] +> The same version number can be reused if there weren't any commits required to correct the release problem or if the `versionHeightOffset` is changed as described above. + +Once the issues have been addressed to fix the build and reset the version (if necessary), follow the same procedure starting at [Tagging the Commit](#tagging-the-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 GitHub. + +```console +git checkout +git merge +git push +``` diff --git a/docs/visual-studio-debugging.md b/docs/visual-studio-debugging.md new file mode 100644 index 0000000..e45e277 --- /dev/null +++ b/docs/visual-studio-debugging.md @@ -0,0 +1,26 @@ +# 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. From f392feb7a359cf3aa84c7b58fe29e24e04a01df2 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 00:24:48 +0700 Subject: [PATCH 14/44] Added Powershell script to run the Release Audit Tool --- .gitignore | 20 +++++++++ .rat-excludes | 17 ++++++++ rat.ps1 | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 .rat-excludes create mode 100644 rat.ps1 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/rat.ps1 b/rat.ps1 new file mode 100644 index 0000000..9b01c70 --- /dev/null +++ b/rat.ps1 @@ -0,0 +1,114 @@ +<# + 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 + + https://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.16). + +.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.16) with exclusions from `rat-exclude.txt`. + +.EXAMPLE + pwsh ./rat.ps1 -Version 0.16 -ExcludeFileName custom-exclude.txt + + Runs Apache RAT version 0.16 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.16", + [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 +} + +# Run from the script directory so '.' means the repository root next to this script +Push-Location $scriptDir +try { + $argsList = @( + "-jar", $ratJar, + "--dir", ".", + "--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" + } +} +finally { + Pop-Location +} \ No newline at end of file From 3fccaf077f0f6bf1046219677e7d56085e0b80c5 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 00:54:09 +0700 Subject: [PATCH 15/44] rat.ps1: Changed RAT version to 0.13, since that is what we know works --- rat.ps1 | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/rat.ps1 b/rat.ps1 index 9b01c70..0f84776 100644 --- a/rat.ps1 +++ b/rat.ps1 @@ -30,7 +30,7 @@ directory as this script) as the target directory. .PARAMETER Version - The version of Apache RAT to use (default: 0.16). + The version of Apache RAT to use (default: 0.13). .PARAMETER ExcludeFileName Name of an exclude file containing path patterns that RAT should ignore. @@ -43,19 +43,19 @@ .EXAMPLE pwsh ./rat.ps1 - Runs Apache RAT (default version 0.16) with exclusions from `rat-exclude.txt`. + Runs Apache RAT (default version 0.13) with exclusions from `rat-exclude.txt`. .EXAMPLE - pwsh ./rat.ps1 -Version 0.16 -ExcludeFileName custom-exclude.txt + pwsh ./rat.ps1 -Version 0.13 -ExcludeFileName custom-exclude.txt - Runs Apache RAT version 0.16 using the specified exclude file. + 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.16", + [string]$Version = "0.13", [string]$ExcludeFileName = ".rat-excludes" ) @@ -88,27 +88,20 @@ if (-not (Test-Path $ratExcludeFile)) { $useExclude = $true } -# Run from the script directory so '.' means the repository root next to this script -Push-Location $scriptDir -try { - $argsList = @( - "-jar", $ratJar, - "--dir", ".", - "--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" - } +$argsList = @( + "-jar", $ratJar, + "--dir", "`"$scriptDir`"", + "--addLicense", + "--force" +) + +if ($useExclude) { + $argsList += @("--exclude-file", "`"$ratExcludeFile`"") } -finally { - Pop-Location + +# Call java with argument list. Use & to invoke program. +& java @argsList + +if ($LASTEXITCODE -ne 0) { + throw "RAT exited with code $LASTEXITCODE" } \ No newline at end of file From 508b39b95b0d994de254726395b7894b57e6ac86 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 00:57:41 +0700 Subject: [PATCH 16/44] SWEEP: Ran the Apache Release Audit Tool to add missing license headers --- Lucene.Net.CodeAnalysis.Dev.sln | 18 ++++++++++++++- docs/images/release-workflow.svg | 22 ++++++++++++++++++- ...Lucene.Net.CodeAnalysis.Dev.Package.csproj | 22 ++++++++++++++++++- .../Lucene.Net.CodeAnalysis.Dev.Sample.csproj | 22 ++++++++++++++++++- .../LuceneDev1000Sample.cs | 21 +++++++++++++++++- .../LuceneDev1001Sample.cs | 19 ++++++++++++++++ .../LuceneDev1002Sample.cs | 19 ++++++++++++++++ .../LuceneDev1003Sample.cs | 19 ++++++++++++++++ .../LuceneDev1004Sample.cs | 19 ++++++++++++++++ .../Lucene.Net.CodeAnalysis.Dev.Vsix.csproj | 22 ++++++++++++++++++- .../Lucene.Net.CodeAnalysis.Dev.csproj | 20 +++++++++++++++++ ...000_FloatingPointEqualityCSCodeAnalyzer.cs | 21 +++++++++++++++++- ...1_FloatingPointFormattingCSCodeAnalyzer.cs | 21 +++++++++++++++++- ...2_FloatingPointArithmeticCSCodeAnalyzer.cs | 21 +++++++++++++++++- ...1003_ArrayMethodParameterCSCodeAnalyzer.cs | 21 +++++++++++++++++- ...04_ArrayMethodReturnValueCSCodeAnalyzer.cs | 21 +++++++++++++++++- .../Resources.resx | 20 +++++++++++++++++ .../Utility/Category.cs | 21 +++++++++++++++++- .../Utility/Descriptors.LuceneDev1xxx.cs | 21 +++++++++++++++++- .../Utility/Descriptors.cs | 21 +++++++++++++++++- .../Utility/FloatingPoint.cs | 21 +++++++++++++++++- .../Lucene.Net.CodeAnalysis.Dev.Tests.csproj | 20 +++++++++++++++++ 22 files changed, 437 insertions(+), 15 deletions(-) diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index c209515..e05168d 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -1,6 +1,22 @@ - + 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}" diff --git a/docs/images/release-workflow.svg b/docs/images/release-workflow.svg index f371675..113591c 100644 --- a/docs/images/release-workflow.svg +++ b/docs/images/release-workflow.svg @@ -1,3 +1,23 @@ + + +

Prepare Release

Tag Version

Manual Review

Main Branch

Release Branch

Git Tag

Draft Release

Publish Release

\ No newline at end of file +}

Prepare Release

Tag Version

Manual Review

Main Branch

Release Branch

Git Tag

Draft Release

Publish Release

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 index 2a5d451..60caadf 100644 --- 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 @@ -1,4 +1,24 @@ - + + + + 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 22c80f3..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,4 +1,24 @@ - + + + + net8.0 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 index 64c7098..cc5cc81 100644 --- 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 @@ -1,4 +1,24 @@ - + + + + net472 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 bf06ca0..0bde957 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj +++ b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj @@ -1,3 +1,23 @@ + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs index a84b568..367281f 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs @@ -1,4 +1,23 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; +/* + * 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; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs index 1331c34..a87a43d 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs @@ -1,4 +1,23 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; +/* + * 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; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs index 3d61a8e..8d52d51 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs @@ -1,4 +1,23 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; +/* + * 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; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs index 00f59ad..5c4f6a0 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs @@ -1,4 +1,23 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; +/* + * 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; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs index 1eac0bf..4c2d24a 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs +++ b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs @@ -1,4 +1,23 @@ -using Lucene.Net.CodeAnalysis.Dev.Utility; +/* + * 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; diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx index 9e476de..189622a 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx +++ b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx @@ -1,4 +1,24 @@ + + + + From defae978e1f36aa3e58dff3352052e4534123f9c Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 01:14:47 +0700 Subject: [PATCH 17/44] docs/make-release.md: Added section for Release Audit Tool --- docs/make-release.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/make-release.md b/docs/make-release.md index 830792b..c50d272 100644 --- a/docs/make-release.md +++ b/docs/make-release.md @@ -4,8 +4,10 @@ This project uses Nerdbank.GitVersioning to assist with creating version numbers ## 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) ### Installing NBGV Tool @@ -15,6 +17,20 @@ Perform a one-time install of the nbgv tool using the following dotnet CLI comma dotnet tool install -g nbgv --version ``` +## Run the Apache Release Audit Tool + +The Release Audit Tool will ensure that all non-generated text files have 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 have license headers +- Exclude files that are automatically generated +- Exclude files that don't work properly with license headers included (such as test data) + ## Versioning Primer This project uses [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html) and strictly adheres to the guidelines about bumping the major, minor and build numbers of the version number. From 18eb22b7efafef8a6a58b840b1cd14a41968a50b Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 12:39:27 +0700 Subject: [PATCH 18/44] Added .editorconfig and .gitattributes for most often hand-edited files --- .editorconfig | 149 +++++++++++++++++++++++++++++++++++++++++++++++++ .gitattributes | 29 ++++++++++ 2 files changed, 178 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..499ae6b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,149 @@ +# 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 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3fec4c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,29 @@ +# 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 From db82a0a8c9b20a13f08070d6f181cfd1d40d8559 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 13:34:29 +0700 Subject: [PATCH 19/44] Added Lucene.Net.CodeAnalysis.Dev.CodeFixes project and Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests --- Directory.Packages.props | 1 + Lucene.Net.CodeAnalysis.Dev.sln | 22 ++- .../CodeFixResources.Designer.cs | 63 ++++++ .../CodeFixResources.resx | 121 ++++++++++++ ...cene.Net.CodeAnalysis.Dev.CodeFixes.csproj | 70 +++++++ ...oatingPointFormattingCSCodeFixProvider .cs | 128 ++++++++++++ ...Lucene.Net.CodeAnalysis.Dev.Package.csproj | 2 +- .../localized.resources.targets | 5 + .../source.extension.vsixmanifest | 4 +- .../Resources.resx | 56 +++--- ...et.CodeAnalysis.Dev.CodeFixes.Tests.csproj | 40 ++++ ...loatingPointFormattingCSCodeFixProvider.cs | 182 ++++++++++++++++++ .../InjectableAnalyzerTest.cs | 12 +- .../InjectableCodeFixTest.cs | 63 ++++++ ....Net.CodeAnalysis.Dev.TestUtilities.csproj | 36 ++++ .../Verifier.cs | 31 ++- .../Lucene.Net.CodeAnalysis.Dev.Tests.csproj | 3 +- ...000_FloatingPointEqualityCSCodeAnalyzer.cs | 2 +- ...1_FloatingPointFormattingCSCodeAnalyzer.cs | 2 +- ...2_FloatingPointArithmeticCSCodeAnalyzer.cs | 2 +- ...1003_ArrayMethodParameterCSCodeAnalyzer.cs | 2 +- ...04_ArrayMethodReturnValueCSCodeAnalyzer.cs | 2 +- 22 files changed, 787 insertions(+), 62 deletions(-) create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.Designer.cs create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Lucene.Net.CodeAnalysis.Dev.CodeFixes.csproj create mode 100644 src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs create mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj create mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs rename tests/{Lucene.Net.CodeAnalysis.Dev.Tests/Utility => Lucene.Net.CodeAnalysis.Dev.TestUtilities}/InjectableAnalyzerTest.cs (83%) create mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableCodeFixTest.cs create mode 100644 tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Lucene.Net.CodeAnalysis.Dev.TestUtilities.csproj rename tests/{Lucene.Net.CodeAnalysis.Dev.Tests/Utility => Lucene.Net.CodeAnalysis.Dev.TestUtilities}/Verifier.cs (88%) diff --git a/Directory.Packages.props b/Directory.Packages.props index ae6b11f..488cdf4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,6 +14,7 @@ + diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index e05168d..ca4448a 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 # Licensed to the Apache Software Foundation (ASF) under one @@ -63,6 +62,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 @@ -101,6 +106,18 @@ Global {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 @@ -111,6 +128,9 @@ Global {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/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.Designer.cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.Designer.cs new file mode 100644 index 0000000..0a3b782 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Lucene.Net.CodeAnalysis.Dev.CodeFixes { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CodeFixResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CodeFixResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Lucene.Net.CodeAnalysis.Dev.CodeFixes.CodeFixResources", typeof(CodeFixResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} 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..2274a40 --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/CodeFixResources.resx @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + 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..1c39ffa --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/Lucene.Net.CodeAnalysis.Dev.CodeFixes.csproj @@ -0,0 +1,70 @@ + + + + + + + netstandard2.0 + enable + + + false + + + + PrepareResources;$(CompileDependsOn) + true + en-US + + + + + + MSBuild:Compile + $(IntermediateOutputPath)CodeFixResources.designer.cs + CSharp + Lucene.Net.CodeAnalysis.Dev + CodeFixResources + Lucene.Net.CodeAnalysis.Dev.CodeFixResources.resources + CodeFixResources.Designer.cs + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs new file mode 100644 index 0000000..63e750b --- /dev/null +++ b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs @@ -0,0 +1,128 @@ +/* + * 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.CodeActions; +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 => + ImmutableArray.Create(Descriptors.LuceneDev1001_FloatingPointFormatting.Id); + + public override FixAllProvider GetFixAllProvider() => + WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + // only handle the first diagnostic for this registration + var diagnostic = context.Diagnostics.FirstOrDefault(); + if (diagnostic == null) + return; + + var 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. + var node = root.FindNode(diagnostic.Location.SourceSpan); + if (node is null) + return; + + var invocation = node as InvocationExpressionSyntax + ?? node.AncestorsAndSelf().OfType().FirstOrDefault(); + + if (invocation is null) + return; + + context.RegisterCodeFix( + CodeAction.Create( + title: "Use J2N.Numerics.*.ToString(...)", + createChangedDocument: c => ReplaceWithJ2NToStringAsync(context.Document, invocation, c), + equivalenceKey: "UseJ2NToString"), + diagnostic); + } + + private async Task ReplaceWithJ2NToStringAsync( + Document document, + InvocationExpressionSyntax invocation, + CancellationToken cancellationToken) + { + if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) + return document; + + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (semanticModel is null) + return document; + + var typeInfo = semanticModel.GetTypeInfo(memberAccess.Expression, cancellationToken); + var type = typeInfo.Type; + + string? j2nTypeName = type?.SpecialType switch + { + SpecialType.System_Single => "Single", + SpecialType.System_Double => "Double", + _ => null + }; + + if (j2nTypeName == null) + return document; // unsupported type + + // Build J2N.Numerics.Single.ToString + var j2n = SyntaxFactory.IdentifierName("J2N"); + var numerics = SyntaxFactory.IdentifierName("Numerics"); + var single = SyntaxFactory.IdentifierName(j2nTypeName); + var toString = SyntaxFactory.IdentifierName("ToString"); + + // Build the chain: J2N.Numerics.Single.ToString + var j2nNumerics = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, j2n, numerics); + var j2nNumericsSingle = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, j2nNumerics, single); + var j2nToStringAccess = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, j2nNumericsSingle, toString); + + + // Build invocation: J2N.Numerics..ToString(, ) + var newArgs = new List { SyntaxFactory.Argument(memberAccess.Expression) }; + if (invocation.ArgumentList != null) + newArgs.AddRange(invocation.ArgumentList.Arguments); + + var newInvocation = SyntaxFactory.InvocationExpression(j2nToStringAccess, SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArgs))) + .WithTriviaFrom(invocation) + .WithAdditionalAnnotations(Formatter.Annotation); + + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + editor.ReplaceNode(invocation, newInvocation); + + return editor.GetChangedDocument(); + } + } +} 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 index 60caadf..3354019 100644 --- 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 @@ -43,7 +43,7 @@ under the License. <_CodeAnalysisCSAssemblyFile>$(RepositoryRoot)src\Lucene.Net.CodeAnalysis.Dev\bin\$(Configuration)\$(TargetFramework)\Lucene.Net.CodeAnalysis.Dev.dll - <_CodeAnalysisCodeFixesCSAssemblyFile>$(RepositoryRoot)src\Lucene.Net.CodeAnalysis.Dev\bin\$(Configuration)\$(TargetFramework)\Lucene.Net.CodeAnalysis.Dev.CodeFixes.dll + <_CodeAnalysisCodeFixesCSAssemblyFile>$(RepositoryRoot)src\Lucene.Net.CodeAnalysis.Dev.CodeFixes\bin\$(Configuration)\$(TargetFramework)\Lucene.Net.CodeAnalysis.Dev.CodeFixes.dll diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets index f6e72c1..de02e0e 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets @@ -11,6 +11,11 @@ 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 index d0dc76f..4ae95c7 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/source.extension.vsixmanifest +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/source.extension.vsixmanifest @@ -16,8 +16,8 @@ - + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx index 189622a..d61ab13 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx +++ b/src/Lucene.Net.CodeAnalysis.Dev/Resources.resx @@ -20,18 +20,18 @@ under the License. --> - + --> 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..8050c4c --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj @@ -0,0 +1,40 @@ + + + + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + diff --git a/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs new file mode 100644 index 0000000..bf1ddbb --- /dev/null +++ b/tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/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.Tests/Utility/InjectableAnalyzerTest.cs b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableAnalyzerTest.cs similarity index 83% rename from tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/InjectableAnalyzerTest.cs rename to tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableAnalyzerTest.cs index 68bfed1..37ca8ef 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/InjectableAnalyzerTest.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/InjectableAnalyzerTest.cs @@ -19,18 +19,16 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; -using System; -using System.Collections.Generic; -namespace Lucene.Net.CodeAnalysis.Dev.Tests.Utility +namespace Lucene.Net.CodeAnalysis.Dev.TestUtilities { public class InjectableCSharpAnalyzerTest : AnalyzerTest { - private readonly Func createAnalyzer; + private readonly Func analyzerFactory; - public InjectableCSharpAnalyzerTest(Func createAnalyzer) + public InjectableCSharpAnalyzerTest(Func analyzerFactory) { - this.createAnalyzer = createAnalyzer ?? throw new ArgumentNullException(nameof(createAnalyzer)); + this.analyzerFactory = analyzerFactory ?? throw new ArgumentNullException(nameof(analyzerFactory)); } public override string Language => LanguageNames.CSharp; @@ -49,7 +47,7 @@ protected override ParseOptions CreateParseOptions() protected override IEnumerable GetDiagnosticAnalyzers() { - return [createAnalyzer()]; + 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.Tests/Utility/Verifier.cs b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Verifier.cs similarity index 88% rename from tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/Verifier.cs rename to tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Verifier.cs index 154c4fb..7b45bcf 100644 --- a/tests/Lucene.Net.CodeAnalysis.Dev.Tests/Utility/Verifier.cs +++ b/tests/Lucene.Net.CodeAnalysis.Dev.TestUtilities/Verifier.cs @@ -18,13 +18,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; using NUnit.Framework; -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Linq; -namespace Lucene.Net.CodeAnalysis.Dev.Tests.Utility +namespace Lucene.Net.CodeAnalysis.Dev.TestUtilities { public class Verifier : IVerifier { @@ -45,7 +42,7 @@ 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) + public virtual void Equal(T expected, T actual, string? message = null) { if (message is null && Context.IsEmpty) { @@ -53,11 +50,11 @@ public virtual void Equal(T expected, T actual, string message = null) } else { - Assert.That(actual, Is.EqualTo(expected), CreateMessage(message)); + Assert.That(actual, Is.EqualTo(expected), CreateMessage(message!)); } } - public virtual void True([DoesNotReturnIf(false)] bool assert, string message = null) + public virtual void True([DoesNotReturnIf(false)] bool assert, string? message = null) { if (message is null && Context.IsEmpty) { @@ -65,11 +62,11 @@ public virtual void True([DoesNotReturnIf(false)] bool assert, string message = } else { - Assert.That(assert, CreateMessage(message)); + Assert.That(assert, CreateMessage(message!)); } } - public virtual void False([DoesNotReturnIf(true)] bool assert, string message = null) + public virtual void False([DoesNotReturnIf(true)] bool assert, string? message = null) { if (message is null && Context.IsEmpty) { @@ -77,12 +74,12 @@ public virtual void False([DoesNotReturnIf(true)] bool assert, string message = } else { - Assert.That(assert, Is.False, CreateMessage(message)); + Assert.That(assert, Is.False, CreateMessage(message!)); } } [DoesNotReturn] - public virtual void Fail(string message = null) + public virtual void Fail(string? message = null) { if (message is null && Context.IsEmpty) { @@ -90,7 +87,7 @@ public virtual void Fail(string message = null) } else { - Assert.Fail(CreateMessage(message)); + Assert.Fail(CreateMessage(message!)); } throw new InvalidOperationException("This program location is thought to be unreachable."); @@ -106,13 +103,13 @@ 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) + 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)); + Assert.Fail(CreateMessage(message!)); } } @@ -122,7 +119,7 @@ public virtual IVerifier PushContext(string context) return new Verifier(Context.Push(context)); } - protected virtual string CreateMessage(string message) + protected virtual string CreateMessage(string? message) { foreach (var frame in Context) { @@ -136,12 +133,12 @@ private sealed class SequenceEqualEnumerableEqualityComparer : IEqualityCompa { private readonly IEqualityComparer _itemEqualityComparer; - public SequenceEqualEnumerableEqualityComparer(IEqualityComparer itemEqualityComparer) + public SequenceEqualEnumerableEqualityComparer(IEqualityComparer? itemEqualityComparer) { _itemEqualityComparer = itemEqualityComparer ?? EqualityComparer.Default; } - public bool Equals(IEnumerable x, IEnumerable y) + public bool Equals(IEnumerable? x, IEnumerable? y) { if (ReferenceEquals(x, y)) { return true; } if (x is null || y is null) { return false; } 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 478b878..6c42255 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,4 +1,4 @@ - + - - - - From d8c2a4cf8b76a47f41dceff170fe9877b77cd86f Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 16 Sep 2025 19:53:54 +0700 Subject: [PATCH 29/44] SWEEP: Moved all analyzers, code fixes, and analyzer/code fix tests into organizational LuceneDev1xxx folders. --- .../LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs} | 0 .../LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs | 0 .../LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs | 0 .../LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs | 0 .../LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs | 0 .../LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs | 0 .../Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests.csproj | 3 ++- ...stLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs | 0 .../Lucene.Net.CodeAnalysis.Dev.Tests.csproj | 1 + .../TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs | 2 +- .../TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs | 2 +- .../TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs | 2 +- .../TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs | 2 +- .../TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs | 2 +- 14 files changed, 8 insertions(+), 6 deletions(-) rename src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/{LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs => LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs} (100%) rename src/Lucene.Net.CodeAnalysis.Dev/{ => LuceneDev1xxx}/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs (100%) rename src/Lucene.Net.CodeAnalysis.Dev/{ => LuceneDev1xxx}/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs (100%) rename src/Lucene.Net.CodeAnalysis.Dev/{ => LuceneDev1xxx}/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs (100%) rename src/Lucene.Net.CodeAnalysis.Dev/{ => LuceneDev1xxx}/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs (100%) rename src/Lucene.Net.CodeAnalysis.Dev/{ => LuceneDev1xxx}/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs (100%) rename tests/Lucene.Net.CodeAnalysis.Dev.CodeFixes.Tests/{ => LuceneDev1xxx}/TestLuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs (100%) rename tests/Lucene.Net.CodeAnalysis.Dev.Tests/{ => LuceneDev1xxx}/TestLuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs (99%) rename tests/Lucene.Net.CodeAnalysis.Dev.Tests/{ => LuceneDev1xxx}/TestLuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs (98%) rename tests/Lucene.Net.CodeAnalysis.Dev.Tests/{ => LuceneDev1xxx}/TestLuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs (98%) rename tests/Lucene.Net.CodeAnalysis.Dev.Tests/{ => LuceneDev1xxx}/TestLuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs (98%) rename tests/Lucene.Net.CodeAnalysis.Dev.Tests/{ => LuceneDev1xxx}/TestLuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs (98%) diff --git a/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs b/src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs similarity index 100% rename from src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider .cs rename to src/Lucene.Net.CodeAnalysis.Dev.CodeFixes/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeFixProvider.cs diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs similarity index 100% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1000_FloatingPointEqualityCSCodeAnalyzer.cs diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs similarity index 100% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1001_FloatingPointFormattingCSCodeAnalyzer.cs diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs similarity index 100% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1002_FloatingPointArithmeticCSCodeAnalyzer.cs diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs similarity index 100% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1003_ArrayMethodParameterCSCodeAnalyzer.cs diff --git a/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs b/src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs similarity index 100% rename from src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs rename to src/Lucene.Net.CodeAnalysis.Dev/LuceneDev1xxx/LuceneDev1004_ArrayMethodReturnValueCSCodeAnalyzer.cs 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 index 8050c4c..9a30a90 100644 --- 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 @@ -1,4 +1,4 @@ - + + diff --git a/Directory.Build.props b/Directory.Build.props index 1d03497..25245ae 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,3 +1,23 @@ + diff --git a/Directory.Build.targets b/Directory.Build.targets index bffdf7e..4d2df08 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,3 +1,23 @@ + @@ -23,5 +43,5 @@ - + diff --git a/Directory.Packages.props b/Directory.Packages.props index 488cdf4..84f2cd3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,13 +1,33 @@ + true true - + 4.14.0 - + @@ -26,5 +46,5 @@ - + diff --git a/docs/docs.folderproj b/docs/docs.folderproj index 6ef2a5c..8933ebd 100644 --- a/docs/docs.folderproj +++ b/docs/docs.folderproj @@ -1,5 +1,25 @@ - + + - \ No newline at end of file + diff --git a/eng/WildcardVersionSupport.targets b/eng/WildcardVersionSupport.targets index 15a409d..2fac11d 100644 --- a/eng/WildcardVersionSupport.targets +++ b/eng/WildcardVersionSupport.targets @@ -1,4 +1,24 @@ - + + diff --git a/eng/eng.folderproj b/eng/eng.folderproj index 6ef2a5c..8933ebd 100644 --- a/eng/eng.folderproj +++ b/eng/eng.folderproj @@ -1,5 +1,25 @@ - + + - \ No newline at end of file + diff --git a/eng/nuget.props b/eng/nuget.props index 6298e42..5ac7f40 100644 --- a/eng/nuget.props +++ b/eng/nuget.props @@ -1,3 +1,23 @@ + true @@ -13,4 +33,4 @@
-
\ No newline at end of file + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 8370f78..67c7a9d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,3 +1,23 @@ - + + - \ No newline at end of file + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 58569e4..ebdaddf 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,3 +1,23 @@ + @@ -5,5 +25,5 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml $(NoWarn);CS1591 - - \ No newline at end of file + + diff --git a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets index de02e0e..6ca9c6d 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets +++ b/src/Lucene.Net.CodeAnalysis.Dev.Vsix/localized.resources.targets @@ -1,4 +1,24 @@ - + + $(GetVsixSourceItemsDependsOn);IncludeResourcesInVsix 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 e02759f..eb4a28b 100644 --- a/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj +++ b/src/Lucene.Net.CodeAnalysis.Dev/Lucene.Net.CodeAnalysis.Dev.csproj @@ -23,7 +23,7 @@ under the License. netstandard2.0 enable - + false diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index dc40b88..e4914c2 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -1,4 +1,24 @@ - + + - + @@ -11,5 +31,5 @@ true - + From 857205d2b7287545ceea22d2ae2c396da3940476 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Wed, 17 Sep 2025 09:37:06 +0700 Subject: [PATCH 31/44] SWEEP: Added Apache 2.0 license headers to all .yml and .ps1 files --- .github/release.yml | 19 +++++++++++- .github/workflows/ci.yml | 19 +++++++++++- .github/workflows/powershell-tests.yml | 19 +++++++++++- .github/workflows/renovate-dependencies.yml | 17 +++++++++++ eng/Ensure-Powershell-Dependencies.ps1 | 17 +++++++++++ eng/build/Markdown-Formatting.Tests.ps1 | 17 +++++++++++ eng/build/Markdown-Formatting.ps1 | 17 +++++++++++ eng/build/Parse-Test-Results.Tests.ps1 | 17 +++++++++++ eng/build/Parse-Test-Results.ps1 | 19 +++++++++++- rat.ps1 | 34 ++++++++++----------- 10 files changed, 174 insertions(+), 21 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index 3e38098..3f9a332 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -1,3 +1,20 @@ +# 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: @@ -25,4 +42,4 @@ changelog: - notes:website-or-documentation - title: 💪 Other Changes labels: - - "*" \ No newline at end of file + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6878472..1d5153e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,21 @@ -name: CI +# 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: diff --git a/.github/workflows/powershell-tests.yml b/.github/workflows/powershell-tests.yml index a11cdac..2e53b36 100644 --- a/.github/workflows/powershell-tests.yml +++ b/.github/workflows/powershell-tests.yml @@ -1,4 +1,21 @@ -name: Test Powershell Scripts +# 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: diff --git a/.github/workflows/renovate-dependencies.yml b/.github/workflows/renovate-dependencies.yml index e36d0d8..d0e00a4 100644 --- a/.github/workflows/renovate-dependencies.yml +++ b/.github/workflows/renovate-dependencies.yml @@ -1,3 +1,20 @@ +# 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: diff --git a/eng/Ensure-Powershell-Dependencies.ps1 b/eng/Ensure-Powershell-Dependencies.ps1 index 730432f..05c86fa 100644 --- a/eng/Ensure-Powershell-Dependencies.ps1 +++ b/eng/Ensure-Powershell-Dependencies.ps1 @@ -1,3 +1,20 @@ +# 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" ) diff --git a/eng/build/Markdown-Formatting.Tests.ps1 b/eng/build/Markdown-Formatting.Tests.ps1 index cd4e0e2..bc740b4 100644 --- a/eng/build/Markdown-Formatting.Tests.ps1 +++ b/eng/build/Markdown-Formatting.Tests.ps1 @@ -1,3 +1,20 @@ +# 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') } diff --git a/eng/build/Markdown-Formatting.ps1 b/eng/build/Markdown-Formatting.ps1 index 0204163..651e85d 100644 --- a/eng/build/Markdown-Formatting.ps1 +++ b/eng/build/Markdown-Formatting.ps1 @@ -1,3 +1,20 @@ +# 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. diff --git a/eng/build/Parse-Test-Results.Tests.ps1 b/eng/build/Parse-Test-Results.Tests.ps1 index 844c9b4..1489fcf 100644 --- a/eng/build/Parse-Test-Results.Tests.ps1 +++ b/eng/build/Parse-Test-Results.Tests.ps1 @@ -1,3 +1,20 @@ +# 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" { diff --git a/eng/build/Parse-Test-Results.ps1 b/eng/build/Parse-Test-Results.ps1 index e33cd25..67af2ca 100644 --- a/eng/build/Parse-Test-Results.ps1 +++ b/eng/build/Parse-Test-Results.ps1 @@ -1,4 +1,21 @@ -<# +# 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. diff --git a/rat.ps1 b/rat.ps1 index 0f84776..15dd4c6 100644 --- a/rat.ps1 +++ b/rat.ps1 @@ -1,19 +1,19 @@ -<# - 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 - - https://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. -#> +# 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 @@ -104,4 +104,4 @@ if ($useExclude) { if ($LASTEXITCODE -ne 0) { throw "RAT exited with code $LASTEXITCODE" -} \ No newline at end of file +} From f92626bd04253594acb60c3f5d3a5a425a89bf9a Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Wed, 17 Sep 2025 09:48:28 +0700 Subject: [PATCH 32/44] SWEEP: Added Apache 2.0 license headers to all .md files (except AnalyzerReleases which require a specific format to be parsed) --- README.md | 21 +++++++++++++++++++++ docs/building-and-testing.md | 21 +++++++++++++++++++++ docs/make-release.md | 21 +++++++++++++++++++++ docs/visual-studio-debugging.md | 21 +++++++++++++++++++++ 4 files changed, 84 insertions(+) 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/docs/building-and-testing.md b/docs/building-and-testing.md index 2c4db98..a984a7a 100644 --- a/docs/building-and-testing.md +++ b/docs/building-and-testing.md @@ -1,3 +1,24 @@ + + # Building and Testing ## Command Line diff --git a/docs/make-release.md b/docs/make-release.md index c50d272..9404e1a 100644 --- a/docs/make-release.md +++ b/docs/make-release.md @@ -1,3 +1,24 @@ + + # Making a 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 builds on the main branch and production releases on release branches. diff --git a/docs/visual-studio-debugging.md b/docs/visual-studio-debugging.md index e45e277..346d410 100644 --- a/docs/visual-studio-debugging.md +++ b/docs/visual-studio-debugging.md @@ -1,3 +1,24 @@ + + # 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. From fd6839081fdfbc0335a7fded833eb88690951601 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Wed, 17 Sep 2025 10:56:08 +0700 Subject: [PATCH 33/44] eng/nuget.props: Added LICENSE.txt and NOTICE.txt to the NuGet package. --- eng/nuget.props | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eng/nuget.props b/eng/nuget.props index 5ac7f40..78cc527 100644 --- a/eng/nuget.props +++ b/eng/nuget.props @@ -25,12 +25,16 @@ https://lucenenet.apache.org lucene-net-icon-128x128.png Apache-2.0 + readme.md + LICENSE.txt + NOTICE.txt https://github.com/$(GitHubOrganization)/$(GitHubProject)/releases/tag/v$(PackageVersion) $(ReleaseNotesUrl) - readme.md + + From a2e41249ff7b4d8f1c9dfbdd243d50ca2b00d7cc Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Wed, 17 Sep 2025 11:03:13 +0700 Subject: [PATCH 34/44] eng/nuget.props: Resolved conflict - cannot specify PackageLicenseExpression and PackageLicenseFile at the same time. Removed the PackageLicenseFile property, but kept the automation to pack the file into the NuGet package. --- eng/nuget.props | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eng/nuget.props b/eng/nuget.props index 78cc527..d124208 100644 --- a/eng/nuget.props +++ b/eng/nuget.props @@ -26,14 +26,13 @@ lucene-net-icon-128x128.png Apache-2.0 readme.md - LICENSE.txt NOTICE.txt https://github.com/$(GitHubOrganization)/$(GitHubProject)/releases/tag/v$(PackageVersion) $(ReleaseNotesUrl) - + From a8f65eb52b128e79b9a9f70148fdb1c784d94b27 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Wed, 17 Sep 2025 18:57:29 +0700 Subject: [PATCH 35/44] Lucene.Net.CodeAnalysis.Dev.sln: Added .editorconfig and .gitattributes files to Solution Items --- Lucene.Net.CodeAnalysis.Dev.sln | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lucene.Net.CodeAnalysis.Dev.sln b/Lucene.Net.CodeAnalysis.Dev.sln index ca4448a..31531c0 100644 --- a/Lucene.Net.CodeAnalysis.Dev.sln +++ b/Lucene.Net.CodeAnalysis.Dev.sln @@ -48,6 +48,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.CodeAnalysis.Dev 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 From f161049308b33febbe6aa6eff7fc4acd4c717d3e Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Wed, 17 Sep 2025 19:12:14 +0700 Subject: [PATCH 36/44] Added Git commit hook to insert the current NuGetPackageVersion into a placeholder token {{vnext}} in the AnalyzerReleases.Shipped.md file, only if preceeded by "## Release ". --- .editorconfig | 21 +++++++++++- .gitattributes | 8 +++++ eng/eng.folderproj | 2 +- eng/git-hooks/post-commit | 71 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100755 eng/git-hooks/post-commit diff --git a/.editorconfig b/.editorconfig index 499ae6b..fc10450 100644 --- a/.editorconfig +++ b/.editorconfig @@ -146,4 +146,23 @@ indent_style = space indent_size = 4 trim_trailing_whitespace = false insert_final_newline = true -resharper_enforce_empty_line_at_end_of_file = true \ No newline at end of file +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 index 3fec4c2..4947c44 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,3 +27,11 @@ # 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/eng/eng.folderproj b/eng/eng.folderproj index 8933ebd..b5711dc 100644 --- a/eng/eng.folderproj +++ b/eng/eng.folderproj @@ -22,4 +22,4 @@ - + \ 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 < Date: Thu, 18 Sep 2025 14:36:20 +0700 Subject: [PATCH 37/44] docs/make-release.md: Simplified release procedure and included step to update AnalyzerReleases documents. --- docs/make-release.md | 225 +++++++++++++++++++++++++++++++------------ 1 file changed, 166 insertions(+), 59 deletions(-) diff --git a/docs/make-release.md b/docs/make-release.md index 9404e1a..55fe8f1 100644 --- a/docs/make-release.md +++ b/docs/make-release.md @@ -21,7 +21,8 @@ # Making a 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 builds on the main branch and production releases on release branches. +> [!NOTE] +> All commands should be executed from the root of the repository unless otherwise stated. ## Prerequisites @@ -29,46 +30,69 @@ This project uses Nerdbank.GitVersioning to assist with creating version numbers - [.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. If on Windows, this will automatically be installed by [Git for Windows](https://gitforwindows.org/). -### Installing NBGV Tool +### 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 ``` -## Run the Apache Release Audit Tool +### 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. -The Release Audit Tool will ensure that all non-generated text files have a license header. +Check whether the Git `core.hooksPath` is correctly set: ```console -pwsh ./rat.ps1 +git config core.hooksPath ``` -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. +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. -- Exclude files that already have license headers -- Exclude files that are automatically generated -- Exclude files that don't work properly with license headers included (such as test data) +```console +git config core.hooksPath ./eng/git-hooks +``` + +Repeat the first command to confirm that it is set. + +--------------------------------------------- + +## Prior to Release -## Versioning Primer +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. -This project uses [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html) and strictly adheres to the guidelines about bumping the major, minor and build numbers of the version number. +### Prepare the Main Branch -The assembly version should remain the same in all cases, except when the major version changes, so that it can be used as a drop-in replacement. +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. -## Creating a Release Branch +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. + +## Create a Release Branch ### Release Workflow Overview ![Release Workflow](images/release-workflow.svg) +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 + + + ### 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://github.com/dotnet/Nerdbank.GitVersioning/blob/master/doc/nbgv-cli.md). +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 setup 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}`. +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 @@ -81,11 +105,11 @@ release/v2.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`. Every build from this branch (regardless of how many commits are added) will be versioned 2.0.0. +The tool created a release branch named `release/v2.0`. Every build from this branch will be versioned 2.0.x, but the patch version may be incremented depending on the number of additional commits that will be 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://github.com/dotnet/Nerdbank.GitVersioning/blob/master/doc/nbgv-cli.md#preparing-a-release)). Starting from the same point as the [Ready to Release](#ready-to-release) scenario, we use the following command. +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 @@ -95,43 +119,112 @@ The command should respond with: ```console release/v2.0 branch now tracks v2.0.0-beta.{height} stabilization and release. -main branch now tracks v2.0.0-alpha.{height} development. +main branch now tracks v2.0.1-alpha.{height} development. ``` The tool created a release branch named `release/v2.0`. Every build from 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. -### Bumping the Version Manually - -When releasing a version that does not directly follow the current release version, manually update the `version` (and `assemblyVersion` if this is a major version bump) in `version.json` before creating the release branch. See the [version.json schema](https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json) to determine valid options. +--------------------------------------------- -## Correcting the Release Version Height +## Run the Apache Release Audit Tool -Nerdbank.GitVersioning is designed in a way that it doesn't produce the same version number twice. This is done by using a "git height", which counts the number of commits since the last version update. This works great for CI, but is less than ideal when we don't want to skip over versions for the release. +> [!IMPORTANT] +> This command depends on Powershell and Java. -Since Nerdbank.GitVersioning considers each commit a new "version," the `versionHeightOffset` can be adjusted on the release branch to ensure the release uses the correct version number. This can be done by using the following command to see what version we are currently on, and then adjusting the value accordingly. +The Release Audit Tool will ensure that all source code files and most other non-generated text files contain a license header. ```console -nbgv get-version +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] -> Officially, it is not recommended to use `versionHeightOffset` and in general that is true. However, using it in a narrow scope, such as on a release branch should be okay. In practice, users will not build from these branches, they will build from a release tag. +> 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. -Then open the `version.json` file at the repository root, and set the `versionHeightOffset` using the formula `versionHeightOffset - ((versionHeight - desiredHeight) + 1)`. For example, if the current version is 2.0.1-beta.14 and we want to release 2.0.1-beta.5 (because the last version released was 2.0.1-beta.4), and the `versionHeightOffset` is set to -21: -###### Calculating versionHeightOffset -``` --21 - ((14 - 5) + 1) = -31 +## 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 guarantees that the release metadata reflects the rules included 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}} ``` -So, we must set `versionHeightOffset` to -31 and commit the change. +### Standard Workflow -Note that the + 1 is because we are creating a new commit that will increment the number by 1. The change must be committed to see the change to the version number. Run the command again to check that the version will be correct. +> [!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. -```console -nbgv get-version +> [!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: @@ -142,32 +235,26 @@ The release process is mostly automated. However, a manual review is required on 4. Abort the release to try again 5. Publish the release to deploy the packages to NuGet.org -## Create a Draft Release +### 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. -### Tagging the Commit +#### Tag the HEAD Commit -If you don't already know the version that corresponds to the HEAD commit, check it now. +Run the following command to tag the HEAD commit. ```console -nbgv get-version +nbgv tag ``` -The reply will show a table of version information. +> [!NOTE] +> The release build workflow always uses the HEAD commit. -```console -Version: 2.0.0 -AssemblyVersion: 2.0.0.0 -AssemblyInformationalVersion: 2.0.0-beta.5+a54c015802 -NuGetPackageVersion: 2.0.0-beta.5 -NpmPackageVersion: 2.0.0-beta.5 -``` +#### Push the Release Branch to the Upstream Repository -Tag the commit with `v` followed by the NuGetPackageVersion. +The final step to begin the release build is to push the tag and any new commits to the upstream repository. -```console -git tag -a v -m "v" +```c# git push --follow-tags ``` @@ -179,11 +266,18 @@ The push will start the automated draft release which will take a few minutes. W > [!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. +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 @@ -205,7 +299,7 @@ Review the draft release notes and edit or regenerate them if necessary. release 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. Check to ensure the NuGet packages can be referenced by a new project and the project will compile. +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. @@ -218,6 +312,8 @@ Once everything is in order, the release can be published, which will deploy the 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. @@ -226,28 +322,39 @@ If the build failed in any way, the release can be restarted by deleting the tag ##### Delete the Failed Tag -Since the tag didn't make it to release, it is important to delete it to avoid a confusing release history. It is required to be removed if the next attempt will be for the same version number. +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 ``` -##### Push a New Version Tag +##### Resetting the Version in `AnalyzerReleases.Shipped.md` -> [!NOTE] -> The same version number can be reused if there weren't any commits required to correct the release problem or if the `versionHeightOffset` is changed as described above. +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}} +``` -Once the issues have been addressed to fix the build and reset the version (if necessary), follow the same procedure starting at [Tagging the Commit](#tagging-the-commit) to restart the draft release. +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 GitHub. +Finally, merge the release branch back into the main branch and push the changes to the upstream repository. ```console -git checkout +git checkout main git merge -git push +git push main ``` + +### 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). From c26a0c99662ab216db9f00a12fb64ff0baf53925 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 18 Sep 2025 15:48:10 +0700 Subject: [PATCH 38/44] version.json: Updated version.json to enforce 3-component release branch numbers. --- version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.json b/version.json index 396a311..489db69 100644 --- a/version.json +++ b/version.json @@ -10,7 +10,7 @@ "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/master$", - "^refs/heads/release/v\\d+(?:\\.\\d+)?$" + "^refs/heads/release/v\\d+\\.\\d+\\.\\d+$" ], "cloudBuild": { "buildNumber": { @@ -20,4 +20,4 @@ "release": { "branchName": "release/v{version}" } -} \ No newline at end of file +} From ab7f0b1bdc6b7c5cff3f8601e18f89c564c952d1 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 18 Sep 2025 17:44:32 +0700 Subject: [PATCH 39/44] docs/make-release: Added workflow for release build outcomes. Also corrected release branching info to show branches with 3 version components. --- docs/images/release-build-outcomes.md | 16 ++++ docs/images/release-build-outcomes.svg | 102 +++++++++++++++++++++++++ docs/images/release-workflow.md | 16 ++++ docs/make-release.md | 12 ++- 4 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 docs/images/release-build-outcomes.md create mode 100644 docs/images/release-build-outcomes.svg create mode 100644 docs/images/release-workflow.md diff --git a/docs/images/release-build-outcomes.md b/docs/images/release-build-outcomes.md new file mode 100644 index 0000000..e5b0ac1 --- /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 +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/make-release.md b/docs/make-release.md index 55fe8f1..7511c20 100644 --- a/docs/make-release.md +++ b/docs/make-release.md @@ -101,11 +101,11 @@ nbgv prepare-release --nextVersion 2.0.1 The command should respond with: ```console -release/v2.0 branch now tracks v2.0.0 stabilization and release. +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`. Every build from this branch will be versioned 2.0.x, but the patch version may be incremented depending on the number of additional commits that will be added. +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 @@ -118,11 +118,11 @@ nbgv prepare-release beta --nextVersion 2.0.1 The command should respond with: ```console -release/v2.0 branch now tracks v2.0.0-beta.{height} stabilization and release. +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`. Every build from 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. +The tool created a release branch named `release/v2.0.0`. Every build from 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. --------------------------------------------- @@ -235,6 +235,10 @@ The release process is mostly automated. However, a manual review is required on 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. From 297c4f33e4364512f132d3aafb6082209467a0de Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 18 Sep 2025 19:42:33 +0700 Subject: [PATCH 40/44] docs/make-release.md: Added info about deciding between major, minor, patch, and prerelease when choosing a version number. --- docs/make-release.md | 90 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/docs/make-release.md b/docs/make-release.md index 7511c20..529ffcb 100644 --- a/docs/make-release.md +++ b/docs/make-release.md @@ -30,7 +30,7 @@ - [.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. If on Windows, this will automatically be installed by [Git for Windows](https://gitforwindows.org/). +- Bash (Installed automatically with [Git for Windows](https://gitforwindows.org/) on Windows). ### Installing the NBGV Tool @@ -67,6 +67,10 @@ Repeat the first command to confirm that it is set. 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. @@ -75,18 +79,59 @@ This project uses Nerdbank.GitVersioning to assist with creating version numbers 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. -## Create a Release Branch +### Decide on a Release Version -### Release Workflow Overview +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. -![Release Workflow](images/release-workflow.svg) +> [!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 @@ -122,7 +167,15 @@ 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 build from 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. +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 +``` --------------------------------------------- @@ -157,7 +210,7 @@ Roslyn analyzers use two release tracking files to manage analyzer rule metadata - **`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 guarantees that the release metadata reflects the rules included in the NuGet package. +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. @@ -245,20 +298,20 @@ Tagging the commit and pushing it to the GitHub repository will start the automa #### Tag the HEAD Commit -Run the following command to 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 uses the HEAD commit. +> 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. -```c# +```console git push --follow-tags ``` @@ -285,6 +338,8 @@ Review the draft release notes and edit or regenerate them if necessary. Release ##### 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 | @@ -353,12 +408,27 @@ Next, follow the same procedure starting at [Tag the HEAD Commit](#tag-the-head- 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). From 77c313e8c851cb65d69fe1ead068d6ad3dc1f1e8 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 18 Sep 2025 19:56:03 +0700 Subject: [PATCH 41/44] release-build-outcomes.md: Updated Mermaid markup to reflect the same state as the release-build-outcomes.svg file. --- docs/images/release-build-outcomes.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/images/release-build-outcomes.md b/docs/images/release-build-outcomes.md index e5b0ac1..9fcc163 100644 --- a/docs/images/release-build-outcomes.md +++ b/docs/images/release-build-outcomes.md @@ -2,15 +2,15 @@ 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"] + 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 ``` From 7c77b46ca6591fa1ce64ea734018dbc6ae70addc Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 18 Sep 2025 20:18:40 +0700 Subject: [PATCH 42/44] Markdown-Formatting.Tests.ps1: Removed stray assert --- eng/build/Markdown-Formatting.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/build/Markdown-Formatting.Tests.ps1 b/eng/build/Markdown-Formatting.Tests.ps1 index bc740b4..d815613 100644 --- a/eng/build/Markdown-Formatting.Tests.ps1 +++ b/eng/build/Markdown-Formatting.Tests.ps1 @@ -42,7 +42,6 @@ Describe "Format-Test-Results" { $output | Should -Match "Passed=$($_.PassedCount)" $output | Should -Match "Failed=$($_.FailedCount)" $output | Should -Match "Ignored=$($_.IgnoredCount)" - "" | Should -Be "" } Context "respects custom status text/icons" { From d8041a77f55b855135243d1e0e0bebc115d0f1fa Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Thu, 18 Sep 2025 20:24:08 +0700 Subject: [PATCH 43/44] .github/workflows/ci.yml: Removed x86 tests and SDK setup --- .github/workflows/ci.yml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d5153e..8fff969 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,15 +113,9 @@ jobs: matrix: # macos-13 is specifically for running x64, macos-latest for arm64 os: [windows-latest, ubuntu-latest, macos-13, macos-latest] - arch: [x64, x86, arm64] + arch: [x64, arm64] tfm: [net8.0] exclude: - - arch: x86 - os: ubuntu-latest - - arch: x86 - os: macos-latest - - arch: x86 - os: macos-13 - arch: arm64 os: macos-13 - arch: arm64 @@ -177,18 +171,9 @@ jobs: # Install the .NET SDK - name: Setup .NET 8.0 - if: matrix.arch != 'x86' - uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 - with: - dotnet-version: 8.0.x - - - name: Setup .NET 8.0 (x86) - if: matrix.arch == 'x86' uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5 with: dotnet-version: 8.0.x - env: - PROCESSOR_ARCHITECTURE: x86 - name: Download Test Binaries uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 From 6e481079df9c9a203383e6d2e6e6c90bfce84384 Mon Sep 17 00:00:00 2001 From: Shad Storhaug Date: Tue, 23 Sep 2025 10:01:04 +0700 Subject: [PATCH 44/44] docs/make-release.md: Corrected slash in release branch notes --- docs/make-release.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/make-release.md b/docs/make-release.md index 529ffcb..c9fdef4 100644 --- a/docs/make-release.md +++ b/docs/make-release.md @@ -409,7 +409,7 @@ Next, follow the same procedure starting at [Tag the HEAD Commit](#tag-the-head- 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`. +> Release branches start with `release/v`. ```console git checkout main @@ -422,7 +422,7 @@ git push main 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`. +> Release branches start with `release/v`. Take care not to delete the tag, which starts with a `v`. ```console git branch -d