Skip to content

Fixes #3052 - #3059

Merged
erikdarlingdata merged 5 commits into
devfrom
fix/3052-strip-comments-walker
Sep 6, 2026
Merged

erikdarlingdata merged 5 commits into
devfrom
fix/3052-strip-comments-walker

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

Six source-scanning pins separated "code" from "comment" by LINE PREFIX. That works for // and ///, which are prefixed on every line. It does not work for block comments, because this codebase puts no asterisk on the continuation lines — only the first line starts with /, and every line after it is indented prose the filter hands to the scanner as code.

CSharpSourceWalker.StripCommentsAndStrings already answers the question exactly, and 23 files already ask it. These pins now ask it too — with two exceptions that are not the same question, both of which the census got wrong and both of which are load-bearing.

Failure direction, stated plainly, because it decides how urgent this is

Every instance UNDER-strips. A comment mention reads as code, the offender list gains an entry that is not one, and an Assert.Empty fails loudly. That is a false positive, not a hidden offender — worth fixing, not urgent. A red test gets looked at.

The instance that actually cost something was not an assertion at all. During #3047's review a check of this shape reported 45 changed lines in a comments-only diff of CollectorScheduleDefaults.cs. Nothing went red, because nothing was asserting: the number fed a human judgement and nearly produced a wrong verdict about whether that PR touched code. The general rule this issue records is the same filter is loud when it drives an assertion and silent when it drives a number. The silent half lives in ad-hoc review checks that nobody commits and no test can reach. This PR fixes the half that is committed, and says so rather than claiming more.

What each pin covers, before and after

Measured, not asserted. A throwaway harness ran the OLD predicate and the NEW predicate over the same real product source and diffed the result sets.

Pin Scanned Before After
McpConfigReadAvoidsSecretColumnsTests.TheMcpSurfaceUsesNeitherSmtpNorWebhooks 94 files under Darling.Service/Mcp 0 offenders 0 offenders, identical set
ServerIdentityFromStoreTests.TheServiceNoLongerDerivesIdentity 3 named files 0 offenders 0 offenders, identical set
ServerIdentityFromStoreTests.IdentityIsMintedInExactlyThreePlaces 418 files across 3 projects 3 files the same 3 files
TimescaleEnableConnectionSafetyTests.NoLiveTest_… Darling.Tests 40 candidate call sites the same 40
AnalysisPassTokenThreadingTests (Darling) 20 files in Darling.Analysis 16 untokened calls / 7 methods / 27 classified catches identical
AnalysisPassTokenThreadingTests (Lite) 21 files in Lite/Analysis 15 untokened calls / 5 methods / 27 classified catches identical
AuroraOnlySqlIsGatedTests.FilesNamingAuroraSurfaces 1,345 files repo-wide 4 files the same 4 files

Nothing moved. No pin was passing for the wrong reason on dev today, and none was failing spuriously. The defect is entirely latent: it fires the moment somebody writes a block comment naming a banned token, which is exactly the thing this codebase's comment style encourages.

The one exception, and it is the reason this needed a before/after rather than a "should be fine": adopting StripCommentsAndStrings in AuroraOnlySqlIsGatedTests takes its found set from 4 files to 0. See below.

Two pins the census got wrong, and one it missed

AuroraOnlySqlIsGatedTests (Lite.Tests) was missed, and it is the interesting one. It drops //, * and /* lines from every non-test *.cs in the repo, then looks for Aurora-only surface names. It is a genuine instance — and the issue's named fix would break it, because every Aurora surface name in this repo is SQL inside a string literal, which the stripper blanks. Measured: found goes 4 → 0, which fails Assert.NotEmpty and makes all four allow-list entries read as stale. So it asks the walk only for what the walk is exact about — where the comments are — and reads both non-comment views: the code, and the literal bodies.

Pg18IoBytesTests is not an instance. Its filter runs over DarlingPgIoReader.PgIoSql — SQL text already extracted from a raw string literal — and drops SQL block-comment lines from the outer SELECT list before counting its items. CSharpSourceWalker cannot read SQL, so adopting it there would be a category error. Left alone, recorded in the guard's allow-list with the reason and with the bound it currently rests on: the window it cuts holds no comment today, so the /* and * arms are inert; a comment added there whose continuation line contains " AS " would over-count and fail the 19 pin loudly.

