Skip to content

[patch] Count every line break in HasMinimumLines, HasMaximumLines and HasExactLines - #394

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/299-count-every-line-break
Oct 8, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/299-count-every-line-break

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #299

Problem

HasMinimumLines, HasMaximumLines and HasExactLines counted only \n. IsMultiLine and IsSingleLine also treat \r and U+2028 as breaks. So "a\rb" passed [IsMultiLine] but counted as one line: [HasMaximumLines(1)] accepted strings that [IsSingleLine] rejected, and [IsMultiLine, HasMinimumLines(2)] contradicted itself. The CRLF "don't double count" block worked out to the same number it started with.

Change

  • New internal LineBreaks helper in Semantics.Strings/Validation/:
    • IsLineBreak(char) returns true for \n, \r, U+2028 (line separator) and U+2029 (paragraph separator).
    • CountLines(string) counts \r\n as one break and every other break character as one each.
  • All three Has*Lines attributes use CountLines, and the no-op CRLF block is removed.
  • IsMultiLine and IsSingleLine use IsLineBreak, so all five attributes share one definition.

Behavior change to review: the issue lists U+2029 as a break, so I included it. As a result, IsSingleLine now rejects U+2029 and IsMultiLine now accepts it. Before this PR, neither treated it as a break. If you'd rather leave U+2029 out, delete the ParagraphSeparator case from IsLineBreak and the two U+2029 DataRows.

Tests (LineCountValidatorsTests)

  • EveryLineEndingCountsAsOneBreak checks \r, \n, \r\n, U+2028 and U+2029: each gives exactly 2 lines in HasExactLines(2), HasMinimumLines(2) and HasMaximumLines(2), and fails HasMaximumLines(1).
  • ConsecutiveCrlfPairsAreSeparateBreaks checks that "a\r\n\r\nb" is 3 lines and "a\n\rb\r\nc" is 4.
  • LineCountsAgreeWithIsMultiLineAndIsSingleLine checks that the same strings pass IsMultiLine and fail IsSingleLine.

With the attribute changes reverted, 5 of the 14 tests in the class fail. With the changes, all pass. The full suite passes locally: 1520 passed, 8 skipped, 0 failed. The one reported error is the Semantics.Cpp.Test net9.0 leg, which could not start because this container has only the .NET 10 runtime. It is unrelated to this change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FtvW1bPVVfCTF7fCWpeyRE


Generated by Claude Code

claude added 2 commits October 7, 2026 19:30
…d HasExactLines

The three Has*Lines attributes counted only '\n', so a lone '\r', U+2028
or U+2029 added no line, while IsMultiLine and IsSingleLine treated '\r'
and U+2028 as breaks. "a\rb" was multi-line yet had one line, so
[HasMaximumLines(1)] accepted strings [IsSingleLine] rejected. The CRLF
block meant to stop double counting computed the same number it started
with.

A shared LineBreaks helper now defines a break once: '\n', '\r', U+2028
and U+2029, with "\r\n" counting as one. All five line attributes use it,
and the no-op CRLF block is gone.

Fixes #299

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtvW1bPVVfCTF7fCWpeyRE
SonarCloud S127: CountLines skipped the '\n' of a "\r\n" pair by
incrementing the for-loop variable in its body. It now leaves the
counter alone and skips a '\n' whose previous character is '\r'.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtvW1bPVVfCTF7fCWpeyRE
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit dd67034 into main Oct 8, 2026
15 checks passed
@matt-edmondson
matt-edmondson deleted the fix/299-count-every-line-break branch October 8, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants