{"owner":"highlightjs","repo":"highlight.js","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"files":{"AGENTS.md":"# Agent notes (highlight.js)\n\n## AI policy (dogfood)\n\nProject policy: `docs/ai-contributions.md`. For **this** repo, when work was\nsubstantially tool-assisted, include an `Assisted-by` trailer on the commit\nand/or PR body (eat our own dogfood):\n\n```text\nAssisted-by: <model> (<effort>)\n```\n\n| Piece | Meaning | Examples |\n| --- | --- | --- |\n| model | Product / model id | `Grok 4.5`, `Claude Sonnet 4` |\n| effort | Reasoning / effort setting if any | `low`, `medium`, `high`, `max` |\n\nExamples:\n\n```text\nAssisted-by: Grok 4.5 (low)\nAssisted-by: Claude Opus 4 (high)\nAssisted-by: Copilot\n```\n\nOmit unknown pieces rather than guessing (`Assisted-by: Copilot` is fine).\nDo not claim assistance on commits that were fully human.\n\n## Grammar API preferences\n\n### Prefer `scope` over `className`\n\n`className` is deprecated. Use `scope` (string) for the mode’s overall CSS scope in new or edited grammar code.\n\n```js\n// good\n{ match: /\\bfoo\\b/, scope: \"keyword\" }\n\n// avoid in new code\n{ match: /\\bfoo\\b/, className: \"keyword\" }\n```\n\n### Drop `relevance` when touching a mode\n\n`relevance` on modes is **deprecated**. Do not add new `relevance` fields.\n\nWhen a PR **touches** a mode that still sets `relevance`, remove `relevance` from that mode (or ask the author to remove it) as part of the change—same “touch it, modernize it” rule as `className` → `scope`.\n\n### Prefer lookarounds over `on:begin` / `on:end` callbacks\n\nWhen a rule is really “match this, except these cases,” prefer **pure regex** (especially negative lookahead/lookbehind) over `\"on:begin\"` / `\"on:end\"` callbacks that call `ignoreMatch()`.\n\nCallbacks run in JS on every candidate match and are harder to optimize; lookarounds stay in the regex engine.\n\n```js\n// preferred — exclude statement keywords that look like calls\n{\n  match: /\\b(?!(?:if|for|while|switch)\\b)[a-z_][A-Za-z0-9_]*(?=\\()/,\n  scope: \"title.function\"\n}\n\n// avoid when a lookahead suffices\n{\n  match: /\\b[a-z_][A-Za-z0-9_]*(?=\\()/,\n  scope: \"title.function\",\n  \"on:begin\": (m, resp) => {\n    if (/^(?:if|for|while|switch)$/.test(m[0])) resp.ignoreMatch();\n  }\n}\n```\n\nCallbacks are still fine when the decision needs sets, multi-match state, paired begin/end checks, or other logic regex cannot express cleanly.\n\n### `scope` vs `beginScope` / `endScope`\n\nThese are **not** interchangeable (see `docs/mode-reference.rst`):\n\n| Field | What it scopes |\n| --- | --- |\n| `scope` (string) | The **whole mode** (content between begin and end, as one region). |\n| `beginScope` | **Only the begin match** (string = wrap entire begin; object = per multi-match piece). |\n| `endScope` | **Only the end match** (same shapes as `beginScope`). |\n\nUse `beginScope` when the mode continues after the begin (e.g. `end: /$/`, `contains: […]`) and only the opening lexeme(s) should be keyword/punctuation—not the rest of the line.\n\n```js\n// begin pieces only; body stays unscoped (except contains)\n{\n  begin: [/^[ \\t]*/, /\\bFeature\\b/, /:/],\n  beginScope: { 2: \"keyword\", 3: \"punctuation\" },\n  end: /$/,\n  contains: [VARIABLE]\n}\n\n// wrong here: would treat the entire mode/line as keyword\n{\n  begin: [/^[ \\t]*/, /\\bFeature\\b/, /:/],\n  scope: { 2: \"keyword\", 3: \"punctuation\" },\n  end: /$/\n}\n```\n\n**Sugar:** for `match: [ … ]` only (no `end`), an object `scope: { 1: \"…\", 3: \"…\" }` is compiled into `beginScope` (`src/lib/ext/multi_class.js` `scopeSugar`). That form is fine for match-only multi-class rules. Prefer explicit `beginScope` when the mode has a real `begin`/`end` pair so intent stays obvious.\n\nString `beginScope: \"keyword\"` wraps the entire begin lexeme (not multi-index).\n\n### Prefer multi-match for keyword + following token\n\nWhen a rule is really “keyword/punctuation, then whitespace, then an identifier (or similar)” with different scopes per piece, prefer multi-match over `begin` + `excludeBegin` / `end: /\\W/` dances.\n\n```js\n// preferred — match-only: object `scope` → beginScope sugar\n{\n  match: [/\\bnew\\b/, /\\s+/, hljs.IDENT_RE],\n  scope: {\n    1: \"keyword\",\n    3: \"type\"\n  }\n}\n\n// preferred — mode with body after begin: use beginScope\n{\n  begin: [/\\bnew\\b/, /\\s+/, hljs.IDENT_RE],\n  beginScope: {\n    1: \"keyword\",\n    3: \"type\"\n  },\n  end: /$/,\n  contains: [/* ... */]\n}\n\n// older style — avoid for new work when multi-match fits\n{\n  className: \"type\",\n  beginKeywords: \"new\", // or begin: /new\\s+/, excludeBegin: true\n  end: /\\W/,\n  excludeBegin: true,\n  excludeEnd: true\n}\n```\n\nMulti-match:\n\n- Uses `begin: [ ... ]` or `match: [ ... ]` (array of consecutive patterns).\n- Per-piece scopes are **1-based** indexes into that array (`beginScope` / `endScope`, or object `scope` sugar on `match`).\n- Avoids empty spans and makes keyword vs type/title boundaries explicit.\n\nSame idea applies to “after `:` type” rules, `class`/`enum` titles, etc. See existing usage in `java.js`, `scala.js`, `rust.js`, and `test/api/multiClassMatch.js`.\n\n### Building patterns\n\n- Prefer regex literals or template strings; hljs accepts string patterns without `new RegExp(...)`.\n- Prefer `regex.concat(...)` when joining lookarounds and `IDENT_RE`-style fragments.\n- Mirror style in nearby grammars (e.g. `src/languages/lib/java.js` number variants use template strings, not `new RegExp`).\n\n## Running tests\n\nMarkup and most mocha suites load the compiler output from **`build/`**, not\n`src/` directly. After editing a grammar under `src/languages/`, rebuild before\ntesting or you get `Cannot find module '../build'` / stale results.\n\n```bash\n# rebuild one language (fast) then run its markup tests\nnode tools/build.js -t node rust\nONLY_LANGUAGES=rust npm run test-markup\n\n# full node build (all languages)\nnode tools/build.js -t node\n\n# full test suite (also needs a prior build)\nnpm test\n\n# other targeted scripts (see package.json)\nnpm run test-markup\nnpm run test-detect\n```\n\nNotes:\n\n- `ONLY_LANGUAGES` is a space-separated list of **markup folder names**\n  (e.g. `rust`, `bash`), matching `test/markup/<lang>/`.\n- Mocha `--grep` often matches poorly here because language suites are nested\n  under dynamic `describe`s; prefer `ONLY_LANGUAGES` for markup.\n- Markup cases live as pairs: `test/markup/<lang>/<name>.txt` and\n  `<name>.expect.txt`. Expect files compare `hljs.highlight(...).value` (trimmed).\n- Do not commit `build/` artifacts from local rebuilds unless the project\n  explicitly expects it (normally CI builds).\n\n## Style / hygiene\n\n### Trailing whitespace\n\n**Ignore trailing whitespace** (and minor EOF newline nits from `git diff --check`)\nunless you are already editing that exact line for a real change. Do not open\ncleanup-only commits or block reviews on trailing spaces. Prefer leaving\nhistorical noise alone over drive-by whitespace diffs.\n"}}