AnalysisPassTokenThreadingTests (both SKUs) was diagnosed in the wrong place. Its only prefix filter is DocBlockAbove, which collects the contiguous /// block above a declaration to read the #2443 exemption marker out of it. A doc comment is prefixed on every line by definition, and the marker lives in a comment — stripping comments there is the one thing that would break it. What actually read prose as code was the call and catch sweeps, which matched raw lines with no comment filter at all. AnalysisSources() now yields two line-for-line aligned views: the walked code for every regex, and the file as written for the doc-block read and the offender messages.

Lite.Tests gets the walker by linked Compile, not by a copy

Lite.Tests cannot ProjectReference Darling.Tests — both are xunit executables under Microsoft.Testing.Platform, and CI would discover the Darling suite twice. A second copy is exactly what #2913 removed: five pins each carried their own walk, they drifted, and only one of them had grown the newline stop that bounds a mis-read literal.

So Lite.Tests.csproj compiles the one file:

<Compile Include="..\Darling\Darling.Tests\CSharpSourceWalker.cs" Link="CSharpSourceWalker.cs" />

One file, one authority, and no parity pin needed because there is nothing to keep in parity — the implementation Lite compiles is byte-identical to the one CSharpSourceWalkerTests exercises. It brings its Darling.Tests namespace with it, which is why the two adopters carry a using Darling.Tests. The lite path filter already names Darling/Darling.Tests/**/!(*.md), so a change to the walker fires the Lite suite.

TimescaleEnableConnectionSafetyTests stops skipping its own file

The skip existed because the file names the method throughout in prose and in regex literals. The walk removes both, so the exemption stopped earning its place and an unsafe call written there is now caught like any other.

This matters more than it sounds: the file that most needed the fix was the one exempting itself. Its IsCheckedOnTheSpot note carries a worked example of the defect — an indented await TimescaleSupport.TryEnableAsync(connection, null, ct); inside a block comment, no asterisk — which the prefix filter reads as an unchecked call. Restoring the old filter with the narrowed skip list reports it as a live offender.

Guard on the guards

CommentFilterAdoptionTests lists every prefix filter left in either test project with the bound it rests on, and the build re-derives the set — the same agreement CommandDeadlineScannerAdoptionTests holds over the command-timeout family. Four kinds are legitimate and each entry says which: collecting a doc run (×2), a stated and measured bound, a filter over SQL, and the control fixture in the guard itself.

The shape is not banned outright, because Lite.Tests/LiteSidebarDotRendersTheCardStatusTests.cs:206 is a legitimate exception. Its doc comment states the measured bound — ServerConnection.cs carries exactly one block comment, the licence header. It also turns out to have a second, stronger reason to keep the prefix form: it asserts that four status words appear in no string literal there, so blanking literal text would satisfy Assert.DoesNotContain vacuously. Adopting the walker would convert it into a pass for the wrong reason.

Assert.StartsWith("/*", sql) is excluded by construction, with a negative lookbehind. Asserting that generated T-SQL opens with a risk-disclosure header is the opposite act from filtering comments out of source, and the census put four files on the list for having the substring. Dropping the lookbehind makes the guard flag DarlingMcpToolsTests, ViewerRecommendationsTests, LiteRecommendationsReaderTests and McpAnalysisFindingsCommandTests — so the exclusion is load-bearing and proven.

