Skip to content

Reject unclosed delimiters, quoted-string abutment, and nested comment imbalance#64

Open
mmucklo wants to merge 1 commit into
masterfrom
fix/goldstandard-conformance
Open

Reject unclosed delimiters, quoted-string abutment, and nested comment imbalance#64
mmucklo wants to merge 1 commit into
masterfrom
fix/goldstandard-conformance

Conversation

@mmucklo

@mmucklo mmucklo commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Structural over-acceptance fixes surfaced by gold-standard differential testing (dominicsayers/isemail 164-case corpus). Reduces the corpus false-accept set 29 → 26.

Fixed

  • Unclosed domain literaltest@[1.2.3.4 (no ]) now rejected (UnterminatedSquareBracket). The end-of-input unterminated-delimiter check is keyed on parser state rather than quote_temp, so unclosed brackets, comments, and obs-routes are all caught.
  • Unbalanced nested comment — a leading comment now opens at nest level 1 (matching the in-address entry), so ((comment)test@… is no longer treated as closed after a single ).
  • atext/quote abutting a quoted-string"test"test@… and "test""test"@… now rejected via a new AtextAfterQuotedString code; a quoted-string is a whole word (RFC 5322 §3.2.4). "word".atom (obs word "." word) stays valid.

Control-char-in-domain was already rejected — that corpus "miss" was an artifact of the Unicode Control-Pictures encoding the corpus uses for control chars.

Verification

94 → 95 tests; PHPStan level 8 / Psalm / cs clean. New testStructuralOverAcceptanceRejections covers every fixed case plus the well-formed neighbours that must stay valid.

Remaining (tracked in ROADMAP, not in this PR)

  • Comment nuances — backslash-escaped parens ((comment\)test@) and atext directly after a mid-local-part comment (test(comment)test@).
  • CR/LF & folding whitespace (~16 cases) — a deliberate design choice (batch trimming); needs a decision, not a blind fix.
  • Trailing root dot — intentional (RFC 5321 §2.3.5), documented.

…t imbalance

Structural over-acceptance fixes surfaced by gold-standard differential testing
(dominicsayers/isemail corpus). Reduces the corpus false-accept set from 29 to 26.

- Unclosed domain literal (test@[1.2.3.4) is rejected — the end-of-input
  unterminated-delimiter check is keyed on parser state rather than quote_temp,
  so unclosed brackets, comments, and obs-routes are all caught.
- Unbalanced nested comment (((comment)test@) is rejected — a leading comment
  now opens at nest level 1, matching the in-address entry.
- atext or a second quote abutting a quoted-string ("test"test@, "test""test"@)
  is rejected via a new AtextAfterQuotedString error code; "word".atom stays valid.

Control-char-in-domain was already rejected (a corpus encoding artifact). The
remaining CR/LF-whitespace and trailing-root-dot divergences are deliberate
design choices, tracked in ROADMAP for a decision. 94 -> 95 tests.
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.64%. Comparing base (6d91a69) to head (b3c6a4c).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##             master      #64      +/-   ##
============================================
+ Coverage     92.87%   93.64%   +0.77%     
- Complexity      381      385       +4     
============================================
  Files             6        6              
  Lines           983      992       +9     
============================================
+ Hits            913      929      +16     
+ Misses           70       63       -7     
Files with missing lines Coverage Δ
src/Parse.php 91.22% <100.00%> (+1.13%) ⬆️
src/ParseErrorCode.php 100.00% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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