Skip to content

Fix 20 more SonarCloud issues; document 8 multi-target false positives - #158

Merged
matt-edmondson merged 6 commits into
mainfrom
chore/sonarcloud-cleanup-2
Aug 14, 2026
Merged

Fix 20 more SonarCloud issues; document 8 multi-target false positives#158
matt-edmondson merged 6 commits into
mainfrom
chore/sonarcloud-cleanup-2

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Follow-up to #157. Fixes 20 more SonarCloud issues and documents 8 that are false positives on this codebase.

Fixed — 20

Rule Count Change
S2223 3 (HIGH) PropertyTemplate.AutoGet/AutoSet/AutoInit were mutable public static fields → static readonly
S1192 4 Extracted the repeated failure message in the kebab/macro/snake/sentence case attributes to a FailureMessage constant
S6610 6 StartsWith("-") / EndsWith("_") → the char overloads
S3358 3 Extracted the nested ternary in AccessibilityLevelAgainst; Clamp01Math.Clamp
S6580 2 IsDateTime / IsTimeSpan now pass an explicit format provider
S4136 2 Grouped the AsAbsolute overloads in the relative path types

Notes on two of those:

  • S2223 — these are compared by reference in WriteTo to detect auto-property shorthand, so they must remain single fixed instances. static readonly enforces that without changing the comparison; const isn't possible for delegates.
  • S6580 — deliberately CultureInfo.CurrentCulture, not InvariantCulture. That is what the provider-less overload already used, so which strings validate is unchanged. Invariant is arguably the better semantic for a validation attribute (validation currently varies by machine locale), but that is a behaviour change and your call — happy to switch it.

Also refreshed CLAUDE.md, which still documented the old three-line header. Carried over from the superseded #151 (thanks — it was the clearest description of the COPYRIGHT.mdfile_header_templateWriteHeaderTo chain anywhere in the repo), extended to note that SourceGeneratorTests now asserts the emitted header so drift fails a test.

Not fixed — 8 false positives, with reasons

S8969 ×4 — "remove this null-forgiving operator; the compiler already knows". Verified by removing all four and building: every one fails on netstandard2.0 with CS8604/CS8602. string.IsNullOrEmpty lacks [NotNullWhen(false)] there, so the ! is mandatory — Sonar only analyses net10, where it is genuinely redundant. Same multi-target shape as the IDE0370 issue from the earlier SDK migration. Unfixable without dropping netstandard2.0 or adding conditional compilation, which the repo conventions discourage.

S1244 ×4 — "do not check floating point equality with exact values".

  • Hsl.cs (×2): max is assigned from one of srgb.R/G/B, so max == srgb.R is an identity test, not an approximate one. An epsilon range would be actively wrong — it could match two channels and pick the wrong hue sector.
  • NormalizedParameter.cs (×2): skew == 1.0 is a fast-path skip, and Math.Pow(x, 1.0) == x regardless, so the comparison cannot change the result — only whether the Pow call happens.

Verification

  • dotnet build — 0 errors, 0 warnings, all target frameworks
  • dotnet test1089/1089 pass

Still open after this — ~66