No build.yml filter entry, deliberately: the guard reads both test projects, and naming Lite.Tests/** in the darling filter would also gate the live-Postgres darling-pg job on all 298 of Lite's test files. It belongs in the category whole-tree-guards already exists for, and that job runs this suite exactly when the build job's step reports skipped.

Verification

Darling.Tests and Lite.Tests target net10.0-windows and cannot run on macOS. The real test files were compiled into throwaway net10.0 xunit v3 hosts named Darling.Tests / Lite.Tests, staged outside the tree with their output inside the (gitignored) project bin/ so the pins' AppContext.BaseDirectory and [CallerFilePath] walk-ups resolve against the real repo root.

Both projects also build clean with -p:EnableWindowsTargeting=true, and the warning profile is byte-identical to dev's.

Green: 30 tests in the Darling host (1 skip — the live DARLING_TEST_PG round-trip), 9 in the Lite host. Re-run after merging origin/dev in; unchanged.

CI ran all eight checks green twice — on ce76919e5 (before the cost gate) and again on the head — including build (Windows, both suites), Darling PostgreSQL tests against a live Postgres, and Darling whole-tree guards. The gate is therefore a cost fix over an already-green substance, and it is red-proofed above as answer-preserving.

Cost, because the first green run measured it

The Aurora read walks a file to separate comments from code, and it enumerates every *.cs file in the repository — 1,345 of them — once per test, four times over. The first version walked unconditionally and took that class from 0.55s to 5.53s locally. On CI, measured within this branch across the two commits, the Windows Run Lite tests step went 5m37s ungated → 4m44s gated (a comparable run on another branch was 4m22s, so the residual is branch-to-branch noise rather than a remaining regression).

It is now gated on the raw text first, which is exact rather than approximate and is what makes skipping the walk safe: blanking only ever replaces a character with a space or a newline, and no surface name contains either, so a needle absent from the raw text is absent from every view of it. Roughly six files get past the gate, and the class is back to 0.49s. The walk also moved out of the Any lambda, where it had been running once per surface name rather than once per file.

Red-proofed eleven ways, each against a copy of the tree at a committed SHA, each failing a different named assertion. Two are two-sided — a block comment naming the banned token is injected into real product source, and the pin is run with the walker (green) and with the prefix filter (red) — and the last pair proves the cost gate changes cost and not answers:

Mutation Fails
restore the prefix filter in TimescaleEnableConnectionSafetyTests NoLiveTest_… — two offenders, both block-comment continuation lines in its own file
AuroraOnlySqlIsGatedTests adopts StripCommentsAndStrings alone TheAllowListHasNoStaleEntries (all 4 entries), EveryFileNamingAnAuroraOnlySurface_IsAccountedFor (Assert.NotEmpty), TheReadFindsASurfaceInSql_AndNotOneInABlockComment, APairedFileReallyReadsTheVanillaViewToo ×2
drop one s_bounded entry EveryLinePrefixCommentFilter_StatesTheBoundItRestsOn — stale-entry side
add a new unbounded prefix filter to a Lite.Tests file the same set equality — new-offender side
the control stops walking its fixture TheLinePrefixForm_ReadsABlockCommentsBodyAsCode_AndTheWalkDoesNot
drop the (?<!Assert) lookbehind the set equality, naming DarlingMcpToolsTests
inject config.Smtp in a block comment, prefix filter restored TheMcpSurfaceUsesNeitherSmtpNorWebhooks — ["DarlingMcpServerAdminTools.cs"]
inject GetDeterministicHashCode( in a block comment, prefix filter restored TheServiceNoLongerDerivesIdentity and IdentityIsMintedInExactlyThreePlaces (a fourth minting site)
inject reader.ReadAsync() in a block comment, walk reverted, both SKUs NoStoreCallOnTheAnalysisPassRunsWithoutThePassToken in each
invert the Aurora raw-text gate all five in the class, including Assert.NotEmpty
remove the gate entirely nothing — the same five pass, which is the claim: it changes cost, not answers

With the walker in place and the injected comments still present, all of those pass — so the controls fail in the intended direction rather than merely failing.

Not verified:

  • Nothing ran on Windows or Linux. CI's build, Darling Linux build and Darling PostgreSQL tests are the arbiters for the full ~4,800-test suites; only the eight pin classes touched here were executed locally.
  • The two behavioural tests in Lite.Tests/AnalysisPassTokenThreadingTests.cs (AnAbandonmentIsAFiredTokenAndACancellationShape_…, TheReadLockWaitIsAbandonableWhileAWriterHoldsIt) were filtered out: they need PerformanceMonitorLite, which is WPF. Neither is touched by this change.
  • Lite.Tests/LiteSidebarDotRendersTheCardStatusTests was not executed. It is unchanged, and the guard's own run proves it is not flagged.
  • No live store or monitored server was involved. This is a test-only change.

Adjacent finding, not fixed here

CrossAppGuardCiGateTests cannot see a Darling.Tests → Lite.Tests reference at all. Its detector keys on "Lite/…" and Path.Combine("Lite", …), and Lite.Tests is a sibling of Lite rather than a directory inside it — so the class of cross-app read this PR introduces is invisible to the pin whose stated job is to require it be filter-reachable. Covered here by whole-tree-guards rather than by a filter entry, so nothing is unguarded, but the detector gap is real and pre-existing.

CHANGELOG entry text

Not applied — every lane appends to the same [Unreleased] block.

- Source-scanning pins now read the shared C# walk instead of dropping lines by comment prefix, which read
  the body of a block comment as code because this codebase puts no asterisk on the continuation lines. A new
  guard lists every prefix filter left in the test projects with the bound it rests on. (#3052)

Six pins separated code from comment by line prefix. That works for `//` and `///`, which are prefixed on
every line, and not for `/* … */`, because this codebase puts no asterisk on the continuation lines: only
the first line starts with `/`, and every line after it is indented prose the filter hands over as code.

`CSharpSourceWalker.StripCommentsAndStrings` already answers the question exactly, and 23 files already ask
it. These pins now ask it too, with two exceptions that are not the same question:

- `AuroraOnlySqlIsGatedTests` looks for Aurora-only surface NAMES, which live in SQL string literals. The
  stripper blanks literal text, so adopting it alone takes the found set from four files to none. It asks
  the walk for the comments and reads both non-comment views instead: the code and the literal bodies.
- `AnalysisPassTokenThreadingTests` (both SKUs) carried the prefix form in `DocBlockAbove`, which COLLECTS
  a `///` run to read an exemption marker out of it — the prefix is the definition of the run there, and
  stripping comments is the one thing that would break it. What actually read prose as code was the call
  and catch sweeps, which matched raw lines with no filter at all. Those now read the walked code; the
  doc-block read still reads the file as written.

