Skip to content

Treat ' directly after a word as an apostrophe (GH #943) - #969

Merged
cscheid merged 2 commits into
mainfrom
bugfix/issue-943
Oct 30, 2026
Merged

cscheid merged 2 commits into
mainfrom
bugfix/issue-943

Conversation

@cscheid

@cscheid cscheid commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Fixes #943.

What changes

A straight ' directly after a word, outside an open single quote, is now an apostrophe instead of a quote mark. Dogs' toys and cats' toys. renders as <p>Dogs’ toys and cats’ toys.</p>, matching pandoc. Also fixed: the cats'., Cats' at end of block, It's the cats' toys., and d' before a code span (one half of #221). It works at paragraph start, in list items, headings, block quotes, the first cell of a pipe table, and inside double quotes. 'Dogs' toys' still closes at the first ', and 'Hello world' still pairs.

How

The external scanner applies pandoc's notAfterString rule for ASCII words. The tree-sitter lexer cannot look behind, so the decision is made at the start of each word: the scanner reads ahead, and if the word is followed by ' plus a non-alphanumeric, it emits word' as one _pandoc_literal_str. Otherwise it returns false and the internal lexer proceeds as before.

"Outside a single quote" is a serialized single_quote_depth counter maintained in the scan wrapper (+1 on open, -1 on close, reset at line endings and block closes). valid_symbols[SINGLE_QUOTE_CLOSE] cannot serve that purpose because a closer is not accepted right after whitespace inside a quote.

One grammar change: a hidden zero-width _paragraph_start token, optional at the front of pandoc_paragraph. At a block start the scanner must also decide whether the line is a pipe-table header, and the two candidate tokens need different ends. When the first word ends in an apostrophe and the line is not a table header, the scanner emits the hint and the next call emits the word. No conflicts: were added; tree-sitter generate produced a deterministic table.

Fallout

  • Regenerating the parser renumbers LR states, so _autogen-table.json and the Q-2-10 case files were rebuilt with scripts/build_error_table.ts.
  • Fixtures in pampa, quarto-core, quarto and qmd-syntax-helper that used groups'-style apostrophes as their parse-error trigger now use a ' with whitespace around it, which still errors. Two LMD snapshots shifted by one column.
  • docs/errors/markdown/Q-2-10.qmd, Q-2-7.qmd and the markdown migration guide are updated.
  • Four claude-notes/plans/*.md files relied on an accidental pairing: a plural possessive such as packages' was read as an opening quote that a later `code`'s closed. With the possessive now an apostrophe, the ' after the code span became an unclosed opener and the strict claude-notes render failed in CI. Those apostrophes are escaped as `code`\'s, per the migration guide.

Known limits (pinned in tests)

Words starting with a digit or a non-ASCII letter (1990s', Agnès'), a ' after a closing delimiter (`a.rs`'s, still Q-2-7), and a possessive inside an emphasis inside a quote. Each keeps its previous error.

Verification

  • tree-sitter test: 840/840 (10 new corpus cases in inline-quotes.txt)
  • cargo xtask test: 17518 passed, 200 skipped
  • clippy (-D warnings) and cargo xtask lint clean
  • tree-sitter parse --time on a 300 KB document: unchanged within noise
  • Windows cross-check (--target x86_64-pc-windows-msvc) could not run: the tree-sitter C build needs Windows headers. The Rust changes are tests and fixtures only.

🤖 Generated with Claude Code

Plural possessives (`Dogs' toys`, `the cats'.`) and `d'` before a code
span were reported as Q-2-10 and failed the render, because the grammar
read every non-word-internal `'` as a quote mark.

The external scanner now applies pandoc's `notAfterString` rule for
ASCII words: at the start of a word it looks ahead, and when the word
is followed by `'` plus a non-alphanumeric it emits `word'` as one
`_pandoc_literal_str`, outside an open single quote. A serialized
`single_quote_depth` counter tracks whether a single quote is open; it
is maintained in the scan wrapper and reset at line endings and block
closes.

At a block start the scanner must also decide whether the line is a
pipe-table header, and the two candidate tokens end at different
places. A hidden zero-width `_paragraph_start` token, optional at the
front of `pandoc_paragraph`, lets the scanner commit to a paragraph
when the first word ends in an apostrophe and the line is not a table
header. No `conflicts:` were added; the generated table stays
deterministic.

Regenerating the parser renumbers LR states, so the pampa error table
was rebuilt. The Q-2-10 corpus case and the fixtures that used
`groups'`-style apostrophes as a parse-error trigger now use a `'`
with whitespace around it, which still errors. The Q-2-10 and Q-2-7
error pages and the markdown migration guide are updated.

Known limits, pinned in tests: words starting with a digit or a
non-ASCII letter (`1990s'`, `Agnès'`), a `'` after a closing delimiter
(`` `a.rs`'s ``), and a possessive inside an emphasis inside a quote.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@posit-snyk-bot

posit-snyk-bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

…de-notes

CI's rustfmt check caught the unformatted test file. The strict
claude-notes render then failed on four plan files: on main, a plural
possessive such as `packages'` was mis-read as an opening quote that a
later `` `code`'s `` happened to close. With `packages'` now an
apostrophe, the `'` after the code span is an unclosed opener (Q-2-7),
the documented limit for a `'` after a closing delimiter. Those
apostrophes are now escaped as `` `code`\'s ``, which is the migration
guide's fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@cscheid
cscheid merged commit c99959d into main Oct 30, 2026
18 of 19 checks passed
@cscheid
cscheid deleted the bugfix/issue-943 branch October 30, 2026 01:28
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.

pampa: plural possessive apostrophes (Dogs' toys) are reported as Q-2-10 and fail the render

2 participants