Get ubuntu-latest green, and clear the three codegen follow-ups - #197
Merged
Conversation
…ut [patch] SEM004 was given a source location in #182; SEM001 and SEM003 were left reporting at Location.None because the collectors that raise them never received the MetadataFile. dimensions.json is thousands of lines, and both are typo-catchers, so "which name is wrong" without "where it is written" leaves the reader to do most of the work themselves. Thread MetadataFile? through CollectAllOperators/CollectAllProducts and their four collectors, and through ResolveForms, then report through ReportAt. The two diagnostics want different searches, which is why this is not one call: SEM001 names something that does not exist anywhere, so the first occurrence of it in the file is the mistake. The existing unscoped FindLocation is exactly right. SEM003 names dimensions that all exist and are spelled correctly. Searching for one unscoped lands on its first legitimate mention, which is somewhere else entirely — technically a location, practically a wrong answer. So FindLocation gains a scoped overload: find the owning dimension's own "name" property, then find the relationship's "other" at or after it. It falls back to the unscoped search, and then to Location.None, so a metadata layout it does not recognise degrades to what was there before rather than to nothing. GeneratorDiagnosticTests gains an AssertPointsAt helper that reads the covered text back out of the metadata, and location tests for SEM001 and SEM003 built on it. Sem004_PointsAtWhereTheUnitIsWrittenRatherThanAtNothing moves onto the same helper, which strengthens it: it asserted only that the location was not Location.None, which would also have passed for a location pointing at the wrong entry — the failure mode this change is specifically about. Closes #193 Co-Authored-By: Claude <noreply@anthropic.com>
Every build emitted one RS2002 per SEM diagnostic — seven of them since #182 added SEM006 and SEM007. The release-tracking analyzer only sees descriptors exposed through DiagnosticAnalyzer.SupportedDiagnostics. These are reported by source generators, which have no such property, so it can never see them however they are declared. That left the project stuck between its own rules: omit the identifiers from AnalyzerReleases.Unshipped.md and RS2008 fails the build; include them and RS2002 warns because nothing claims them. Keep the identifiers tracked and silence RS2002, scoped to Semantics.SourceGenerators.csproj with a comment saying why the rule cannot be satisfied here. AnalyzerReleaseTrackingTests already enforces what RS2008 enforces, from a vantage point that can actually see the descriptors, so the discipline survives the suppression. Verified: a clean build of the project now reports 0 warnings. Closes #194 Co-Authored-By: Claude <noreply@anthropic.com>
CLAUDE.md documented the reproduction along with a limitation: it reached only Semantics.SourceGenerators, the one project declaring its SDK with the <Project Sdk="..."> attribute form. The five ktsu.Sdk projects use <Project> with <Sdk Name="..." /> elements, which CustomBeforeMicrosoftCommonProps does not reach — so five of six projects had no local coverage and their findings only surfaced after a push. CustomAfterMicrosoftCommonProps does reach that form, and is still early enough for restore to pick up the analyzer PackageReference. Switch the documented invocation and the comment in sonar-local.props, and replace the limitation paragraph with the caveat that actually applies: the globalconfig was calibrated against this repository's profile, so a clean local run is strong evidence rather than proof. Verified by building with the After hook and seeing Sonar findings reported against Semantics.Color and Semantics.Music, neither of which the Before hook reached at all. The first wider run surfaced one finding, in Semantics.Color: S1244 against the exact zero check in Oklab.Cbrt. The comparison is correct — the Newton-Raphson step divides by x * x, and x is exactly zero only when value is, so a tolerance band would send small-but-valid inputs down the shortcut and lose the precision the refinement exists to recover. Suppressed at the method with that justification rather than reworked. Closes #195 Co-Authored-By: Claude <noreply@anthropic.com>
`Test on ubuntu-latest` has been red on main since 8b9df02 added Linux to the matrix, with 92 failures — every one in the path tests. The new coverage found what it was added to find on its first run: these tests had only ever been exercised on Windows, and had its spelling of everything baked in. All 92 turned out to be test assumptions rather than Semantics.Paths defects. The library takes its separators from System.IO.Path and treats a backslash as an ordinary filename character where that is what it is, which is correct; the tests asserted the Windows answer and got the right one for the wrong platform. TestPaths composes fixtures from Path.DirectorySeparatorChar and a platform-appropriate root, so `TestPaths.Absolute("projects", "app")` is `C:\projects\app` on Windows and `/projects/app` elsewhere. Rendering every converted fixture back into its Windows spelling reproduces the original string literal exactly, so Windows behaviour is unchanged by construction — worth stating because a Linux-green suite proves nothing about the platform the tests came from. Mixed-separator fixtures use Path.AltDirectorySeparatorChar rather than a hard-coded backslash. On Unix both separators are '/', so those cases degrade to ordinary paths rather than smuggling in a backslash and testing something else. Eleven were not fixture spelling and needed a decision: - UNC is a Windows concept; \\server\share is an ordinary relative filename on Unix. [OSCondition(OperatingSystems.Windows)] + [TestCategory("OS-Specific")], matching Parent_OfUncShareRoot_ReturnsTheShareItself. - < > : | are reserved on Windows and legal in a Unix filename. The validators ask the running platform via Path.GetInvalidFileNameChars, so accepting them off Windows is the correct answer, not a gap. Same gating, with the reason on each test. - "rejects path separators" tests now build the separator from Path rather than hard-coding a backslash, which makes them assert the thing they are named for on both platforms instead of only one. AbsoluteDirectoryPathParentTests was already platform-correct via a per-platform conditional; TestPaths makes one expression cover both, so the conditional goes. Result on Linux: 1110 total, 0 failed, 1102 passed, 8 skipped — the 8 being the Windows-only cases above. Closes #191 Co-Authored-By: Claude <noreply@anthropic.com>
…patch] Three findings, two of them only visible because the local reproduction now reaches every project. S107 on ResolveForms is mine: threading MetadataFile? through for #193 pushed it to eight parameters. The relationship, the file it came from, the dimension that owns it and its field path all travel together and only exist to let a diagnostic say where it is, so they become one RelationshipSite. ResolveForms drops to four parameters and ReportFormMissing to three, and the call sites read better than the seven-argument version they replace. S1244 on NormalizedParameter.Logarithmic is pre-existing, newly visible. The exact zero check is correct — a logarithmic range is undefined at zero but perfectly well defined at 1e-9, so a tolerance band would reject legitimate ranges. Comparing Math.Sign results says the same thing without comparing floats, so this needs no suppression. S3267 on DimensionsGenerator is pre-existing: the loop only ever reads dimension.Name, so it iterates the names. Verified: the whole solution now builds clean under the Sonar analyzers except for a pre-existing backlog in Semantics.Test (S4144 on identical test bodies, S101 on SSNString), which is untouched by this branch and is its own triage. Generated output is unchanged — verified by rebuilding Semantics.Quantities and diffing Generated/. Co-Authored-By: Claude <noreply@anthropic.com>
5 tasks
…ed [patch] SonarCloud failed the quality gate on #197: 66.7% coverage on new code against a required 80%. Measuring the same thing locally (cobertura, intersected with the lines the branch adds) put it at 76.5% and named the same two files. Every gap was a real one, and the tests are real rather than coverage padding: QuantitiesGenerator — the eight SEM001 report sites are one per relationship kind per field, each with its own field-path message, and only integrals[...].other had a test. A rename that dropped any of the other seven would not have failed anything. Now a DataRow per kind and field, asserting both the location and the field path in the message. SEM003 had only its self branch covered. The other two participants are the interesting ones — a cross product needs the form on its other operand and on its result, and the diagnostic has to name whichever is missing it rather than the dimension that owns the relationship. Two tests, using a second dimension that declares vector0 only. MetadataFile — the scoped FindLocation overload had its happy path covered through SEM003 and none of its fallbacks, which are the whole reason it is safe to use: an unrecognised metadata layout has to degrade to the unscoped search and then to Location.None rather than throw or point somewhere arbitrary. MetadataFileTests covers both overloads directly, including a null SourceText. New-code coverage measured the same way afterwards: 52/52 lines, 100%. 1127 tests, 0 failures. The Sonar analyzers report nothing new — the remaining findings are the pre-existing Semantics.Test backlog (S4144, S101). Co-Authored-By: Claude <noreply@anthropic.com>
|
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #191, #193, #194, #195 — everything left over from #182 and #187.
The four land on one branch because that's the branch this work stream uses; they're independent and reviewable in commit order.
#191 —
Test on ubuntu-latestgoes greenRed on
mainsince8b9df02added Linux to the matrix, with 92 failures, every one in the path tests. The new coverage found what it was added to find on its first run: these tests had only ever run on Windows and had its spelling of everything baked in.All 92 were test assumptions, not
Semantics.Pathsdefects. The library takes its separators fromSystem.IO.Pathand treats a backslash as an ordinary filename character where that is what it is — which is correct. The tests asserted the Windows answer and got the right answer for the wrong platform.IsChildOf_WithMixedSeparators_WorksCorrectly, which the issue singled out as worth reading carefully, is one of these: it failed only because itsC:\projectsfixture won't construct on Linux.TestPathscomposes fixtures fromPath.DirectorySeparatorCharand a platform-appropriate root, soTestPaths.Absolute("projects", "app")isC:\projects\appon Windows and/projects/appelsewhere.Windows behaviour is unchanged by construction. Rendering every converted fixture back into its Windows spelling reproduces the original string literal exactly — I checked this mechanically across all 14 files, and the only lines that don't round-trip are the ones I deliberately restructured. Worth saying, because a green Linux run proves nothing about the platform these tests came from. CI bears it out:
Test on windows-latestis green on every commit here.Mixed-separator fixtures use
Path.AltDirectorySeparatorCharrather than a hard-coded backslash. On Unix both separators are/, so those cases degrade to ordinary paths instead of smuggling in a backslash and quietly testing something else.Eleven weren't fixture spelling and needed a decision:
\\server\share)[OSCondition(OperatingSystems.Windows)]+[TestCategory("OS-Specific")], matching the existingParent_OfUncShareRoot_ReturnsTheShareItself< > : |in namesPath.GetInvalidFileNameChars, so accepting them off Windows is the correct answer, not a gap. Same gating, reason recorded on each testPathinstead of hard-coding\, so it asserts the thing it's named for on both platforms rather than oneAbsoluteDirectoryPathParentTestswas already platform-correct via a per-platform conditional;TestPathsmakes one expression cover both, so the conditional goes.#193 — SEM001 and SEM003 now say where
SEM004 got a source location in #182; these two were left at
Location.Nonebecause the collectors that raise them never received theMetadataFile.dimensions.jsonis thousands of lines and both are typo-catchers, so "which name is wrong" without "where it is written" leaves the reader to do most of the work.The two want different searches. SEM001 names something that exists nowhere, so the first occurrence in the file is the mistake — the existing unscoped
FindLocationis right. SEM003 names dimensions that all exist and are spelled correctly; searching for one unscoped lands on its first legitimate mention somewhere else entirely. Technically a location, practically a wrong answer. SoFindLocationgains a scoped overload — find the owning dimension's own"name", then the relationship's"other"at or after it — falling back to the unscoped search and then toLocation.None, so an unfamiliar metadata layout degrades to today's behaviour rather than to nothing.GeneratorDiagnosticTestsgains anAssertPointsAthelper that reads the covered text back out of the metadata. That strengthens the existing SEM004 test, which moves onto it: it asserted only that the location wasn'tLocation.None, which would have passed for a location pointing at the wrong entry — the exact failure mode this is about.#194 — no more RS2002
Seven warnings on every build, one per SEM diagnostic. The release-tracking analyzer only sees descriptors exposed through
DiagnosticAnalyzer.SupportedDiagnostics; these are reported by source generators, which have no such property. Omit them fromAnalyzerReleases.Unshipped.mdand RS2008 fails the build; include them and RS2002 warns.Identifiers stay tracked; RS2002 is silenced in
Semantics.SourceGenerators.csprojwith a comment saying why the rule can't be satisfied here.AnalyzerReleaseTrackingTestsalready enforces what RS2008 enforces from a vantage point that can see the descriptors. Verified: a clean build of the project reports 0 warnings.#195 — the Sonar reproduction reaches all six projects
CustomBeforeMicrosoftCommonPropsreaches onlySemantics.SourceGenerators, the one project using the<Project Sdk="...">attribute form. The fivektsu.Sdkprojects use<Sdk Name="..." />elements, which that hook doesn't reach.CustomAfterMicrosoftCommonPropsdoes, and is still early enough for restore to pick up the analyzerPackageReference:dotnet build -p:CustomAfterMicrosoftCommonProps=$PWD/.sonarlint/sonar-local.propsVerified by seeing Sonar findings reported against
Semantics.ColorandSemantics.Music, neither of which theBeforehook reached at all. The "Known limitation" paragraph is replaced with the caveat that actually applies: the globalconfig was calibrated against this repository's profile, so a clean local run is strong evidence rather than proof.The backlog it exposed
Triaged rather than deferred, since all three were errors under the raised severities:
Oklab.Cbrt, S1244 — the exact-zero check is correct: the Newton-Raphson step divides byx * x, andxis zero only whenvalueis, so a tolerance band would send small-but-valid inputs down the shortcut and lose the precision the refinement exists to recover. Suppressed at the method with that justification.NormalizedParameter.Logarithmic, S1244 — same shape, but here it rewrites cleanly: comparingMath.Signresults says "is zero" without comparing floats. No suppression needed.DimensionsGenerator, S3267 — the loop only readsdimension.Name, so it iterates the names.One finding is mine: threading
MetadataFile?through for #193 pushedResolveFormsto eight parameters (S107). The relationship, its file, its owning dimension and its field path all travel together and exist only to let a diagnostic say where it is, so they become oneRelationshipSite.ResolveFormsdrops to four parameters andReportFormMissingto three.Test coverage on the new code
The first push cleared CI but failed SonarCloud's gate at 66.7% coverage on new code against a required 80%. Measuring the same thing locally — cobertura output intersected with the lines this branch adds — reproduced it at 76.5% and named the same two files. Every gap was real, so the last commit closes them with tests rather than padding:
integrals[...].otherhad a test. A rename dropping any of the other seven would have failed nothing. Now aDataRowper kind and field, asserting both the location and the field path in the message.MetadataFile.FindLocation's scoped overload had its happy path covered through SEM003 and none of its fallbacks — which are the whole reason it's safe to use.MetadataFileTestsnow covers both overloads directly, including a nullSourceText.SonarCloud now reports 100.0% coverage on new code, matching the local measurement of 52/52 lines exactly.
Verification
Test on ubuntu-latestgreen for the first time since the matrix added it.Verify Generated Files: green.git diffoverSemantics.Quantities/Generated/after a rebuild is empty — nothing here changes emitted code.Not verified from here: SonarCloud reports 2 new issues alongside the passing gate.
sonarcloud.iois unreachable from the agent sandbox (the network policy 403s the CONNECT), so I can't enumerate them, and the local analyzer reproduction doesn't surface them — it reports only the pre-existingSemantics.Testbacklog (S4144 on identical test bodies, S101 onSSNString), which this branch doesn't touch. Anyone with dashboard access can see what they are in a click; they didn't block the gate.