`Pg18IoBytesTests` is not an instance: its filter runs over SQL text already extracted from a raw string
literal, and the C# walk cannot read SQL.

`TimescaleEnableConnectionSafetyTests` also stops skipping its own file. The skip existed because the file
names the method throughout in prose and in regex literals; the walk removes both, so an unsafe call
written there is now caught like any other. Its own `IsCheckedOnTheSpot` note carries a worked example of
the defect — an indented `await TimescaleSupport.TryEnableAsync(...)` inside a block comment, which the
prefix filter reads as an unchecked call.

`CommentFilterAdoptionTests` is the guard on the guards: every prefix filter left in either test project is
listed with the bound it rests on, and the build re-derives the set. Four kinds are legitimate — collecting
a doc run, a stated and measured bound, a filter over SQL, and the control fixture in the guard itself —
and each entry says which. `Assert.StartsWith("/*", sql)` is excluded by construction, because asserting
that generated T-SQL opens with a disclosure header is the opposite act, and the census got that wrong
twice.

Every instance UNDER-strips, so the failure is a spurious red rather than a missed offender, and the
covered set is unchanged on `dev` today for all six.
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown

Reviewed the diff (test-infrastructure only -- no T-SQL, no production code touched, so the T-SQL style section doesn't apply here).

Summary: this is a well-executed fix. Adopting CSharpSourceWalker.StripCommentsAndStrings/StringLiteralBodies in place of ad-hoc line-prefix filters is the right call given the codebase's un-asterisked block-comment style, and the two 'census got it wrong' exceptions (AnalysisPassTokenThreadingTests.DocBlockAbove, which must stay on line-prefix /// matching because the exemption marker lives IN a comment; AuroraOnlySqlIsGatedTests, which must ask StringLiteralBodies because every Aurora surface name lives in a SQL string literal that the stripper would blank) are both correctly reasoned and correctly implemented.

Things I specifically verified rather than took on faith:

  • CommentFilterAdoptionTests's own census: independently grepped both test projects for line-prefix-style .StartsWith(...) calls on comment tokens (excluding Assert.StartsWith) and got exactly the 6 files listed in s_bounded -- no missing or stale entries.
  • CI wiring for the new guard: CommentFilterAdoptionTests reads both Darling.Tests and Lite.Tests sources but isn't named in any build.yml path filter. Confirmed darling-tree-guards runs the full Darling.Tests suite (not a class filter) whenever the build job's 'Run Darling tests' step reports skipped, so a Lite-only change that would otherwise dodge the darling/core/root filters still gets this guard exercised. Matches the PR description's claim.
  • TimescaleEnableConnectionSafetyTests self-exemption removal: confirmed the file's own remaining prose mentions of TimescaleSupport.TryEnableAsync (the doc-comment <code> block and the un-asterisked block comment in IsCheckedOnTheSpot's doc) are both correctly stripped by the walker, so removing the file's blanket exemption doesn't introduce a false failure.
  • Lite/Darling parity: AnalysisPassTokenThreadingTests was updated identically in both Darling.Tests and Lite.Tests (same (file, code, source) tuple shape, same DocBlockAbove/AnalysisSources treatment). The shared CSharpSourceWalker.cs is compiled into Lite.Tests via a linked <Compile Include> rather than copied, which is the correct way to avoid a second copy drifting -- consistent with the The shared source-walker blanks interpolated-string holes, so a call inside an interpolation is invisible to every scan built on it #2913 rationale cited in the .csproj comment.

