-
Notifications
You must be signed in to change notification settings - Fork 0
0015 sonar rules in the build
Status: accepted · Date: 2026-08-06
Amended by 0019 (2026-08-10).
samples/now carries the analyser. The reason given below — thatGenerated/would light up prose — is measured false: Roslyn skips.g.csfiles as generated code, and SonarAnalyzer honours that.
SonarAnalyzer.CSharp was referenced nowhere in this repository: not in
Directory.Build.props, not in src/Directory.Packages.props, not in any
.csproj, not in a workflow.
The consequence was structural rather than a matter of discipline.
TreatWarningsAsErrors covers the analyzers a project actually references, and
SonarAnalyzer was not one of them, so dotnet build could not fail on a Sonar
rule however many warnings were errors. A code review reading a diff could not
see them either. The only two things that did were SonarCloud, after the
push, and SonarLint in VS Code, for files that happened to be open.
That second one is incidental coverage, and the gap it leaves is measurable: a
one-off local analyzer run over src/DataNet.Metrics reported 12 findings in
code committed several tasks earlier, in files nobody had opened. None of them
was wrong, and all of them would have come back from SonarCloud on the pull
request — which is the round trip this decision removes.
SonarAnalyzer.CSharp is an analyzer-only PackageReference —
PrivateAssets="all", so it reaches no published .nuspec — in src/,
tests/ and bench/.
The version is pinned once, as $(DataNetSonarAnalyzerVersion) in the
repository-root Directory.Build.props, and read from three places because the
three areas reference packages differently: src/ and tests/ have Central
Package Management and take it through their Directory.Packages.props, while
bench/ has none and names the version on the PackageReference itself. One
pin, three uses, nothing to drift.
src/ alone was the cautious option, on the theory that test and benchmark code
trips different rules and turning them all into errors at once may not be the
trade the project wants. Measured before deciding, it isn't a trade at all:
| Area | Findings on first compile under the rules |
|---|---|
src/ |
7 |
tests/ |
4 |
bench/ |
0 |
Four findings is not a cleanup, and bench/ was already clean. Against that,
the root Directory.Build.props has said "warnings are errors everywhere: src,
tests and bench alike" since the beginning, and SonarCloud already reports on all
three — so scoping the local build more narrowly than the remote gate would
recreate the round trip in miniature, for the code that is read most often.
Amended by 0019 (2026-08-10).
samples/now carries the analyser. The reason given below — thatGenerated/would light up prose — is measured false: Roslyn skips.g.csfiles as generated code, and SonarAnalyzer honours that.
The samples are analysed by SonarCloud (#77) but do not get the analyzer here.
They consume the packages from a local feed, so building them requires a pack
first and they are deliberately outside DataNet.slnx (ADR 0009) — the
build a contributor runs does not include them. And
samples/DataNet.DocSnippets/Generated/ is extracted verbatim from the guides
and already excluded from SonarCloud's analysis: adding the analyzer would light
up prose written for a reader, in a file whose fix belongs in a Markdown
document. The 11 findings above were found without them.
-
A finding is a compile error on the machine that wrote the code. This is
demonstrated rather than asserted: a commented-out line added to a file in each
of
src/,tests/andbench/fails that area's build witherror S125. -
The first compile under the rules produced 11 findings, and every one was a
judgement. Three
S125were English prose whose semicolon the rule reads as a statement — reworded, not suppressed. OneS2306was a local namedasync, renamed. The remaining seven are#pragma warning disablewith a reason in the source, perCONTRIBUTING.md: fiveS1244where exact zero is exactly the value being guarded against and a tolerance would change the function, oneS6966on the synchronousSavea test exists to compare againstSaveAsync, and oneS1133on an[Obsolete]whose removal is already scheduled. -
The SonarCloud job is unchanged. It still reads the whole project,
including the samples and the Python under
tools/, and reports duplication and coverage, which a local analyzer does not. Inside that job the scanner installs its own analyzers over the build it observes, so the reference here is inert there; the two are pinned independently, and a version skew shows up as a finding SonarCloud reports and the local build does not. -
The analyzer reaches no package.
PrivateAssets="all"keeps it out of every dependency group, andtools/check_nuspec_dependencies.pyis exactly the assertion that it stays out — its expected graph is exact, so an analyzer that ever leaked would fail the run rather than ship. - Raising the version is a deliberate act with a cost. A new SonarAnalyzer release adds rules, and adding rules to a build where warnings are errors breaks it. The pin is in one place so that the bump is one edit and one cleanup, rather than three edits and a slow divergence between areas.
- 0001-target-framework
- 0002-unicode-comparison-unit
- 0003-provenance-and-licensing
- 0004-levenshtein-myers-backlog
- 0005-hamming-jellyfish-divergence
- 0006-ratcliff-autojunk
- 0007-metaphone-scope
- 0008-italian-enza-nltk-divergence
- 0009-sample-consumes-a-local-feed
- 0010-stop-word-list-provenance
- 0011-persistence-format
- 0012-per-package-versioning
- 0013-sentencepiece-parity-scope
- 0014-precompiled-normalizer
- 0015-sonar-rules-in-the-build
- 0016-metrics-package-placement
- 0017-bpe-parity-scope
- 0018-multiclass-roc-auc-parallelism-is-opt-in
- 0019-the-net-analysers-run-in-the-build-too
- 0020-normalize-is-a-projection-not-a-parameter
- 0021-multioutput-is-a-method-not-an-enum
- 0022-added-token-matching-flags
- 0023-byte-level-decode-substitutes
- 0024-weighted-median-averages-within-scikit-learns-epsilon
- 0025-quickselect-replaces-a-full-sort-for-the-median
- 0026-r2-and-explainedvariance-split-their-undefined-cases-differently
- 0027-r2-and-explainedvariance-vectorize-only-a-single-output
- 0028-log1p-is-kahans-identity-not-math-log-1-plus-x
- 0029-balanced-accuracy-adjusted-is-left-to-ieee-754-at-the-edge
- 0030-cohen-kappa-keeps-scikit-learns-expected-matrix-orientation
- 0031-nosamplecorrect-mirrors-numpys-float64-upcast
- 0032-fbeta-substitutes-tp-predicted-and-support-algebraically
- 0033-compensated-sum-is-neumaiers-variant
- 0034-dropout-is-refused-for-want-of-a-user
- 0035-a-null-pre-split-is-removed-with-invert-not-isolated
- 0036-a-member-may-ship-without-an-oracle-if-it-says-so
- 0037-the-guards-run-before-the-commit
- 0038-the-gate-confronts-an-exception-tag-with-the-page-that-documents-it
- 0039-mutual-information-returns-zero-on-an-empty-input
- 0040-a-curve-is-a-sealed-class-per-curve
- 0041-one-sample-file-per-public-class
- 0042-phonetic-encoders-refuse-a-null-word
- 0043-the-equality-table-is-sized-to-the-pattern
- 0044-compression-belongs-to-the-caller
- 0045-a-console-call-carries-its-reason-on-the-line
- 0046-check-adr-immutable-runs-in-ci-only
- 0047-one-gate-per-kernel-not-one-per-alphabet
- 0048-the-gate-depends-on-the-kernel-and-the-alphabet
- 0049-two-gates-per-kernel-tested-where-the-width-is-known
- 0050-the-sentencepiece-bpe-lineage-stays-a-bpe-model
- 0051-the-save-paths-cost-is-the-buffer-not-the-encoding
- benchmark_latest
- decisions
- equivalence
- matplotlib
- migration
- nightly_run
- numpy
- pandas
- performance
- pytorch
- seaborn
- sklearn
- statsmodels