Close the two remaining review findings: one real, one demonstrably not - #77
Merged
Merged
Conversation
closesAttribute() asked whether the raw line held `)]` anywhere. A Justification quoting code can hold one — "the analyzer reads GetValues(type)] wrongly" — and on the line that opens a wrapped suppression that made the site look like one already written on a single line. Neither mode then had anything to say about it: check reported nothing, --fix rewrote nothing, and both exited 0, which is what a clean tree answers. That is the third time this rule has given that answer, and the reason its test file exists. The bracket tests now read the line with the interior of its double-quoted strings removed. It is not a C# lexer and does not need to be: a raw string never reaches here, since the """ fences already skip it, and a doubled quote inside a verbatim string closes and reopens, which ends in the same place. Deliberately it does not also cut the line at a //. Doing so would read a Justification citing a URL as a comment and take the real `)]` away with it, turning a suppression already on one line into a wrapped one — whose forward scan is what deleted two members the last time it was let loose. So a `)]` written inside a comment is still read as a close, and joins the shapes this admits it cannot read: the comment would swallow the rest of the joined line, so there is no rewrite to offer there anyway. Two fixtures hold both directions — one wrapped suppression carrying a bracket in its Justification, now reported and joined, and one already on one line carrying a URL, still untouched with the member below it intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0191jYiaUYBQSdwfXNME8gVB
EscapeIgnoringCase walks a name char by char, and OrdinalIgnoreCase does not: it folds a surrogate pair as one Unicode scalar, so it answers U+10400 and U+10428 equal where char.ToUpperInvariant applied to each code unit says they differ. 260 non-BMP scalars are cased that way, and every one of them is an identifier character in Unicode terms — so the pattern would advertise a value the binder accepts under a different casing and exclude the casing itself. None of them can reach that method, which is why nothing is changed here. What arrives is an unannotated member's C# name, and C# has no identifier carrying a supplementary-plane character: Roslyn lexes identifiers a char at a time, so a surrogate is category Cs and not an identifier character. All three spellings were put to the compiler — the character itself, \U00010400, and the surrogate pair as two \uXXXX escapes — and each is CS1056, "unexpected character". A declared name is an ordinary string literal and may hold a surrogate pair, but it is matched ordinally and written by EscapeForJsonSchema, which is the same walk on both sides. It cannot arrive by the other door either: reaching EscapeIgnoringCase means also being the C# name of an unannotated member, and EMN0005 refuses that enum before a schema is built. Written down because the gap between the two rules is real and only the unreachability closes it, which is not something a reader can see from the code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0191jYiaUYBQSdwfXNME8gVB
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.
Summary
The last review pass left two open questions before
1.0.0-rc.1. One turned out to be a realfalse negative in the layout checker and is fixed; the other is a gap that cannot be reached from
C# and is written down rather than coded around.
Type of change
Changes
tools/style/lint-layout.sh:closesAttribute()asked whether the raw line held)]anywhere.A
Justificationquoting code can hold one —"the analyzer reads GetValues(type)] wrongly"—and on the line that opens a wrapped suppression that made the site look like one already
written on a single line. Neither mode had anything to say about it: check reported nothing,
--fixrewrote nothing, both exited 0 — which is what a clean tree answers. The bracket tests nowread the line with the interior of its double-quoted strings removed.
//. That reads aJustificationciting a URL asa comment, takes the real
)]away with it, and turns a suppression already on one line into awrapped one — whose forward scan is what deleted two members the last time it was let loose. A
)]inside a comment therefore stays read as a close, and joins the shapes the checker admits itcannot read: joining would fold the comment over the rest of the line, so there is no rewrite to
offer there anyway.
tools/style/lint-layout.test.sh: two fixtures, one per direction. A wrapped suppression carryinga bracket in its
Justification, now reported and joined; and one already on one line carrying aURL, still untouched with the member below it intact — the assertion that keeps the comment from
being cut first.
CLAUDE.md: records which of the two readings the checker takes and why the other is worse.EnumMemberNameSchemaTransformer: a remark onEscapeIgnoringCasesaying why walkingcharbycharis exact for every name that reaches it, since it is narrower thanOrdinalIgnoreCaseingeneral.
OrdinalIgnoreCasefolds a surrogate pair as one Unicode scalar, so it answers U+10400and U+10428 equal where
char.ToUpperInvariantapplied to each code unit says they differ; 260non-BMP scalars are cased that way. None can arrive: what reaches the method is an unannotated
member's C# name, and C# has no identifier carrying a supplementary-plane character. All three
spellings were put to the compiler — the character itself,
\U00010400, and the surrogate pair astwo
\uXXXXescapes — and each is CS1056. A declared name may hold one, but it is matchedordinally and written by
EscapeForJsonSchema, the same walk on both sides; and it cannot arriveby the other door either, since reaching
EscapeIgnoringCasemeans also being the C# name of anunannotated member, which
EMN0005refuses before a schema is built. No code changed.Testing
dotnet build -c Release— clean, warnings are errors heredotnet test -c Release— 972 passed, 0 failedtests/PackageSmokeTest/run.sh— run on this branch, green; not strictly required, sincepackaging, the analyzers and the public entry points are untouched
tools/style/lint-layout.test.sh, thentools/style/lint-layout.shover the repositorytools/release-notes/changelog-section.test.shandtools/commit-lint/lint-commit-message.test.shBeyond the two findings, three further candidates were reproduced and then rejected, so they are
recorded here rather than acted on:
ß, prefix-overlappingnames, regex syntax, one non-
[Flags]) and roughly 11 000 inputs — casings, commas, trailingcommas, and the twenty-five whitespace code points — found no divergence between the binder,
System.Text.Jsonand the emitted OpenAPI pattern.List<TFlags?>: the document types the componentstringwhile the server accepts a nullelement. ASP.NET Core emits
{"type": "string"}identically forList<X?>andList<X>, so thenullability signal is absent rather than dropped, and inventing one would over-promise on the
non-nullable use. Present with and without this package.
already emits
enum: [..., null]there, and the transformer produces the same plustype: ["null", "string"]. The server answers 200/400 identically either way, so the fixreproduces what the platform says and adds nothing.
Public API
Documentation
docs/updatedCHANGELOG.mdanddocs/for-users/CHANGELOG.fr.mdboth updatedNothing under
docs/changed: the two documentation edits are a<remarks>block andCLAUDE.md,neither of which is a paired page. No changelog entry either — neither change is visible to a
consumer of the packages.
Unrelated to this branch, but on the path to tagging:
CHANGELOG.mdstill carries only## [Unreleased], sotools/release-notes/changelog-section.sh 1.0.0-rc.1exits 1 and the releaseworkflow would stop at "Resolve the release notes" — before any build. The section has to be written
under the exact name the tag will carry.
Generated by Claude Code