No correctness, security, or performance issues found. Nothing here required an inline comment.

The read walks a file to separate comments from code, and it enumerates every `*.cs` file in the repository —
1,345 of them — once per test, four times over. Walking unconditionally took the class from 0.55s to 5.53s,
a 10x regression on a guard that is not the thing under test; gated it is 0.49s.

The gate is exact rather than approximate, which is what makes skipping the walk on it safe: blanking only
ever replaces a character with a space or a newline, and no surface name contains either, so a needle absent
from the raw text is absent from every view of it. Roughly six files get past it.

The walk also moves out of the `Any` lambda, where it ran once per surface name rather than once per file.
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown

Reviewed this PR against CONTRIBUTING.md conventions and the correctness/parity/security/performance criteria. Summary: no issues found; this is test-infrastructure-only (no T-SQL, no collector/product code touched), so the T-SQL style rules don't apply here.

What I checked and verified by hand:

  • Correctness of the (File, Code, Source) refactor in both AnalysisPassTokenThreadingTests.cs files: Code (the CSharpSourceWalker.StripCommentsAndStrings output) is what every regex reads, Source is only used for DocBlockAbove and offender messages. The two arrays are guaranteed line-aligned since the walker preserves every newline and only blanks characters to spaces -- confirmed this holds for IndexOfDeclaration(code, i) feeding into DocBlockAbove(source, ...).
  • TimescaleEnableConnectionSafetyTests.cs dropping its own self-exemption: verified by grep that this file's only mentions of TimescaleSupport.TryEnableAsync live inside a /// doc example, a /* */ block comment, and a regex string literal -- all of which StripCommentsAndStrings correctly removes, so no new false offender is introduced by scanning this file now.
  • New CommentFilterAdoptionTests.cs allow-list: grepped both test projects for every .StartsWith(comment-prefix) line-prefix-filter call site (excluding Assert.StartsWith, which the guard's own regex correctly excludes). The 6 remaining sites match the 6 entries in s_bounded exactly, so the new EveryLinePrefixCommentFilter_StatesTheBoundItRestsOn test should pass as written, and its stated rationale for each site checks out against each file's actual code.
  • Lite/Darling parity: this PR proactively closes a parity gap rather than opening one -- CSharpSourceWalker.cs stays authored once in Darling.Tests and is pulled into Lite.Tests.csproj via a Compile Link, with a comment explaining why a ProjectReference can't be used (both are xunit executables; CI would discover the Darling suite twice). The AnalysisPassTokenThreadingTests.cs changes are identical in shape between the Lite and Darling copies. AuroraOnlySqlIsGatedTests.cs and the Darling-only identity/MCP pins have no counterpart in the other app, but that's pre-existing (whole-tree or Darling-only concerns), not something this PR should have mirrored.
  • Performance: the AuroraOnlySqlIsGatedTests.cs change adds a raw-text Contains gate before running the (measured 10x costlier) walk, and the gate is proven exact -- the walker only ever replaces characters with spaces/newlines, so a needle absent from raw text can't appear in any derived view. No perf regression; if anything this file gets faster per the PR's own measurement.
  • Security: none of this touches SQL, secrets, network, or file/process boundaries beyond reading .cs source files under the repo root for test purposes.

No inline comments filed -- didn't find anything rising to an actionable issue.

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