Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/scratchpad/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
<UsePublicApiAnalyzers>false</UsePublicApiAnalyzers>
<IncludeNuGetPackSupport>false</IncludeNuGetPackSupport>

<!--
Scratch code does not justify its null-forgiving operators. Nullable.Extended.Analyzer would report each
one as an error.
-->
<UseNullableExtendedAnalyzer>false</UseNullableExtendedAnalyzer>

<!--
A scratch Exe that gets packed would otherwise write its asset list into the artifacts directory of the
repository, where bv release reads it.
Expand Down
19 changes: 16 additions & 3 deletions .globalconfig
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,7 @@ dotnet_naming_style.begins_with_i.capitalization = pascal_case
# ==============================================================================================
# STYLECOP ANALYZERS
# ----------------------------------------------------------------------------------------------
# This is the single source of truth for StyleCop.Analyzers severities.
# DO NOT set severities in .DotSettings files, as precedence rules are unclear.
# This is the single source of truth for StyleCop.Analyzers settings and severities.
# Ordering and grouping follows the categories listed in the official documentation.
# See https://github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/documentation
# ----------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -517,7 +516,6 @@ dotnet_diagnostic.SA1654.severity = warning # Properly encode generic brackets
# PUBLIC API ANALYZERS
# ----------------------------------------------------------------------------------------------
# This is the single source of truth for Public API Analyzer severities.
# DO NOT set severities in .DotSettings files, as precedence rules are unclear.
# We only list non-default severities here.
# See https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.md
# ----------------------------------------------------------------------------------------------
Expand All @@ -539,6 +537,21 @@ dotnet_diagnostic.RS0056.severity = warning # Enable tracking of nullability of
dotnet_diagnostic.RS0057.severity = warning # Internal members should not use oblivious types
dotnet_diagnostic.RS0058.severity = warning # Missing shipped or unshipped internal API file

# ==============================================================================================
# NULLABLE.EXTENDED ANALYZER
# ----------------------------------------------------------------------------------------------
# This is the single source of truth for Nullable.Extended.Analyzer severities.
# Every rule is listed, defaults included.
# See https://github.com/tom-englert/Nullable.Extended
# ----------------------------------------------------------------------------------------------
# Last update: 2026-10-26 (Nullable.Extended.Analyzer 1.18.2)
# ==============================================================================================

dotnet_diagnostic.NX0001.severity = warning # Instance of NullForgiving operator without justification detected
dotnet_diagnostic.NX0002.severity = warning # Instance of NullForgiving operator without justification detected
dotnet_diagnostic.NX0003.severity = warning # Instance of NullForgiving operator without justification detected
dotnet_diagnostic.NX0004.severity = warning # Instance of NullForgiving operator without justification detected