S3776 cognitive complexity ×9 (mostly QuantitiesGenerator, now covered by tests so safer to refactor), S1133 deprecation TODOs ×10, S1192 ×15 in the generators, S4136 ×8 in SemanticString.cs (pure reordering in a 600-line public API file with #if blocks — high diff noise, deserves its own pass), S1172 ×6, S3267 ×6, plus a small tail.

- S2223 (3, HIGH): PropertyTemplate.AutoGet/AutoSet/AutoInit were mutable
  public static fields. Made static readonly - they are compared by
  reference in WriteTo to detect auto-property shorthand, so they must stay
  single fixed instances and readonly enforces that without changing the
  comparison.
- S1192 (4): extracted the repeated validation failure message in the
  kebab/macro/snake/sentence case attributes to a FailureMessage constant.
- S6610 (6): StartsWith("-")/EndsWith("_") -> the char overloads in the
  same three casing attributes. Verified across all TFMs; Polyfill supplies
  the char overloads on netstandard2.0.
- S6580 (2): IsDateTime/IsTimeSpan now pass an explicit format provider.
  Deliberately CultureInfo.CurrentCulture, not InvariantCulture: that is
  what the provider-less overload already used, so which strings validate
  is unchanged. Switching to invariant would be a behaviour change and is
  the maintainers call.
- S3358 (3): extracted the nested ternary in AccessibilityLevelAgainst into
  named thresholds, and replaced the chained Clamp01 ternaries in Color and
  Hsl with Math.Clamp (identical NaN behaviour).

Also refreshed CLAUDE.md - it still documented the old three-line header.
Carried over from the superseded #151, extended to note that
SourceGeneratorTests now asserts the emitted header.

Build clean on all TFMs, 1089/1089 tests pass.
The explicit IRelativePath.AsAbsolute() implementation sat between the two
public AsAbsolute overloads, splitting the group. Moved it below both in
RelativeFilePath and RelativeDirectoryPath. Pure reordering, no behaviour
change.

Build clean, 1089/1089 tests pass.
Addresses both quality-gate failures on this PR.

new_duplicated_lines_density (38.7%): the FailureMessage constants were
introduced with an identical three-line XML doc comment in four files, which
is literal duplication. The comment is gone; a private const does not need
one, and the declaration line itself differs per file.

new_coverage (56.3%): PropertyTemplate had no tests at all. Added four
covering auto-get/auto-set, auto-get/auto-init, the abstract (no accessor)
form, and a custom getter. This needed InternalsVisibleTo on
Semantics.SourceGenerators, matching the pattern already used by
Semantics.Paths and Semantics.Quantities.

Worth a look: AutoGet, AutoSet and AutoInit are not referenced by any
generator, so the auto-property shorthand branches in WriteTo are currently
unreachable from production code. They are kept and now exercised rather
than deleted, since they read as a deliberate affordance for template
authors - but if they are not wanted, deleting them would also remove the
dead branch and cut WriteTo s S3776 complexity.

SourceGeneratorTests now iterates the generators inside one test instead of
using [DynamicData] with an IIncrementalGenerator parameter. A public test
method taking a reference type trips CA1062, and every escape is blocked
here: CA1510 rejects an explicit ArgumentNullException throw, CA1062 does
not recognise ArgumentNullException.ThrowIfNull, and Polyfill s Ensure
cannot be referenced because its shim types collide with the ones the
generator assembly embeds publicly (106 x CS0433). Assertion messages name
the generator, so failures stay unambiguous.

ktsu.CodeBlocker no longer needs ExcludeAssets="compile" now that
BundleAnalyzerDependencies=false stops the facade leak.

Build clean, 1086/1086 tests pass.
Closes the remaining coverage gaps on this PR, all of which turned out to be
genuinely untested branches rather than artefacts of the changes:

- Each casing validator has two rejection branches beyond the leading and
  trailing separator checks - a separator belonging to a different
  convention, and correct separators with the wrong letter case. Neither was
  exercised for kebab, snake or macro, nor was sentence case s
  lowercase-first-letter branch. Covered by one table-driven test rather
  than four near-identical ones, which would have added duplication.
- RelativeFilePath and RelativeDirectoryPath implement
  IRelativePath.AsAbsolute() explicitly so the interface returns the base
  AbsolutePath while the public overload returns the specific type. No test
  had ever gone through the interface, so both explicit implementations were
  dead to the suite.

Build clean, 1088/1088 tests pass.
Merged main, which bumped ktsu.Sdk 2.26.1 -> 2.27.0. That build had not run
on main yet (only the Dependabot workflow did), so these failures are the
SDK bump rather than anything in this branch - main will hit them too.

- KTSU0001 (new in 2.27.0): the downlevel targets must reference the
  packages supplying the framework types they use rather than picking them
  up transitively. Added System.Memory for netstandard2.1 (Paths, Strings
  and Strings.Identifiers already had it for netstandard2.0 only; Color and
  Music had neither) and System.Threading.Tasks.Extensions for
  netstandard2.0, across all five multi-targeted libraries. Added the
  missing System.Threading.Tasks.Extensions PackageVersion.
- Committed the .editorconfig the SDK rewrites on build - 2.27.0 changed the
  C++ section to the brace-glob form. This is what failed the
  verify-generated job, not generator drift.

Build clean on all TFMs, 1088/1088 tests pass.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
9.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@matt-edmondson
matt-edmondson merged commit 7fc3b8e into main Aug 14, 2026
5 of 6 checks passed
@matt-edmondson
matt-edmondson deleted the chore/sonarcloud-cleanup-2 branch August 14, 2026 06:00
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