Skip to content

js_parser: stop the // @bun keyword scan at the end of the pragma line - #46122

Open
robobun wants to merge 5 commits into
mainfrom
robobun/f81fed32/bun-pragma-stop-at-line-end
Open

robobun wants to merge 5 commits into
mainfrom
robobun/f81fed32/bun-pragma-stop-at-line-end

Conversation

@robobun

@robobun robobun commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Parser::has_bun_pragma (src/js_parser/parse/parse_entry.rs:2574) reads the // @bun line for the module loader. It skips the byte after @bun-cjs, @bytecode or a lone @, so it can read past the \n. A bare \r does not stop it.
  • bun build --target=bun --format=cjs output with @bytecode on line 2 makes the loader read <file>.jsc. JSC runs that file if it accepts it.
  • // @bun @bytecode\nconsole.log("@bun-cjs"); fails with TypeError: Expected CommonJS module to have a function wrapper. 1.3.5 runs it. fix(parser): fix bytecode CJS pragma detection after shebang #25868 (1.3.6) widened the scan.

Fix

  • Each arm of the loop moves the cursor past exactly what it matched: @bun-cjs, @bytecode, or one byte. The loop stops at \n or \r.
  • has_bun_pragma for a minified lodash bundle: 393,293 instructions to 69. No pragma: 26 to 22.
  • Verified: test/bundler/transpiler/bun-pragma.test.ts. 18 of 19 new tests fail on main.
  • Self-reviewed: 25 concerns raised, 21 addressed. Not done: a word-end rule and a whole-line reader (separate design decision), a tracking issue.

Background

  • bun build --target=bun output starts with // @bun. @bun-cjs marks a CommonJS wrapper function. @bytecode makes the loader read <file>.jsc.
  • JSC gets the source as Latin-1, where a // comment ends at \r or \n.
  • Considered strings::index_of_char to cut the line first: one more call per pragma source, same result.

Downsides

  • Four spellings bun build never writes change kind, as in 1.1.42: @@bun-cjs, @bytecode@bun-cjs, @@bytecode, @bun-cjs@bytecode.
  • // @bun @foo costs 121 instructions, up from 96.
  • Still wrong: // @bunny reads as the pragma, @bun-cjsx as @bun-cjs.
Notes

Repro

printf '// @bun @bytecode\nconsole.log("the text @bun-cjs is on the second line");\n' > esm.js
bun esm.js
Build esm.js // @bun\r and the same code line
1.3.5 exit 0 exit 0
1.3.6, 1.4.3, main prints the line, then the TypeError, exit 1 the same
this PR exit 0 exit 0

On main a space before the \n, or a CRLF line end, hides the bug. The skipped byte is then the space or the \r.

No GitHub issue reports this. It was found by a read of the code.

History

