Skip to content

refactor: spell control characters as escapes, and lint for raw ones - #29948

Open
veksa wants to merge 2 commits into
prisma:mainfrom
veksa:fix/raw-nul-separator
Open

refactor: spell control characters as escapes, and lint for raw ones#29948
veksa wants to merge 2 commits into
prisma:mainfrom
veksa:fix/raw-nul-separator

Conversation

@veksa

@veksa veksa commented Aug 10, 2026

Copy link
Copy Markdown

Linked issue

n/a — nothing filed. Say the word if you'd rather talk the check over first.

Summary

Two files hold a control character as a raw byte where an escape was meant. Same string at runtime, so nothing misbehaves. The cost is all in the tooling around the code.

packages/2-sql/1-core/contract/src/validators.ts separates the halves of a (tableName, name) key with a NUL. Git reads a file containing a NUL as binary, so GitHub shows no diff for 1100 lines of validation logic, and blame, log -S and grep skip it.

packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts has a raw BEL in a test input. That test is about escaping control characters, every neighbouring line writes its own character as an escape, and the expectation on the same line asks for the escaped form — so the intent isn't in question.

The separator itself is a good idea and a house pattern. schema-diff.ts has SIBLING_KEY_DELIMITER, psl-field-resolution.ts has MODEL_COORDINATE_SEPARATOR, migration-graph.ts spells out the reasoning in a comment: no identifier can contain a NUL, so ("a_b", "c") can never collide with ("a", "b_c"). Only the spelling was off.

The second commit adds pnpm lint:control-bytes so nobody has to catch this by eye next time.

Testing performed

Finding them meant scanning all 6711 tracked files byte by byte. Git's own binary flag wasn't enough — it only reads the first 8000 bytes, so a NUL further down a long file never shows up.

  • pnpm lint:control-bytes against the pre-fix files exits 1 and names both, with line and byte; on the fixed tree it exits 0
  • node --test scripts/lint-control-bytes.test.mjs — 7 cases: NUL, the rest of the C0 range, an already-escaped character, tab/LF/CR, binary formats, and both exit codes
  • pnpm test:scripts — 395 tests, 371 pass. Without this branch it's 388/364 with the same 24 failures, so nothing here adds one. Those 24 are path-separator assertions in unrelated scripts that only fail on Windows
  • pnpm --filter @internal/emitter test 208/208 and pnpm --filter @internal/sql-contract test 322/322, plus typecheck and lint on both
  • pnpm lint:workflows after adding the CI step

git ls-files --eol now calls validators.ts i/lf, where it was i/-text. It's a text file again.

Skill update

n/a — internal tooling, plus two literals rewritten to the same value.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is a conventional commit (per CONTRIBUTING.md for external contributors).
  • The Skill update section above is filled in.

Notes for the reviewer

The check skips binaries by file extension rather than by sniffing content, which is the one call worth arguing about. A NUL byte is what makes a file look binary in the first place, so sniffing would skip exactly the files this is meant to find. The cost is that a new binary format has to be added to the list, and until it is the check complains instead of going quiet. I'd rather have it that way round, but it's your call.

It also only looks at tracked files, so a violation in a file you haven't added yet is invisible. I walked into that myself while writing it: the script picked up a raw NUL in its own doc comment and reported the tree clean, because it wasn't tracked. CI runs against a commit, so everything is tracked there.

In validators.ts I named a constant instead of inlining the escape, following the two sites that already do that. Both styles exist in the tree, so switch me if you prefer the inline one.

Two commits, literals first and the check second, so you can drop the CI step without losing the fix.

Summary by CodeRabbit

  • New Features

    • Added a new validation check to catch raw control characters in source files during CI.
    • Added a command to run this check locally from the package scripts.
  • Bug Fixes

    • Improved handling of escaped control characters in serialization tests, keeping output unchanged.
  • Tests

    • Added coverage for detecting raw control bytes, reporting file/line details, and ignoring binary or whitespace-safe cases.

veksa added 2 commits August 10, 2026 10:47
Signed-off-by: Aleksandr Khizhnyi <mendler88@gmail.com>
Signed-off-by: Aleksandr Khizhnyi <mendler88@gmail.com>
@veksa
veksa requested a review from a team as a code owner August 10, 2026 08:13
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ed11c7e-db23-4ac0-bb57-11335455f8b4

📥 Commits

Reviewing files that changed from the base of the PR and between e43c51a and 7c89b8f.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • package.json
  • packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts
  • packages/2-sql/1-core/contract/src/validators.ts
  • scripts/lint-control-bytes.mjs
  • scripts/lint-control-bytes.test.mjs

📝 Walkthrough

Walkthrough

The pull request adds a control-byte lint script, tests its detection and reporting behavior, wires it into package scripts and CI, and replaces raw control-character source occurrences with escaped or named representations.

Changes

Control-byte linting

Layer / File(s) Summary
Scanner and validation
scripts/lint-control-bytes.mjs, scripts/lint-control-bytes.test.mjs
The scanner checks Git-tracked text files for disallowed C0 control bytes and DEL. Tests cover detection, exclusions, reporting, and exit status.
Package and CI wiring
package.json, .github/workflows/ci.yml
Package scripts expose the lint command and its tests. CI runs the control-byte lint step.
Source control-byte cleanup
packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts, packages/2-sql/1-core/contract/src/validators.ts
The bell character uses escaped notation. Table-scoped duplicate detection uses a named NUL separator constant.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • prisma/prisma#29884: Both changes add a Node test file to package.json’s test:scripts command.
  • prisma/prisma#29889: Both changes address control-character representation in domain-type-generation.test.ts.

Suggested reviewers: aqrln

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: replacing raw control characters with escapes and adding linting for them.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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