# ==============================================================================================
# RESHARPER INSPECTIONS
# ----------------------------------------------------------------------------------------------
Expand Down
17 changes: 17 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"mcpServers": {
"nullable-extended": {
"type": "stdio",
"command": "dotnet",
"args": [
"tool",
"run",
"nullable-extended-mcp",
"--root",
".",
"--solution",
"Buildvana.slnx"
]
}
}
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ The patch number restarted from 1, below the 2.0.x versions already published.
- **BREAKING CHANGE**: Buildvana SDK raises BVSDK1006 on a project whose [SDK does not layer on `Microsoft.NET.Sdk`](docs/introduction.md#project-types), where such a project used to build.
- **BREAKING CHANGE**: A file-based app is a [project type of its own](docs/internal-use-properties.md#project-type), `BV_IsFileBasedAppProject`, so `BV_IsExeProject` is `false` for it.
- **BREAKING CHANGE**: `bv` no longer reads `.config/dotnet-tools.json`, and stops with an error that [names the move](docs/directory-structure.md#migration-from-configdotnet-toolsjson) when the home directory holds one.
- **BREAKING CHANGE**: The `StandardAnalyzers` module adds [Nullable.Extended.Analyzer](docs/sdk-modules/standard-analyzers.md#usenullableextendedanalyzer-property), so a null-forgiving operator without a justification comment no longer compiles without a warning.
- The [`ReleaseAssetList` module](docs/sdk-modules/release-asset-list.md#generatereleaseassetlist-property) writes no release asset list for a file-based app.
- On GitHub, `bv release` refuses [two assets with the same file name](docs/tool-commands/release.md#publishing-on-github) before it pushes the commits.
- A [`notice:` level](docs/command-line.md#verbosity), shown from `minimal` verbosity up, sits between `warning:` and `info:`.
Expand Down
3 changes: 3 additions & 0 deletions Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile> <!--Do not generate XML documentation -->
<UsePublicApiAnalyzers>false</UsePublicApiAnalyzers>

<!-- The null-forgiving operators of this repository still lack a "// !" justification comment. -->
<UseNullableExtendedAnalyzer>false</UseNullableExtendedAnalyzer>
</PropertyGroup>

<!-- Target frameworks -->
Expand Down
46 changes: 42 additions & 4 deletions docs/sdk-modules/standard-analyzers.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `StandardAnalyzers` module

This module turns on the code quality and code style analyzers of the .NET SDK, StyleCop, and the public API analyzers.
This module turns on the code quality and code style analyzers of the .NET SDK, StyleCop, the public API analyzers, and Nullable.Extended.Analyzer.
It sets stricter defaults than the .NET SDK, and adds the `stylecop.json` file and the public and internal API files to the compilation.

---
Expand All @@ -13,20 +13,22 @@ It sets stricter defaults than the .NET SDK, and adds the `stylecop.json` file a
- [Code analysis properties](#code-analysis-properties)
- [`UseStyleCopAnalyzers` property](#usestylecopanalyzers-property)
- [`UsePublicApiAnalyzers` property](#usepublicapianalyzers-property)
- [`UseNullableExtendedAnalyzer` property](#usenullableextendedanalyzer-property)
- [`UseTfmSpecificPublicApiFiles` property](#usetfmspecificpublicapifiles-property)
- [`UseTfmSpecificInternalApiFiles` property](#usetfmspecificinternalapifiles-property)
- [Usage](#usage)
- [Package references](#package-references)
- [`stylecop.json` file](#stylecopjson-file)
- [Public API files](#public-api-files)
- [Internal API files](#internal-api-files)
- [Null-forgiving operators](#null-forgiving-operators)
- [Diagnostics](#diagnostics)

---

## Configuration

The module is active in every project, and each of the three analyzer sets has a switch of its own.
The module is active in every project, and each of the four analyzer sets has a switch of its own.
Configure the rules of every set in an `.editorconfig` file or a `.globalconfig` file, as the [.NET documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files) describes.
A severity in an `.editorconfig` file reaches only a diagnostic located in a source file.
So configure RS0052, RS0053, RS0054, and RS0058 in a `.globalconfig` file, because they report a line of an internal API file, or no location.
Expand Down Expand Up @@ -62,6 +64,12 @@ Set it to `true` to run the public API analyzers in a project, or to `false` to
The default is `true` for a [library project](../internal-use-properties.md#project-type), and `false` for every other project.
A value MSBuild reads as true, such as `yes`, counts as `true`, and any other value counts as `false`.

### `UseNullableExtendedAnalyzer` property

Set it to `false` to leave Nullable.Extended.Analyzer out of a project.
The default is `true`, except for a project that uses the `Microsoft.Build.NoTargets` SDK.
A value MSBuild reads as true, such as `yes`, counts as `true`, and any other value counts as `false`.

### `UseTfmSpecificPublicApiFiles` property

Set it to `true` to keep one pair of [public API files](#public-api-files) per target framework, or to `false` to keep one pair for the project.
Expand All @@ -80,10 +88,17 @@ A value MSBuild reads as true, such as `yes`, counts as `true`, and any other va

### Package references

The module adds a `PackageReference` to `StyleCop.Analyzers` when `UseStyleCopAnalyzers` is `true`, and one to `Microsoft.CodeAnalysis.PublicApiAnalyzers` when `UsePublicApiAnalyzers` is `true`.
The module adds a `PackageReference` to each analyzer package whose switch is `true`:

| Package | Switch |
| ------------------------------------------- | ----------------------------- |
| `StyleCop.Analyzers` | `UseStyleCopAnalyzers` |
| `Microsoft.CodeAnalysis.PublicApiAnalyzers` | `UsePublicApiAnalyzers` |
| `Nullable.Extended.Analyzer` | `UseNullableExtendedAnalyzer` |

Buildvana SDK pins the version of each package, and `dotnet list package` shows the reference as auto-referenced, with the `(A)` mark.
Each reference has `PrivateAssets="All"`, so a project that references this one, and a NuGet package this one produces, get no dependency on the analyzers.
A `PackageReference` of yours to either package replaces the module's reference, and under central package management a `PackageVersion` item sets its version.
A `PackageReference` of yours to one of the packages replaces the module's reference, and under central package management a `PackageVersion` item sets its version.

The code quality and code style analyzers ship with the .NET SDK, and need no package.

Expand Down Expand Up @@ -149,6 +164,29 @@ Without `require_api_files`, RS0051 reports every internal type and member of a
With `require_api_files` set to `true`, a project with neither file of a pair gets no diagnostic for that kind of API.
A project with one file of the internal pair still gets RS0058.

### Null-forgiving operators

Nullable.Extended.Analyzer reports every null-forgiving operator (`!`) as a warning.
The operator silences a nullable warning on the strength of an invariant the compiler cannot check, and the analyzer makes each such invariant visible.
Four rules report the operator, one per context:

| Rule | Context |
| ------ | ---------------------------------------------------------------------------------------------- |
| NX0001 | Any context the other three rules do not cover |
| NX0002 | On `null`, `default`, or `default(T)` |
| NX0003 | Inside a lambda expression, on any other operand |
| NX0004 | On `null`, `default`, or `default(T)` in the initializer of a property with an `init` accessor |

A comment on the line above the statement or declaration clears the warning, when its text starts with `!` and a space:

```csharp
// ! The dictionary was filled from the same keys a moment ago.
var value = lookup.GetValueOrDefault(key)!;
```

The [Nullable.Extended documentation](https://github.com/tom-englert/Nullable.Extended) describes the analyzer and its code fixes.
Set the severity of each rule in a `.editorconfig` file or a `.globalconfig` file, as [Configuration](#configuration) says.

---

## Diagnostics
Expand Down
6 changes: 6 additions & 0 deletions dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"commands": [
"bv"
]
},
"nullable.extended.mcp": {
"version": "1.18.2",
"commands": [
"nullable-extended-mcp"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>

<!-- Include Nullable.Extended.Analyzer, which reports every use of the null-forgiving operator. -->
<ItemGroup>
<BV_PackageReference Include="Nullable.Extended.Analyzer"
PrivateAssets="All"
IncludeAssets="runtime; build; native; contentfiles; analyzers" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions src/Buildvana.Sdk/Modules/StandardAnalyzers/Module.targets
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,20 @@
<Import Project="$(MSBuildThisFileDirectory)Module.UsePublicApiAnalyzers.targets"
Condition="$(UsePublicApiAnalyzers)" />

<!-- UseNullableExtendedAnalyzer default = true, except for no-target projects. -->
<PropertyGroup Condition="'$(UseNullableExtendedAnalyzer)' == ''">
<UseNullableExtendedAnalyzer>true</UseNullableExtendedAnalyzer>
<UseNullableExtendedAnalyzer Condition="$(BV_IsNoTargetsProject)">false</UseNullableExtendedAnalyzer>
</PropertyGroup>
<!-- Store 'true' for any value MSBuild compares equal to it, such as 'yes'. -->
<PropertyGroup Condition="'$(UseNullableExtendedAnalyzer)' == 'true'">
<UseNullableExtendedAnalyzer>true</UseNullableExtendedAnalyzer>
</PropertyGroup>
<PropertyGroup Condition="'$(UseNullableExtendedAnalyzer)' != 'true'">
<UseNullableExtendedAnalyzer>false</UseNullableExtendedAnalyzer>
</PropertyGroup>

<Import Project="$(MSBuildThisFileDirectory)Module.UseNullableExtendedAnalyzer.targets"
Condition="$(UseNullableExtendedAnalyzer)" />

</Project>
1 change: 1 addition & 0 deletions src/Buildvana.Sdk/Sdk/PackageVersions.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
-->
<ItemGroup>
<BV_PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="5.6.0" />
<BV_PackageVersion Include="Nullable.Extended.Analyzer" Version="1.18.2" />
<BV_PackageVersion Include="StyleCop.Analyzers" Version="1.3.133-beta" />
<BV_PackageVersion Include="Tools.InnoSetup" Version="7.1.0" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions tests/Buildvana.Sdk.Tests/Buildvana.Sdk.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<AssemblyMetadata Include="RealNuGetPackPackableProjectTargetsPath" Value="$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\Buildvana.Sdk\Modules\NuGetPack\Module.Core.PackableProject.targets'))" />
<AssemblyMetadata Include="RealReleaseAssetListModuleTargetsPath" Value="$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\Buildvana.Sdk\Modules\ReleaseAssetList\Module.targets'))" />
<AssemblyMetadata Include="RealSourceGeneratorsAfterModulesCoreTargetsPath" Value="$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\Buildvana.Sdk\Modules\SourceGenerators\AfterModules.Core.targets'))" />
<AssemblyMetadata Include="RealStandardAnalyzersModuleTargetsPath" Value="$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\Buildvana.Sdk\Modules\StandardAnalyzers\Module.targets'))" />
<AssemblyMetadata Include="RealStandardAnalyzersUsePublicApiAnalyzersTargetsPath" Value="$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\Buildvana.Sdk\Modules\StandardAnalyzers\Module.UsePublicApiAnalyzers.targets'))" />
<AssemblyMetadata Include="RealXmlDocumentationModuleTargetsPath" Value="$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\..\src\Buildvana.Sdk\Modules\XmlDocumentation\Module.targets'))" />
</ItemGroup>
Expand Down
Loading
Loading