Where it shows

  • bun build --target=bun --format=cjs --minify of a file that holds the string "@bytecodealliance/preview2-shim" puts that string on line 2. On main the loader opens out.cjs.jsc on each load. With this PR it does not. A --banner '// built by @bytecode-labs' does the same, because the banner follows the { of the wrapper on line 2.
  • With a .jsc that JSC accepts for the text, main logs [Disk Cache] Cache hit for sourceCode and uses the bytecode. docs/bundler/bytecode.mdx names the @bytecode pragma as the gate for that read.
  • The TypeError needs // @bun @bytecode above ES module code, or // @bun @. bun build writes the first only with --compile, and a compiled executable is not affected. Bun.build({ compile, bytecode: true, format: "esm" }) returns that text as outputs[0]. A saved copy fails on main with SyntaxError: Unexpected keyword 'export' when its first code line holds @bun-cjs.
  • bun -e, stdin and blob: modules reach the same reader. On main, -e and stdin run the text as a CommonJS script, so an import statement on line 2 is a SyntaxError. A blob: import throws the TypeError.
  • bun fmt --end-of-line=cr turns // @bun\n... into // @bun\r.... The result fails on main.

The rule after this PR

The pragma line ends at the first \n or \r. A keyword is read at every @ of that line. 1.1.42 read the four spellings below the same way.

Pragma line 1.4.3 and main This PR 1.1.42
// @bun @@bun-cjs ES module CommonJS CommonJS
// @bun @bytecode@bun-cjs ES module, reads .jsc CommonJS, reads .jsc CommonJS, reads .jsc
// @bun @@bytecode ES module ES module, reads .jsc ES module, reads .jsc
// @bun @bun-cjs@bytecode CommonJS CommonJS, reads .jsc CommonJS, reads .jsc

The test has a row for a keyword after a lone @ (@@bun-cjs, @@bytecode @bun-cjs) and after a keyword (@bytecode@bun-cjs, @bun-cjs@bytecode). A word-end rule would change the two glued rows.

Numbers

Release builds on linux-x64: main at 3880151, and the same tree with this change. The instruction counts are gdb single steps from the entry of has_bun_pragma to its return.

First line main This PR
no pragma 26 22
// @bun, LF 45 40
// @bun, CRLF 55 43
@bytecode / @bun-cjs / both, LF, short code line 436 / 963 / 1,084 77 / 69 / 106
the same three, CRLF 86 / 83 / 114 80 / 72 / 109
the same three, a space before the LF 86 / 83 / 114 88 / 80 / 117
// @bun @foo 96 121
// @bun @ at the end of the text 56 56
// @bun @bun-cjs, first code line of 4 / 8 / 16 KiB 41,663 / 82,623 / 164,543 69 / 69 / 69
minified express / react-dom/server / lodash (first code line 150 B / 12,087 B / 39,321 B) 1,583 / 120,944 / 393,293 69 / 69 / 69
  • Calls of has_bun_pragma: 201 for a graph of 201 modules (CommonJS and ES module), 104 for require("express"). Both builds give the same counts.
  • has_bun_pragma goes from 353 to 326 bytes. Its only call target stays panic_bounds_check. size of the stripped binary gives 107,067,334 bytes of text for both builds.
  • Kind at the return for two lists of modelled first lines (50 and 49 rows): 8 and 9 rows change. These are the rows with keyword text on the next line, the bare CR row, @@bun-cjs and @bun-cjs@bytecode. None of the 44 rows with a header that bun build writes changes. No row moves between pragma and no pragma.
  • Opens of <file>.jsc per load of the --banner build above: 1 to 0. The same build with --bytecode: 1 to 1.
  • User instructions of the whole process (perf_event_open, interleaved runs, medians). Minified lodash bundle with BUN_JSC_useConcurrentJIT=0 BUN_JSC_useConcurrentGC=0: 39,013,027 to 38,606,348 (1.04% less, noise 0.02%). With default settings the noise is 2.1% and hides the change. A script with no pragma: 3,761,160 to 3,763,113 (noise 3,312).

Not changed here

Each of these fails the same way on main and on this PR. 1.1.42 runs all of them.

Related

Tests

  • test/bundler/transpiler/bun-pragma.test.ts has 26 tests. Release build of main: 8 pass, 18 fail. Debug build of this PR: 26 pass.
  • The one new test that passes on main is the control: // @bun @bytecode @bun-cjs reads its .jsc.
  • Also run on the debug build: test/cli/run/commonjs-invalid.test.ts, test/cli/run/run-cjs.test.ts, test/bundler/transpiler/runtime-transpiler.test.ts, test/bundler/bundler_banner.test.ts, test/bundler/bundler_bun.test.ts, test/bundler/bundler_cjs.test.ts.

Self-review, not done

  • A word-end rule for the prefix and the keywords, and a whole-line reader in place of the loop. Both decide which files are pragma files.
  • A tracking issue for the list under "Not changed here".

`has_bun_pragma` moved the cursor past a matched `@bun-cjs` or `@bytecode`
and then stepped once more, so it never examined the byte after the
keyword. When that byte was the `\n` that ends the pragma line, the scan
read the next line too. A lone `@` before the `\n` did the same.

The `@` arm now compares at `contents[cursor + 1..]` and leaves the cursor
on the last byte of a matched keyword. The loop has one step, and it
examines every byte of the pragma line.
The module loader gives a `// @bun` source to JSC as Latin-1, and there a
`//` comment ends at `\r` or `\n`. `has_bun_pragma` stopped at `\n` only, so
it read the code after a bare `\r` as pragma text.

Tests: a bare CR row, a `bun -e` row, and a bare `@bytecode` token in the
sidecar rows.
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: f947403d-603e-439d-be21-daa11543b8ab
📥 Commits

Reviewing files that changed from the base of the PR and between 3bfaecd and 77dccac.

📒 Files selected for processing: 1
  • src/js_parser/parse/parse_entry.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Walkthrough

Changes

Bun pragma parsing

Layer / File(s) Summary
Pragma-line detection and tests
src/js_parser/parse/parse_entry.rs, test/bundler/transpiler/bun-pragma.test.ts
The scanner stops at \r or \n and matches @bun-cjs or @bytecode from each @. Tests cover execution modes, hashbangs, adjacent keywords, and .jsc sidecar reads.

Suggested reviewers: jarred-sumner

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 65b1c

Merge Risk: ⚪ Minimal · up to 65b1c

The pragma parsing fix is mergeable with no identified blocking risk.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: stopping the // @bun keyword scan at the pragma line.
Description check Passed The description explains the problem, fix, scope, known limitations, reproduction, and verification results. It does not use the template headings exactly, but it provides the required change summary …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the problem, fix, scope, known limitations, reproduction, and verification results. It does not use the template headings exactly, but it provides the required change summary and detailed verification evidence.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:05 AM PT - Oct 20th, 2026

❌ @robobun, your commit 65b1c85 has 1 failures in Build #129232 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 46122

That installs a local version of the PR into your bun-46122 executable, so you can run:

bun-46122 --bun

@robobun

robobun commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. The diff is green. The one red CI job is not from this change.

Repro (main and Bun 1.4.3):

printf '// @bun @bytecode\nconsole.log("the text @bun-cjs is on the second line");\n' > esm.js
bun esm.js
# prints the line, then:
# TypeError: Expected CommonJS module to have a function wrapper. If you weren't messing around with Bun's internals, this is a bug in Bun
# exit 1

With this PR (#46122) the file runs as an ES module and exits 0. In test/bundler/transpiler/bun-pragma.test.ts, 18 of the 26 tests fail on main and all 26 pass with the fix.

CI (build 129232 on 65b1c85): 179 jobs passed, 1 failed, 2 did not run.

  • The failed job is windows 2019 x64 - test-bun. It failed in test/cli/bake/bake.test.ts with EBUSY: resource busy or locked, rm when tempDir removes its folder. test: fix bake.test.ts cleanup and the --check URL on Windows #46135 fixed that test on main after this branch was cut, and the branch does not have that commit. An update of the branch from main picks it up.
  • The two jobs that did not run are darwin any x64 - test-bun. They expired without an agent.
  • test/bundler/transpiler/bun-pragma.test.ts is not in the failure set of any lane that ran: Linux, Windows, and macOS aarch64.
  • Three other tests failed once and passed on a retry: test-quic-error-destroy-rejects-promises.mjs, s3.leak.test.ts and abort-signal-leak.test.ts. This change does not touch them.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline findings, I also checked the removed cursor >= contents.len() guard against the new contents[cursor + 1..] slice in has_bun_pragma (src/js_parser/parse/parse_entry.rs:2581): the loop bound end is contents.len(), so cursor + 1 <= len holds on every iteration and a source ending in a bare @ cannot panic. I also traced the keyword arithmetic — a match adds the keyword length, leaving the cursor on the keyword's last byte, and the shared cursor += 1 lands on the following byte, so a \n directly after @ bun-cjs/@ bytecode now terminates the scan as intended.

Extended reasoning...

The change rewrites the @ arm of the // @ bun pragma keyword loop in src/js_parser/parse/parse_entry.rs and adds a \r line terminator, plus a new subprocess test matrix in test/bundler/transpiler/bun-pragma.test.ts; it touches the module loader's classification of already-bundled sources (ESM vs CJS, .jsc sidecar read) but no auth, crypto, or injection surface. Inline findings on glued-keyword behavior changes, the missing word-end rule, and the hashbang skip ignoring \r already signal that a human should weigh those design decisions, so this note only records the bounds and cursor-arithmetic checks that were ruled out.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 src/js_parser/parse/parse_entry.rs — Users with an existing already-bundled file whose pragma line holds a glued keyword now get a different module kind, or a .jsc sidecar read, than on main. At parse_entry.rs:2581 the @ arm now peeks at every @, so // @ bun @@ bun-cjs and @ bytecode@ bun-cjs become CommonJS and // @ bun @@ bytecode and @ bun-cjs@ bytecode now request bytecode; on main the byte after a keyword or lone @ was skipped and these resolved differently. A .jsc next to such a file is now loaded. Fix: either keep main's resolution for these four spellings or add a word-end rule so a keyword only matches when followed by space, tab, CR, LF or EOF, and cover all four spellings in the test.

    Why this was flagged

    If any stored bundle or hand-written loader file already carries // @ bun @@ bytecode or // @ bun @ bun-cjs@ bytecode on its first line, the loader behaviour flips after this merge. On the base, at @@ bytecode the first @ advanced cursor onto the second @, saw it was not b, advanced again onto b, so bytecode was never matched and no .jsc was opened. With the new arm at parse_entry.rs:2581-2588 the second @ is visited and bytecode matches, so src/bundler/transpiler.rs:1630-1677 opens <file>.jsc and hands it to JSC as bytecode for the source. Similarly @ bytecode@ bun-cjs was ESM on base and is now CommonJS. The only in-tree writer emits space-separated keywords, so no bun build output changes, but the pragma is a documented public format that other tools and users write; the PR tests only @@ bun-cjs. Remedy: add a word-end check after each keyword match so glued spellings keep one well-defined meaning, and test all four.

    Verification: Base: on @ the arm did cursor += 1; if contents[cursor] != b'b' { cursor += 1; continue; }, so the byte after a lone @ was never examined. New code (src/js_parser/parse/parse_entry.rs:2578-2592): let slice = &contents[cursor + 1..]; on every @, so a second @ there is visited. @@ bytecode -> Bytecode. When the result is Bytecode/BytecodeCjs, src/bundler/transpiler.rs:1630-1680 opens <path>.jsc.

  • 🟣 src/js_parser/parse/parse_entry.rs — Users running a CR-only file with a hashbang above // @ bun @ bun-cjs get their module silently misloaded, even though this PR now says the pragma line ends at \r. The hashbang skip at parse_entry.rs:2555 still scans only for \n, so after #!/usr/bin/env bun\r it either runs off the end and returns None, or lands on a later line and misses the pragma. The lexer at src/js_parser/lexer.rs:1288 ends the hashbang at CR, LF, U+2028 and U+2029, so the two readers disagree. Fix: stop the hashbang skip at the same terminators the pragma line uses (\n or \r), in this PR, since it is the sibling site of the same line-ending class.

    Why this was flagged

    The trigger is a source whose hashbang line ends in a bare \r, e.g. the output of bun fmt --end-of-line=cr on a bun build --target=bun --format=cjs bundle. It arrives through the runtime loader via has_bun_pragma at parse_entry.rs:888 with has_hashbang true, because the lexer at src/js_parser/lexer.rs:1288 ends the hashbang at 0x0D. The skip loop at parse_entry.rs:2555-2560 only compares against b'\n'; with no LF in the file it returns None, so the file is transpiled as plain JS and the CJS wrapper function expression is evaluated and discarded, printing nothing and exiting 0. With a stray LF later in the file, cursor lands on an arbitrary line, the prefix check at 2567 fails, and the same silent misload happens. The base has the same loop, but this PR adds \r as a pragma line terminator at parse_entry.rs:2577 and tests both 'a bare CR ends the pragma line' and 'below a hashbang line' separately, never together; REVIEW.md requires fixing every sibling site in the same class in the same PR. Remedy: terminate the hashbang skip at b'\n' | b'\r' as well.

    Verification: src/js_parser/parse/parse_entry.rs:2554-2565 (unchanged by the diff) only looks for \n. The lexer (src/js_parser/lexer.rs:1288) ends the hashbang at CR as well, so for #!/usr/bin/env bun\r// @ bun @ bun-cjs\r the loop returns None and the pragma is missed. On the base commit the same input takes the same route, so merging makes nothing worse. The new test row at test/bundler/transpiler/bun-pragma.test.ts:61 only covers a hashbang terminated by \n.

Comment thread src/js_parser/parse/parse_entry.rs Outdated
The reader now examines every byte of the pragma line, so a keyword is read
at every `@`. Add rows for `@@bytecode @bun-cjs`, `@bytecode@bun-cjs` and
`@bun-cjs@bytecode` beside the `@@bun-cjs` row. Each fails on main.
Comment thread src/js_parser/parse/parse_entry.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the final state of this PR (including the test-only commit 3bfaecd) and found no bugs; because it changes which spellings of the // @ bun line classify a file as CommonJS or bytecode, a maintainer should still weigh in on that semantics choice.

What was reviewed:

  • Diffed old vs new control flow of the @ arm in has_bun_pragma (src/js_parser/parse/parse_entry.rs:2578-2588): the contents[cursor + 1..] slice is in bounds because the loop guard gives cursor < contents.len(), and after a starts_with match the += len then += 1 cannot overshoot end.
  • Traced the consumer in src/bundler/transpiler.rs:1630-1686: the .jsc sidecar read is gated on virtual_source.is_none() && allow_bytecode_cache, so the bun -e test exercises only the ESM/CJS kind, as intended.
  • Checked the sidecar harness against existing suites: vm.Script produceCachedData is implemented in NodeVMScript.cpp and the [Disk Cache] Cache hit for sourceCode probe under BUN_JSC_verboseDiskCache=1 is the same signal test/bundler/bun-build-compile.test.ts already relies on.
Extended reasoning...

The change is a ten-line rewrite of the keyword loop in Parser::has_bun_pragma plus a new test matrix in test/bundler/transpiler/bun-pragma.test.ts; it touches no auth, crypto or injection surface, though the pragma does gate whether the loader opens a .jsc sidecar. The Rust change is small and its bounds arithmetic checks out, but it deliberately alters classification for glued spellings (@@ bun-cjs, @ bytecode@ bun-cjs, @ bun-cjs@ bytecode) and adds a bare-CR terminator, which the PR itself lists as a semantics decision. The sibling hashbang-skip loop still ends only at \n and the missing word-end rule (already flagged inline in the earlier run) remain explicitly out of scope per the description. The tests could not be executed in this environment, so the .jsc cache-hit control row rests on the author's claim.

Still open from earlier reviews (1):

  • Unresolved: 1 minor or pre-existing.

Each arm of the keyword loop now steps by the length of what it matched:
`@bun-cjs`, `@bytecode`, or one byte. This is the form of the prefix test
above the loop, and it needs no shared step after the match.
@robobun

robobun commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Answers to the review points. The code changes are in 3bfaecd and 77dccac.

A keyword directly after an @ or after a keyword. The new result is intended. The old loop skipped one byte after a lone @ and after a keyword, and that skip is the bug: when the byte is the \n, the scan reads the next line. Without the skip, a keyword is read at every @ of the pragma line. The published 1.1.42 reads all four spellings the same way as this PR, and bun build writes none of them. 3bfaecd adds a test row for each position: @@bun-cjs, @@bytecode @bun-cjs, @bytecode@bun-cjs and @bun-cjs@bytecode. Each row fails on main.

A hashbang line that ends with a bare \r. The finding is correct, and the bug is older than this PR. I did not fold it in, because a change of the reader alone breaks a bundle that runs today:

bun build --target=bun --format=cjs --banner $'#!/usr/bin/env bun\r// text' entry.js --outfile out.cjs
bun out.cjs

bun build cuts a hashbang banner at the first \n (src/bundler/linker_context/postProcessJSChunk.rs:413). So the output starts with #!/usr/bin/env bun\r// text\n// @bun @bun-cjs\n. If the skip in has_bun_pragma stops at the \r, the next bytes are // text, the prefix test fails, and the wrapper never runs (exit 0, no output). The reader and the writer have to change together, and #43834 rewrites the same if.

No word end after // @bun, @bun-cjs and @bytecode. Also correct, and it dates from #15912 (1.1.43). // @bundleInto:common above TypeScript prints on 1.1.42 and fails with SyntaxError: Unexpected token ':' from 1.1.43 to main. This PR does not change it. A fix changes which files count as pragma files, so it needs its own decision. The old helper is not enough: // @bun-serve docs above TypeScript fails on 1.1.42 too. On #15888 Jarred wrote that the match is "supposed to only apply when there is a space or tab character between".

The comment in the @ arm. Done in 77dccac. Each arm now moves the cursor past exactly what it matched, in the same form as the prefix test above the loop. The comment is gone. The kind is the same for all 99 first lines that I compared, and a file with no pragma costs 22 instructions in has_bun_pragma, down from 26.

The PR body lists the last two bugs under Downsides and "Not changed here". No issue tracks them yet.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the latest push and didn't find any bugs. A human look is still worthwhile because the final rule ("a keyword is read at every @ of the pragma line, with no word-end check") deliberately changes the kind for the four glued spellings and leaves the word-end rule open, which is a loader-semantics decision rather than a pure bugfix.

What was reviewed:

  • The rewritten loop in src/js_parser/parse/parse_entry.rs: bounded by cursor < end, every arm breaks or advances, starts_with on contents[cursor..] cannot overrun; the last commit is step-for-step equivalent to the previous push (7+1 == len("@ bun-cjs")).
  • The .jsc sidecar tests: BUN_JSC_verboseDiskCache and the "Cache hit" line are already relied on by bun-build-compile.test.ts, vm.Script({ produceCachedData }) is implemented in NodeVMScript.cpp, and transpiler.rs only opens <path>.jsc for the Bytecode/BytecodeCjs variants, so the readsSidecar true/false rows are load-bearing against the old scan.
  • Harness conventions: using/await using, concurrent pipe drain, combined toEqual object, tempDir, it.concurrent.each.
Extended reasoning...

The PR rewrites the ~15-line keyword scan in Parser::has_bun_pragma (src/js_parser/parse/parse_entry.rs) so the // @ bun line ends at \n or \r and each @ arm advances by exactly what it matched, and adds a test matrix in test/bundler/transpiler/bun-pragma.test.ts covering entry/require/import, bun -e, and .jsc sidecar reads. The security-relevant surface is the gate that decides whether a file is handed to JSC untranspiled and whether a sibling .jsc bytecode file is loaded; the change narrows that gate to the pragma line, which is strictly safer than the old scan that read into the next source line. No CODEOWNERS entry covers either file, the bug hunt ran dry, and index arithmetic is bounds-safe. Defer rather than approve because the PR intentionally changes the kind assigned to @@ bun-cjs-style glued spellings and explicitly leaves the word-end rule as a separate design decision, which a maintainer should weigh.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant