Skip to content

Enable nullable, analyzers, and code style enforcement - #37

Merged
tillig merged 6 commits into
mainfrom
feature/code-standards
Sep 1, 2026
Merged

Enable nullable, analyzers, and code style enforcement#37
tillig merged 6 commits into
mainfrom
feature/code-standards

Conversation

@tillig

@tillig tillig commented Sep 1, 2026

Copy link
Copy Markdown
Member

Part of #32

Proposed Changes

A new Directory.Build.props — there was none — carrying LangVersion, Nullable, AnalysisLevel, EnforceCodeStyleInBuild, and TreatWarningsAsErrors in Release, so policy lives in one place instead of fifteen csproj files. All 15 projects now build with zero warnings in Release, where before there were 218.

Five commits, ordered so the mechanical churn is separable from the real fixes:

  1. Directory.Build.props. LangVersion=latest is load-bearing, not cosmetic — see below.
  2. File-scoped namespaces and redundant usings. 78 files, entirely dotnet format output plus a scripted using removal. Listed in .git-blame-ignore-revs.
  3. The blame-ignore entry.
  4. The actual warning fixes. 13 files, 23 insertions.
  5. WebFormsExample assembly name. Both RootNamespace and AssemblyName said MvcExample.

Why LangVersion had to move first

net481 and netstandard2.0 default to C# 7.3, and Nullable=enable under 7.3 is a hard build error (CS8630), not a warning. So a global Nullable property would have broken 8 of the 15 projects. Raising LangVersion also unblocked IDE0161, which is why file-scoped namespaces finally reach the .NET Framework examples — dotnet format had been silently unable to touch them for four PRs. The six files still using block scope are tool-generated designer and WCF proxies, which Roslyn excludes from style rules by design.

Judgment calls worth reviewing

Five suppressed rules, documented inline in the props file. CA1707/CA1716 are names ASP.NET dictates: Web Forms code-behind for Default.aspx is _Default, AutoEventWireup requires Page_Load, and Global.asax expects a type called Global. CA1848/CA1873 are logging-performance boilerplate — right in production, pure noise wrapped around a two-line sample, and avoiding exactly that was the point of choosing latest-recommended. CA5368 wants a per-user ViewStateUserKey and these samples have no user identity to key on.

CA3147 was a real finding. The MVC About and Index actions had no [HttpGet], so they also accepted POST. Adding the attribute is the honest fix; adding an antiforgery token to a read-only view would not have been.

The multitenant console app got a small refactor. It held its container and tenant strategy in nullable statics assigned from Main, so every use needed a null check the design never permitted. They're now readonly fields initialized at their declaration, with the strategy passed into ConfigureDependencies instead of read back off a field — which also resolved CA1859.

NU1900 is excluded from TreatWarningsAsErrors. It reports that the vulnerability audit feed was unreachable, and it fired locally as a 403. A build shouldn't fail because a feed rate-limited us.

Verification

Zero warnings in Release across all 15 projects, and both gates pass. Every runnable example was executed, not just compiled: the Configuration example still resolves both plugins, AttributeMetadata still exercises all five metadata paths, the multitenant console app still resolves tenant 1, tenant 2, and default to distinct overrides, and all three ASP.NET Core apps still serve their endpoints. That last set mattered most for the multitenant refactor, since moving initialization out of Main changes when the container is built.

The six .NET Framework projects are build-verified only, as before.

LangVersion=latest is what makes the rest possible: net481 and netstandard2.0
default to C# 7.3, and Nullable=enable is a hard build error (CS8630) below C# 8.
Raising it also lets IDE0161 reach the .NET Framework examples, so file-scoped
namespaces finally apply everywhere.

AnalysisLevel is latest-recommended rather than latest-all. The five suppressed
rules are documented inline; the theme is that ASP.NET dictates certain names,
and logging-performance boilerplate belongs in production code rather than in a
ten-line teaching sample.

NU1900 is excluded from TreatWarningsAsErrors. It reports that the vulnerability
audit feed was unreachable, which shouldn't fail a build.

Part of #32
Mechanical follow-on from raising LangVersion. dotnet format converted the 57
remaining block-scoped namespace files; the six it skipped are tool-generated
designer and WCF proxy files, which Roslyn excludes from style rules by design.

Also removes 25 usings that ImplicitUsings already provides.

Part of #32
Mostly narrow: null-conditional where a value really can be absent, sealing
internal types with no subtypes, and PascalCase logging placeholders. The
null-forgiving operators in the Web Forms page, the MVC action filter, and the
Global.asax container provider all sit on values Autofac property injection or
application startup guarantees.

Two are more than cosmetic. The multitenant console app held its container and
tenant strategy in nullable statics assigned from Main, which meant every use
needed a null check the design never actually allowed; they are now readonly
fields initialized where they're declared, with the strategy passed in rather
than read back off a field. And the MVC actions get [HttpGet], because without
it they also accept POST, which is what CA3147 was objecting to.

Also drops the last ReSharper suppression comment.

Part of #32
Both RootNamespace and AssemblyName said MvcExample, so the Web Forms sample
built an assembly named after an unrelated project while every type in it, and
every Inherits attribute pointing at those types, used WebFormsExample.

Part of #32
Matches the Source.ruleset pattern the library repos use. The previous NoWarn
was also far too broad: it suppressed five rules across all fifteen projects
when only five projects needed anything, so a real CA1716 or CA5368 in a console
example would have gone unreported.

Splitting ASP.NET out of the common set is what the measurements asked for.
CA1707, CA1716 and CA5368 only fire where System.Web dictates a type or handler
name, and CA1848 and CA1873 only where ASP.NET Core logging is in play. None of
them can occur in a plain console sample. Source.ruleset therefore carries no
deviations yet and exists as the baseline every project inherits; AspNet.ruleset
includes it and adds the eight hosted projects' exemptions.

Also drops the NU1900 exclusion. That warning only appears against a feed
without NuGetAudit support, so suppressing it locally is a local concern rather
than something the public build should carry.

Part of #32
@tillig
tillig force-pushed the feature/code-standards branch from 1ecf28d to 607c2cc Compare September 1, 2026 15:53
@tillig
tillig merged commit 87d98c6 into main Sep 1, 2026
16 checks passed
@tillig
tillig deleted the feature/code-standards branch September 1, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant