PrismJS / PrismJS/prism

diff:<lang> glues hunks and files together, so one unterminated construct contaminates the rest

Open
#4,117 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
13k
Forks
1.4k
Avg merge
15h 36m
Merged PRs (30d)
3

Description

Introduced by #4110. diff selects 'unchanged, deleted, diff' and 'unchanged, inserted, diff'. Everything not named by a selector is dropped from the text handed to the inner language — coord lines (@@ …, --- a/file, ***, and normal-diff 1c1), and also the unmatched text, which is where diff --git a/x b/x lands since it matches none of the coord patterns.

Dropping them makes non-adjacent regions contiguous, so an unterminated construct — block comment, string, template literal, fenced block — runs on into the next hunk and the next file.

Repro

import { createInstance } from './tests/helper/prism-loader.js';

const prism = await createInstance(['diff', 'javascript', 'clike', 'markup']);
const code = [
	'diff --git a/a.js b/a.js', '@@ -1,2 +1,2 @@', ' /* note', '-const s = 1;',
	'diff --git a/b.js b/b.js', '@@ -1,1 +1,1 @@', '-const t = 2;', '',
].join('\n');

console.log(prism.highlight(code, 'diff:javascript'));
v2   [deleted [prefix -][keyword const] t [operator =] [number 2][punctuation ;]
new  [deleted [prefix -][comment const t = 2;

b.js is a different file and renders entirely as a comment. Highlighting a real git log -p or a multi-hunk patch hits this routinely: git diff 28d76273 2b5ee8bd as diff:javascript differs from v2 on 29 of its 1304 lines for this reason.

Why v2 was fine

$inner used to sit inside each line-block token, so every block was tokenized alone. #4110 moved it to the top level so constructs can span blocks — which is the feature, and it is what makes a comment opened on an unchanged line and closed on a changed one work. The concatenation now also spans things that are not contiguous.

Why it needs a new concept

The obvious rule — "a container the selector does not name breaks the document" — is wrong. Building the before document must skip inserted blocks without breaking, because the old file really is contiguous across them, but must break at coord. Both are merely unnamed, so the selector language cannot tell them apart.

One option, prototyped and confirmed working (~40 lines, not submitted because it is new public API rather than a bug fix): a separator selector on InnerSpec naming containers that end a document, with diff declaring separator: 'coord'. For the cross-file case the unmatched text would have to break as well, or the bleed survives wherever two regions abut without a @@ between them.

Is that concept one you want in the model, or would you rather solve it another way?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproduction using tests/helper/prism-loader.js, then compare the diff selector behavior and InnerSpec changes introduced by #4110. The fix should prevent unterminated constructs from crossing diff hunks and files while preserving constructs that legitimately span blocks; the issue leaves the API design unresolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
devtools
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.