rehype-highlight-code-lines 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,22 @@
1
1
  # rehype-highlight-code-lines
2
2
 
3
- [![NPM version][badge-npm-version]][npm-package-url]
4
- [![NPM downloads][badge-npm-download]][npm-package-url]
5
- [![Build][badge-build]][github-workflow-url]
6
- [![codecov](https://codecov.io/gh/ipikuka/rehype-highlight-code-lines/graph/badge.svg?token=RKrZlvMHwq)](https://codecov.io/gh/ipikuka/rehype-highlight-code-lines)
7
- [![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fipikuka%2Frehype-highlight-code-lines%2Fmaster%2Fpackage.json)](https://github.com/ipikuka/rehype-highlight-code-lines)
8
- [![typescript][badge-typescript]][typescript-url]
9
- [![License][badge-license]][github-license-url]
3
+ [![npm version][badge-npm-version]][url-npm-package]
4
+ [![npm downloads][badge-npm-download]][url-npm-package]
5
+ [![publish to npm][badge-publish-to-npm]][url-publish-github-actions]
6
+ [![code-coverage][badge-codecov]][url-codecov]
7
+ [![type-coverage][badge-type-coverage]][url-github-package]
8
+ [![typescript][badge-typescript]][url-typescript]
9
+ [![license][badge-license]][url-license]
10
10
 
11
- This package is a [unified][unified] ([rehype][rehype]) plugin **to add container to each line in a code block, allowing numbering of the code block and highlighting desired lines of code**.
11
+ This package is a **[unified][unified]** (**[rehype][rehype]**) plugin that **wraps each line of code in a container, enabling code block numbering and line highlighting**.
12
12
 
13
- **[unified][unified]** is a project that transforms content with abstract syntax trees (ASTs) using the new parser **[micromark][micromark]**. **[remark][remark]** adds support for markdown to unified. **[mdast][mdast]** is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree. "**[rehype][rehype]**" is a tool that transforms HTML with plugins. "**[hast][hast]**" stands for HTML Abstract Syntax Tree (HAST) that rehype uses.
13
+ **[unified][unified]** is a project that transforms content with abstract syntax trees (ASTs) using the new parser **[micromark][micromark]**. **[remark][remark]** adds support for markdown to unified. **[mdast][mdast]** is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree. **[rehype][rehype]** is a tool that transforms HTML with plugins. **[hast][hast]** stands for HTML Abstract Syntax Tree (HAST) that rehype uses.
14
14
 
15
- **This plugin allows adding line numbers to code blocks and highlighting of desired code lines.**
15
+ **This plugin enables line numbering for code blocks and highlights specific lines as needed.**
16
16
 
17
17
  ## When should I use this?
18
18
 
19
- The `rehype-highlight-code-lines` is useful if you want to add line numbers to code blocks and want to highlight desired code lines.
19
+ The `rehype-highlight-code-lines` is ideal for adding line numbers to code blocks and highlighting specific lines.
20
20
 
21
21
  **The `rehype-highlight-code-lines` is NOT code highlighter and does NOT provide code highlighting!** You can use a code highlighter for example **[rehype-highlight][rehype-highlight]** to highlight the code, then use the `rehype-highlight-code-lines` **after**.
22
22
 
@@ -42,9 +42,9 @@ yarn add rehype-highlight-code-lines
42
42
 
43
43
  ## Usage
44
44
 
45
- In a code fence, just after the language of the code block,
46
- + specify a range of number in curly braces in order to highlight desired code lines,
47
- + and/or add `showLineNumbers` in order to add numbering to code lines.
45
+ In a code fence, right after the language of the code block:
46
+ + Use curly braces `{}` to specify a range of line numbers to highlight specific lines.
47
+ + Add `showLineNumbers` to enable line numbering.
48
48
 
49
49
  **\`\`\`[language] {2,4-6} showLineNumbers**
50
50
 
@@ -54,7 +54,7 @@ In a code fence, just after the language of the code block,
54
54
 
55
55
  **\`\`\`[language] showLineNumbers**
56
56
 
57
- You can use the specifiers without a language.
57
+ You can use the specifiers without a language:
58
58
 
59
59
  **\`\`\`{5} showLineNumbers**
60
60
 
@@ -140,7 +140,6 @@ All options are **optional** and have **default values**.
140
140
  ```typescript
141
141
  type HighlightLinesOptions = {
142
142
  showLineNumbers?: boolean; // default is "false"
143
- lineContainerTagName?: "div" | "span"; // default is "span"
144
143
  };
145
144
 
146
145
  use(rehypeHighlightLines, HighlightLinesOptions);
@@ -176,28 +175,27 @@ Sometimes you may want to start the line numbering from a specific number. In th
176
175
 
177
176
  #### `lineContainerTagName`
178
177
 
179
- It is a **union** type option which is **"div" | "span"** for providing custom HTML tag name for code lines.
178
+ **It is a deprecated option.** Marked as `@deprecated`, will be removed in the next versions.
180
179
 
181
- By default, it is `span` which is **inline** level container. If you set it as `div`, the container will be **block** level, in perspective of CSS.
180
+ It was a **union** type option which was **"div" | "span"** for providing custom HTML tag name for code lines.
181
+
182
+ By default, it is `span` which is **inline** level container. If you set it as `div`, the container will still be `span` after deprecation.
182
183
 
183
184
  ```javascript
184
185
  use(rehypeHighlightLines, {
185
- lineContainerTagName: "div",
186
+ lineContainerTagName: "div", // @deprecated, always "span"
186
187
  });
187
188
  ```
188
189
 
189
- Now, the code line container's tag name will be `div`.
190
-
191
190
  ### Examples:
192
191
 
193
192
  ```typescript
194
193
  // line numbering will occur as per directive "showLineNumber" and code-line containers will be <span> inline element
195
194
  use(rehypeHighlightLines);
196
195
 
197
- // all code blocks will be numbered and code-line containers will be <div> block element
196
+ // all code blocks will be numbered by default and code-line containers will be <span> inline element
198
197
  use(rehypeHighlightLines, {
199
198
  showLineNumbers: true,
200
- lineContainerTagName: "div",
201
199
  });
202
200
  ```
203
201
 
@@ -210,32 +208,6 @@ use(rehypeHighlightLines, {
210
208
  + [demo blog application](https://next-mdx-remote-client-in-app-router.vercel.app/) using `next-mdx-remote-client` within `Next.js app router`
211
209
  + [demo blog application](https://next-mdx-remote-client-in-pages-router.vercel.app/) using `next-mdx-remote-client` within `Next.js pages router`
212
210
 
213
- ## Copying Code Block's Content Issue
214
-
215
- When the line container is "div" block element, the end of line character (eol) at the end of line causes unwanted extra blank line. In order to fix the issue, I've **removed the eol when it is "div", but kept the eol when it is "span"**.
216
-
217
- But, **the lack of the eol when "div" causes another issue.** If you implement a button for copying code block content, it doesn't work as expected since all code are printed in a single line. In order to work around the issue, you can implement an `onClick` of the `button` like below:
218
-
219
- *I assume you use a `useRef` for `<pre>` element.*
220
-
221
- ```javascript
222
- const onClick = () => {
223
- if (!preRef.current) return;
224
-
225
- // clone the <code> element in order not to cause any change in actual DOM
226
- const code = preRef.current.getElementsByTagName("code")[0].cloneNode(true);
227
-
228
- // add eol to each code-line since there is no eol at the end when they are div
229
- Array.from((code as HTMLElement).querySelectorAll("div.code-line")).forEach(
230
- (line) => {
231
- line.innerHTML = line.innerHTML + "\r";
232
- }
233
- );
234
-
235
- void navigator.clipboard.writeText(code.textContent ?? "");
236
- };
237
- ```
238
-
239
211
  ## Styling
240
212
 
241
213
  The following styles can be added for **line highlighting** and **line numbering** to work correctly:
@@ -275,29 +247,22 @@ pre > code {
275
247
  }
276
248
 
277
249
  .code-line {
250
+ min-width: 100%;
278
251
  padding-left: 12px;
279
252
  padding-right: 12px;
280
253
  margin-left: -12px;
281
254
  margin-right: -12px;
282
255
  border-left: 4px solid transparent; /* prepare for highlighted code-lines */
283
- }
284
256
 
285
- div.code-line:empty {
286
- /* it is necessary because there is no even eol character in div code-lines */
287
- height: 15.5938px; /* calculated height */
288
- }
289
-
290
- span.code-line {
291
- min-width: 100%;
292
257
  display: inline-block;
293
258
  }
294
259
 
295
260
  .code-line.inserted {
296
- background-color: var(--color-inserted-line); /* inserted code-line (+) color */
261
+ background-color: var(--color-inserted-line); /* inserted code-line (+) */
297
262
  }
298
263
 
299
264
  .code-line.deleted {
300
- background-color: var(--color-deleted-line); /* deleted code-line (-) color */
265
+ background-color: var(--color-deleted-line); /* deleted code-line (-) */
301
266
  }
302
267
 
303
268
  .highlighted-code-line {
@@ -324,7 +289,7 @@ This plugin modifies the `hast` (HTML abstract syntax tree).
324
289
 
325
290
  ## Types
326
291
 
327
- This package is fully typed with [TypeScript][typescript].
292
+ This package is fully typed with [TypeScript][url-typescript].
328
293
 
329
294
  The plugin exports the type `HighlightLinesOptions`.
330
295
 
@@ -375,17 +340,6 @@ I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to
375
340
 
376
341
  [MIT License](./LICENSE) © ipikuka
377
342
 
378
- ### Keywords
379
-
380
- 🟩 [unified][unifiednpm] 🟩 [rehype][rehypenpm] 🟩 [rehype plugin][rehypepluginnpm] 🟩 [hast][hastnpm] 🟩 [markdown][markdownnpm] 🟩 [rehype-highlight][rehypehighlightnpm]
381
-
382
- [unifiednpm]: https://www.npmjs.com/search?q=keywords:unified
383
- [rehypenpm]: https://www.npmjs.com/search?q=keywords:rehype
384
- [rehypepluginnpm]: https://www.npmjs.com/search?q=keywords:rehype%20plugin
385
- [hastnpm]: https://www.npmjs.com/search?q=keywords:hast
386
- [markdownnpm]: https://www.npmjs.com/search?q=keywords:markdown
387
- [rehypehighlightnpm]: https://www.npmjs.com/search?q=keywords:rehype-highlight
388
-
389
343
  [unified]: https://github.com/unifiedjs/unified
390
344
  [micromark]: https://github.com/micromark/micromark
391
345
  [remark]: https://github.com/remarkjs/remark
@@ -394,18 +348,23 @@ I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to
394
348
  [rehype]: https://github.com/rehypejs/rehype
395
349
  [rehypeplugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md
396
350
  [hast]: https://github.com/syntax-tree/hast
397
- [typescript]: https://www.typescriptlang.org/
398
351
  [rehype-highlight]: https://github.com/rehypejs/rehype-highlight
399
352
 
400
353
  [badge-npm-version]: https://img.shields.io/npm/v/rehype-highlight-code-lines
401
354
  [badge-npm-download]:https://img.shields.io/npm/dt/rehype-highlight-code-lines
402
- [npm-package-url]: https://www.npmjs.com/package/rehype-highlight-code-lines
355
+ [url-npm-package]: https://www.npmjs.com/package/rehype-highlight-code-lines
356
+ [url-github-package]: https://github.com/ipikuka/rehype-highlight-code-lines
403
357
 
404
358
  [badge-license]: https://img.shields.io/github/license/ipikuka/rehype-highlight-code-lines
405
- [github-license-url]: https://github.com/ipikuka/rehype-highlight-code-lines/blob/main/LICENSE
359
+ [url-license]: https://github.com/ipikuka/rehype-highlight-code-lines/blob/main/LICENSE
406
360
 
407
- [badge-build]: https://github.com/ipikuka/rehype-highlight-code-lines/actions/workflows/publish.yml/badge.svg
408
- [github-workflow-url]: https://github.com/ipikuka/rehype-highlight-code-lines/actions/workflows/publish.yml
361
+ [badge-publish-to-npm]: https://github.com/ipikuka/rehype-highlight-code-lines/actions/workflows/publish.yml/badge.svg
362
+ [url-publish-github-actions]: https://github.com/ipikuka/rehype-highlight-code-lines/actions/workflows/publish.yml
409
363
 
410
364
  [badge-typescript]: https://img.shields.io/npm/types/rehype-highlight-code-lines
411
- [typescript-url]: https://www.typescriptlang.org/
365
+ [url-typescript]: https://www.typescriptlang.org
366
+
367
+ [badge-codecov]: https://codecov.io/gh/ipikuka/rehype-highlight-code-lines/graph/badge.svg?token=RKrZlvMHwq
368
+ [url-codecov]: https://codecov.io/gh/ipikuka/rehype-highlight-code-lines
369
+
370
+ [badge-type-coverage]: https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fipikuka%2Frehype-highlight-code-lines%2Fmain%2Fpackage.json
@@ -2,12 +2,16 @@ import type { Plugin } from "unified";
2
2
  import type { Root } from "hast";
3
3
  export type HighlightLinesOptions = {
4
4
  showLineNumbers?: boolean;
5
+ /**
6
+ * @deprecated container tag name is always "span"
7
+ * will be removed in the next versions
8
+ */
5
9
  lineContainerTagName?: "div" | "span";
6
10
  };
7
11
  export declare function clsx(arr: (string | false | null | undefined | 0)[]): string[];
8
12
  /**
9
13
  *
10
- * adds line numbers to code blocks and allow highlighting of desired code lines
14
+ * add line numbers to code blocks and allow highlighting of desired code lines
11
15
  *
12
16
  */
13
17
  declare const plugin: Plugin<[HighlightLinesOptions?], Root>;
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { visit, CONTINUE } from "unist-util-visit";
1
+ import { visit } from "unist-util-visit";
2
2
  import rangeParser from "parse-numeric-range";
3
3
  const DEFAULT_SETTINGS = {
4
4
  showLineNumbers: false,
@@ -10,44 +10,62 @@ export function clsx(arr) {
10
10
  }
11
11
  /**
12
12
  *
13
- * adds line numbers to code blocks and allow highlighting of desired code lines
13
+ * add line numbers to code blocks and allow highlighting of desired code lines
14
14
  *
15
15
  */
16
16
  const plugin = (options) => {
17
17
  const settings = Object.assign({}, DEFAULT_SETTINGS, options);
18
18
  /**
19
19
  *
20
- * flatten code element children
20
+ * check code element children need flattening or not
21
+ *
22
+ */
23
+ function checkCodeTreeForFlatteningNeed(code) {
24
+ const elementContents = code.children;
25
+ // type ElementContent = Comment | Element | Text
26
+ for (const elemenContent of elementContents) {
27
+ if (elemenContent.type === "element")
28
+ if (elemenContent.children.length >= 1 && elemenContent.children[0].type === "element")
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+ /**
34
+ *
35
+ * flatten code element children, recursively
21
36
  * inspired from https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/highlight.js
22
37
  *
23
38
  */
24
- function flattenCodeTree(children, className = [], newTree = []) {
25
- for (let i = 0; i < children.length; i++) {
26
- const node = children[i];
27
- if (node.type !== "element") {
28
- newTree = newTree.concat([node]);
39
+ function flattenCodeTree(code, className = []) {
40
+ const newTree = [];
41
+ for (const elementContent of code.children) {
42
+ if (elementContent.type === "comment" || elementContent.type === "text") {
43
+ newTree.push(elementContent);
29
44
  }
30
45
  else {
31
- // @ts-expect-error
46
+ // @ts-expect-error className is different from other key of properties, and expected to be an array or undefined
32
47
  // /* v8 ignore next */
33
- const classNames = className.concat(node.properties?.className || []);
34
- if (node.children.length === 1 && node.children[0].type !== "element") {
35
- node.properties.className = classNames;
36
- newTree = newTree.concat([node]);
48
+ const classNames = className.concat(elementContent.properties.className || []);
49
+ if (elementContent.children.length === 1 &&
50
+ elementContent.children[0].type !== "element") {
51
+ elementContent.properties.className = classNames;
52
+ newTree.push(elementContent);
37
53
  }
38
54
  else {
39
- newTree = newTree.concat(flattenCodeTree(node.children, classNames));
55
+ newTree.push(...flattenCodeTree(elementContent, classNames));
40
56
  }
41
57
  }
42
58
  }
59
+ // Mutate the original code object
60
+ code.children = newTree;
43
61
  return newTree;
44
62
  }
45
63
  /**
46
64
  *
47
- * constructs the line element
65
+ * construct the line element
48
66
  *
49
67
  */
50
- const createLine = (children, lineNumber, startingNumber, showLineNumbers, linesToBeHighlighted) => {
68
+ const createLine = (children, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted) => {
51
69
  const firstChild = children[0];
52
70
  const isAddition = firstChild?.type === "element" &&
53
71
  Array.isArray(firstChild.properties.className) &&
@@ -57,23 +75,32 @@ const plugin = (options) => {
57
75
  firstChild.properties.className.some((cls) => typeof cls === "string" && cls.includes("deletion"));
58
76
  return {
59
77
  type: "element",
60
- tagName: settings.lineContainerTagName,
78
+ tagName: "span", // now it is always "span"
61
79
  children,
62
80
  properties: {
63
81
  className: clsx([
64
82
  "code-line",
65
- showLineNumbers && "numbered-code-line",
83
+ directiveShowLineNumbers && "numbered-code-line",
66
84
  linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
67
85
  isAddition && "inserted",
68
86
  isDeletion && "deleted",
69
87
  ]),
70
- dataLineNumber: showLineNumbers ? startingNumber - 1 + lineNumber : undefined,
88
+ dataLineNumber: directiveShowLineNumbers ? startingNumber - 1 + lineNumber : undefined,
71
89
  },
72
90
  };
73
91
  };
74
92
  // match all common types of line breaks
75
- const RE = /\r?\n|\r/g;
76
- function gutter(tree, showLineNumbers, startingNumber, linesToBeHighlighted) {
93
+ const REGEX_LINE_BREAKS = /\r?\n|\r/g;
94
+ /**
95
+ *
96
+ * check the code line is empty or with value only spaces
97
+ *
98
+ */
99
+ function isEmptyLine(line) {
100
+ return (line.length === 0 ||
101
+ (line.length === 1 && line[0].type === "text" && line[0].value.trim() === ""));
102
+ }
103
+ function gutter(tree, directiveShowLineNumbers, startingNumber, linesToBeHighlighted) {
77
104
  const replacement = [];
78
105
  let index = -1;
79
106
  let start = 0;
@@ -81,56 +108,72 @@ const plugin = (options) => {
81
108
  let lineNumber = 0;
82
109
  while (++index < tree.children.length) {
83
110
  const child = tree.children[index];
84
- if (child.type === "text") {
85
- let textStart = 0;
86
- let match = RE.exec(child.value);
87
- while (match) {
88
- // Nodes in this line. (current child is exclusive)
89
- const line = tree.children.slice(start, index);
90
- /* v8 ignore start */
91
- // Prepend text from a partial matched earlier text.
92
- if (startTextRemainder) {
93
- line.unshift({ type: "text", value: startTextRemainder });
94
- startTextRemainder = "";
95
- }
96
- /* v8 ignore end */
97
- // Append text from this text.
98
- if (match.index > textStart) {
99
- const value = child.value.slice(textStart, match.index);
100
- line.push({ type: "text", value });
101
- }
102
- // Add a line
103
- lineNumber += 1;
104
- replacement.push(createLine(line, lineNumber, startingNumber, showLineNumbers, linesToBeHighlighted));
105
- // Add eol if the tag name is "span"
106
- if (settings.lineContainerTagName === "span") {
107
- replacement.push({ type: "text", value: match[0] });
108
- }
109
- start = index + 1;
110
- textStart = match.index + match[0].length;
111
- match = RE.exec(child.value);
111
+ if (child.type !== "text")
112
+ continue;
113
+ let textStart = 0;
114
+ let match = REGEX_LINE_BREAKS.exec(child.value);
115
+ while (match !== null) {
116
+ // Nodes in this line. (current child is exclusive)
117
+ const line = tree.children.slice(start, index);
118
+ // Prepend text from a partial matched earlier text.
119
+ if (startTextRemainder) {
120
+ line.unshift({ type: "text", value: startTextRemainder });
121
+ startTextRemainder = "";
122
+ }
123
+ // Append text from this text.
124
+ if (match.index > textStart) {
125
+ const value = child.value.slice(textStart, match.index);
126
+ line.push({ type: "text", value });
112
127
  }
113
- // If we matched, make sure to not drop the text after the last line ending.
114
- if (start === index + 1) {
115
- startTextRemainder = child.value.slice(textStart);
128
+ if (!isEmptyLine(line)) {
129
+ lineNumber += 1;
130
+ replacement.push(createLine(line, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted));
116
131
  }
132
+ // Add eol
133
+ replacement.push({ type: "text", value: match[0] });
134
+ start = index + 1;
135
+ textStart = match.index + match[0].length;
136
+ // iterate the match
137
+ match = REGEX_LINE_BREAKS.exec(child.value);
138
+ }
139
+ // If we matched, make sure to not drop the text after the last line ending.
140
+ if (start === index + 1) {
141
+ startTextRemainder = child.value.slice(textStart);
117
142
  }
118
143
  }
119
144
  const line = tree.children.slice(start);
120
- /* v8 ignore start */
121
145
  // Prepend text from a partial matched earlier text.
122
146
  if (startTextRemainder) {
123
147
  line.unshift({ type: "text", value: startTextRemainder });
124
148
  startTextRemainder = "";
125
149
  }
126
- if (line.length > 0) {
127
- lineNumber += 1;
128
- replacement.push(createLine(line, lineNumber, startingNumber, showLineNumbers, linesToBeHighlighted));
150
+ if (!isEmptyLine(line)) {
151
+ if (line.length > 0) {
152
+ lineNumber += 1;
153
+ replacement.push(createLine(line, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted));
154
+ }
129
155
  }
130
- /* v8 ignore end */
131
156
  // Replace children with new array.
132
157
  tree.children = replacement;
133
158
  }
159
+ /**
160
+ *
161
+ * get the programming language analyzing the classNames
162
+ *
163
+ */
164
+ function getLanguage(classNames) {
165
+ const isLanguageString = (element) => {
166
+ return String(element).startsWith("language-") || String(element).startsWith("lang-");
167
+ };
168
+ const languageString = classNames?.find(isLanguageString);
169
+ if (languageString?.slice(0, 5) === "lang-") {
170
+ return languageString.slice(5).toLowerCase();
171
+ }
172
+ if (languageString?.slice(0, 9) === "language-") {
173
+ return languageString.slice(9).toLowerCase();
174
+ }
175
+ return languageString;
176
+ }
134
177
  /**
135
178
  * Transform.
136
179
  *
@@ -141,48 +184,34 @@ const plugin = (options) => {
141
184
  */
142
185
  return (tree) => {
143
186
  visit(tree, "element", function (node, index, parent) {
144
- /* v8 ignore next */
145
- if (!parent || typeof index === "undefined")
187
+ if (!parent || index === undefined || node.tagName !== "code") {
146
188
  return;
147
- if (node.tagName !== "pre")
148
- return CONTINUE;
149
- const code = node.children[0];
150
- /* v8 ignore next */
151
- if (!code || code.type !== "element" || code.tagName !== "code")
152
- return;
153
- let meta = code.data?.meta?.toLowerCase().trim();
154
- // handle if there is no language provided in code block
155
- if (Array.isArray(code.properties.className)) {
156
- const testingFunction = (element) => typeof element === "string" && element.startsWith("language-");
157
- const className = code.properties.className.find(testingFunction);
158
- if (className) {
159
- const language = className.slice(9).toLowerCase();
160
- if (language.startsWith("{") ||
161
- language.startsWith("showlinenumbers") ||
162
- language.startsWith("nolinenumbers")) {
163
- meta = meta ? language + meta : language;
164
- const idx = code.properties.className.findIndex(testingFunction);
165
- if (idx > -1) {
166
- code.properties.className[idx] = "language-unknown";
167
- }
168
- }
169
- }
170
189
  }
171
- if (settings.showLineNumbers) {
172
- if (!meta) {
173
- meta = "showlinenumbers";
174
- }
175
- else if (!meta.includes("showlinenumbers")) {
176
- meta = meta + " showlinenumbers";
177
- }
190
+ if (parent.type !== "element" || parent.tagName !== "pre") {
191
+ return;
178
192
  }
179
- if (!meta)
193
+ const code = node;
194
+ const classNames = code.properties.className;
195
+ // only for type narrowing
196
+ /* v8 ignore next */
197
+ if (!Array.isArray(classNames) && classNames !== undefined)
180
198
  return;
181
- const showLineNumbers = meta.includes("nolinenumbers")
199
+ let meta = code.data?.meta?.toLowerCase().trim() || "";
200
+ const language = getLanguage(classNames);
201
+ if (language?.startsWith("{") ||
202
+ language?.startsWith("showlinenumbers") ||
203
+ language?.startsWith("nolinenumbers")) {
204
+ // add specifiers to meta
205
+ meta = (language + " " + meta).trim();
206
+ // remove all classnames like hljs, lang-x, language-x, because of false positive
207
+ code.properties.className = undefined;
208
+ }
209
+ const directiveShowLineNumbers = meta.includes("nolinenumbers")
182
210
  ? false
183
- : meta.includes("showlinenumbers");
211
+ : settings.showLineNumbers || meta.includes("showlinenumbers");
184
212
  let startingNumber = 1;
185
- if (showLineNumbers) {
213
+ // find the number where the line number starts, if exists
214
+ if (directiveShowLineNumbers) {
186
215
  const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
187
216
  const start = REGEX1.exec(meta)?.groups?.start;
188
217
  if (start && !isNaN(Number(start)))
@@ -192,12 +221,15 @@ const plugin = (options) => {
192
221
  const REGEX2 = /{(?<lines>[\d\s,-]+)}/g;
193
222
  const strLineNumbers = REGEX2.exec(meta)?.groups?.lines?.replace(/\s/g, "");
194
223
  const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
195
- if (!showLineNumbers && linesToBeHighlighted.length === 0)
224
+ // if nothing to do for numbering and highlihting, just return
225
+ if (!directiveShowLineNumbers && linesToBeHighlighted.length === 0)
196
226
  return;
197
- // flatten deeper nodes into first level <span>s an texts
198
- code.children = flattenCodeTree(code.children);
199
- // add wrapper for each line mutating the code element
200
- gutter(code, showLineNumbers, startingNumber, linesToBeHighlighted);
227
+ // flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
228
+ if (checkCodeTreeForFlatteningNeed(code)) {
229
+ flattenCodeTree(code);
230
+ }
231
+ // add container for each line mutating the code element
232
+ gutter(code, directiveShowLineNumbers, startingNumber, linesToBeHighlighted);
201
233
  });
202
234
  };
203
235
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"root":["../../src/index.ts"],"version":"5.7.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rehype-highlight-code-lines",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Rehype plugin to add line numbers to code blocks and allow highlighting of desired code lines",
5
5
  "type": "module",
6
6
  "exports": "./dist/esm/index.js",
@@ -13,7 +13,7 @@
13
13
  "lint": "eslint .",
14
14
  "test": "vitest --watch=false",
15
15
  "test:watch": "vitest",
16
- "test:file": "vitest test.spec.ts",
16
+ "test:file": "vitest test.html.spec.ts",
17
17
  "prepack": "npm run build",
18
18
  "prepublishOnly": "npm run test && npm run format && npm run test-coverage",
19
19
  "test-coverage": "vitest run --coverage"
@@ -30,12 +30,17 @@
30
30
  },
31
31
  "keywords": [
32
32
  "unified",
33
- "hast",
34
33
  "rehype",
34
+ "hast",
35
35
  "markdown",
36
+ "mdx",
36
37
  "plugin",
37
- "rehype-plugin",
38
- "rehype-highlight",
38
+ "highlight",
39
+ "highlighting",
40
+ "rehype plugin",
41
+ "rehype highlight",
42
+ "code highlighting",
43
+ "syntax highlighting",
39
44
  "line numbering",
40
45
  "line highlighting"
41
46
  ],
@@ -46,26 +51,30 @@
46
51
  "url": "https://github.com/ipikuka/rehype-highlight-code-lines/issues"
47
52
  },
48
53
  "devDependencies": {
54
+ "@eslint/js": "^9.19.0",
49
55
  "@types/dedent": "^0.7.2",
50
- "@types/node": "^20.14.8",
51
- "@typescript-eslint/eslint-plugin": "^7.13.1",
52
- "@typescript-eslint/parser": "^7.13.1",
53
- "@vitest/coverage-v8": "^1.6.0",
56
+ "@types/node": "^22.13.1",
57
+ "@vitest/coverage-v8": "^3.0.5",
58
+ "@vitest/eslint-plugin": "^1.1.25",
54
59
  "dedent": "^1.5.3",
55
- "eslint": "^8.57.0",
56
- "eslint-config-prettier": "^9.1.0",
57
- "eslint-plugin-prettier": "^5.1.3",
58
- "prettier": "^3.3.2",
59
- "rehype-highlight": "^7.0.0",
60
- "rehype-stringify": "^10.0.0",
60
+ "eslint": "^9.19.0",
61
+ "eslint-config-prettier": "^10.0.1",
62
+ "eslint-plugin-prettier": "^5.2.3",
63
+ "prettier": "^3.4.2",
64
+ "rehype": "^13.0.2",
65
+ "rehype-highlight": "^7.0.2",
66
+ "rehype-parse": "^9.0.1",
67
+ "rehype-stringify": "^10.0.1",
61
68
  "remark-gfm": "^4.0.0",
62
69
  "remark-parse": "^11.0.0",
63
- "remark-rehype": "^11.1.0",
64
- "rimraf": "^5.0.7",
65
- "type-coverage": "^2.29.0",
66
- "typescript": "^5.5",
67
- "vfile": "^6.0.1",
68
- "vitest": "^1.4.0"
70
+ "remark-rehype": "^11.1.1",
71
+ "rimraf": "^5.0.10",
72
+ "type-coverage": "^2.29.7",
73
+ "typescript": "^5.7.3",
74
+ "typescript-eslint": "^8.23.0",
75
+ "unist-util-remove-position": "^5.0.0",
76
+ "vfile": "^6.0.3",
77
+ "vitest": "^3.0.5"
69
78
  },
70
79
  "dependencies": {
71
80
  "@types/hast": "^3.0.4",
@@ -77,7 +86,6 @@
77
86
  "atLeast": 100,
78
87
  "detail": true,
79
88
  "ignoreAsAssertion": true,
80
- "ignoreCatch": true,
81
89
  "strict": true
82
90
  },
83
91
  "sideEffects": false
package/src/index.ts CHANGED
@@ -1,16 +1,18 @@
1
1
  import type { Plugin } from "unified";
2
2
  import type { Root, Element, ElementContent, ElementData } from "hast";
3
- import { type VisitorResult, visit, CONTINUE } from "unist-util-visit";
3
+ import { visit, type VisitorResult } from "unist-util-visit";
4
4
  import rangeParser from "parse-numeric-range";
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/ban-types
7
6
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
8
7
 
9
- // eslint-disable-next-line @typescript-eslint/ban-types
10
8
  type PartiallyRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
11
9
 
12
10
  export type HighlightLinesOptions = {
13
11
  showLineNumbers?: boolean;
12
+ /**
13
+ * @deprecated container tag name is always "span"
14
+ * will be removed in the next versions
15
+ */
14
16
  lineContainerTagName?: "div" | "span";
15
17
  };
16
18
 
@@ -34,7 +36,7 @@ export function clsx(arr: (string | false | null | undefined | 0)[]): string[] {
34
36
 
35
37
  /**
36
38
  *
37
- * adds line numbers to code blocks and allow highlighting of desired code lines
39
+ * add line numbers to code blocks and allow highlighting of desired code lines
38
40
  *
39
41
  */
40
42
  const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
@@ -46,45 +48,66 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
46
48
 
47
49
  /**
48
50
  *
49
- * flatten code element children
51
+ * check code element children need flattening or not
52
+ *
53
+ */
54
+ function checkCodeTreeForFlatteningNeed(code: Element): boolean {
55
+ const elementContents = code.children;
56
+
57
+ // type ElementContent = Comment | Element | Text
58
+ for (const elemenContent of elementContents) {
59
+ if (elemenContent.type === "element")
60
+ if (elemenContent.children.length >= 1 && elemenContent.children[0].type === "element")
61
+ return true;
62
+ }
63
+
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ *
69
+ * flatten code element children, recursively
50
70
  * inspired from https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/highlight.js
51
71
  *
52
72
  */
53
- function flattenCodeTree(
54
- children: ElementContent[],
55
- className: string[] = [],
56
- newTree: ElementContent[] = [],
57
- ): ElementContent[] {
58
- for (let i = 0; i < children.length; i++) {
59
- const node = children[i];
60
- if (node.type !== "element") {
61
- newTree = newTree.concat([node]);
73
+ function flattenCodeTree(code: Element, className: string[] = []): ElementContent[] {
74
+ const newTree: ElementContent[] = [];
75
+
76
+ for (const elementContent of code.children) {
77
+ if (elementContent.type === "comment" || elementContent.type === "text") {
78
+ newTree.push(elementContent);
62
79
  } else {
63
- // @ts-expect-error
80
+ // @ts-expect-error className is different from other key of properties, and expected to be an array or undefined
64
81
  // /* v8 ignore next */
65
- const classNames = className.concat(node.properties?.className || []);
66
-
67
- if (node.children.length === 1 && node.children[0].type !== "element") {
68
- node.properties.className = classNames;
69
- newTree = newTree.concat([node]);
82
+ const classNames = className.concat(elementContent.properties.className || []);
83
+
84
+ if (
85
+ elementContent.children.length === 1 &&
86
+ elementContent.children[0].type !== "element"
87
+ ) {
88
+ elementContent.properties.className = classNames;
89
+ newTree.push(elementContent);
70
90
  } else {
71
- newTree = newTree.concat(flattenCodeTree(node.children, classNames));
91
+ newTree.push(...flattenCodeTree(elementContent, classNames));
72
92
  }
73
93
  }
74
94
  }
95
+
96
+ // Mutate the original code object
97
+ code.children = newTree;
75
98
  return newTree;
76
99
  }
77
100
 
78
101
  /**
79
102
  *
80
- * constructs the line element
103
+ * construct the line element
81
104
  *
82
105
  */
83
106
  const createLine = (
84
107
  children: ElementContent[],
85
108
  lineNumber: number,
86
109
  startingNumber: number,
87
- showLineNumbers: boolean,
110
+ directiveShowLineNumbers: boolean,
88
111
  linesToBeHighlighted: number[],
89
112
  ): Element => {
90
113
  const firstChild = children[0];
@@ -105,31 +128,43 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
105
128
 
106
129
  return {
107
130
  type: "element",
108
- tagName: settings.lineContainerTagName,
131
+ tagName: "span", // now it is always "span"
109
132
  children,
110
133
  properties: {
111
134
  className: clsx([
112
135
  "code-line",
113
- showLineNumbers && "numbered-code-line",
136
+ directiveShowLineNumbers && "numbered-code-line",
114
137
  linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
115
138
  isAddition && "inserted",
116
139
  isDeletion && "deleted",
117
140
  ]),
118
- dataLineNumber: showLineNumbers ? startingNumber - 1 + lineNumber : undefined,
141
+ dataLineNumber: directiveShowLineNumbers ? startingNumber - 1 + lineNumber : undefined,
119
142
  },
120
143
  };
121
144
  };
122
145
 
123
146
  // match all common types of line breaks
124
- const RE = /\r?\n|\r/g;
147
+ const REGEX_LINE_BREAKS = /\r?\n|\r/g;
148
+
149
+ /**
150
+ *
151
+ * check the code line is empty or with value only spaces
152
+ *
153
+ */
154
+ function isEmptyLine(line: ElementContent[]): boolean {
155
+ return (
156
+ line.length === 0 ||
157
+ (line.length === 1 && line[0].type === "text" && line[0].value.trim() === "")
158
+ );
159
+ }
125
160
 
126
161
  function gutter(
127
162
  tree: Element,
128
- showLineNumbers: boolean,
163
+ directiveShowLineNumbers: boolean,
129
164
  startingNumber: number,
130
165
  linesToBeHighlighted: number[],
131
166
  ) {
132
- const replacement: Array<ElementContent> = [];
167
+ const replacement: ElementContent[] = [];
133
168
 
134
169
  let index = -1;
135
170
  let start = 0;
@@ -139,76 +174,106 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
139
174
  while (++index < tree.children.length) {
140
175
  const child = tree.children[index];
141
176
 
142
- if (child.type === "text") {
143
- let textStart = 0;
144
- let match = RE.exec(child.value);
145
-
146
- while (match) {
147
- // Nodes in this line. (current child is exclusive)
148
- const line = tree.children.slice(start, index);
177
+ if (child.type !== "text") continue;
149
178
 
150
- /* v8 ignore start */
179
+ let textStart = 0;
180
+ let match = REGEX_LINE_BREAKS.exec(child.value);
151
181
 
152
- // Prepend text from a partial matched earlier text.
153
- if (startTextRemainder) {
154
- line.unshift({ type: "text", value: startTextRemainder });
155
- startTextRemainder = "";
156
- }
182
+ while (match !== null) {
183
+ // Nodes in this line. (current child is exclusive)
184
+ const line = tree.children.slice(start, index);
157
185
 
158
- /* v8 ignore end */
186
+ // Prepend text from a partial matched earlier text.
187
+ if (startTextRemainder) {
188
+ line.unshift({ type: "text", value: startTextRemainder });
189
+ startTextRemainder = "";
190
+ }
159
191
 
160
- // Append text from this text.
161
- if (match.index > textStart) {
162
- const value = child.value.slice(textStart, match.index);
163
- line.push({ type: "text", value });
164
- }
192
+ // Append text from this text.
193
+ if (match.index > textStart) {
194
+ const value = child.value.slice(textStart, match.index);
195
+ line.push({ type: "text", value });
196
+ }
165
197
 
166
- // Add a line
198
+ if (!isEmptyLine(line)) {
167
199
  lineNumber += 1;
168
200
  replacement.push(
169
- createLine(line, lineNumber, startingNumber, showLineNumbers, linesToBeHighlighted),
201
+ createLine(
202
+ line,
203
+ lineNumber,
204
+ startingNumber,
205
+ directiveShowLineNumbers,
206
+ linesToBeHighlighted,
207
+ ),
170
208
  );
209
+ }
171
210
 
172
- // Add eol if the tag name is "span"
173
- if (settings.lineContainerTagName === "span") {
174
- replacement.push({ type: "text", value: match[0] });
175
- }
211
+ // Add eol
212
+ replacement.push({ type: "text", value: match[0] });
176
213
 
177
- start = index + 1;
178
- textStart = match.index + match[0].length;
179
- match = RE.exec(child.value);
180
- }
214
+ start = index + 1;
215
+ textStart = match.index + match[0].length;
181
216
 
182
- // If we matched, make sure to not drop the text after the last line ending.
183
- if (start === index + 1) {
184
- startTextRemainder = child.value.slice(textStart);
185
- }
217
+ // iterate the match
218
+ match = REGEX_LINE_BREAKS.exec(child.value);
219
+ }
220
+
221
+ // If we matched, make sure to not drop the text after the last line ending.
222
+ if (start === index + 1) {
223
+ startTextRemainder = child.value.slice(textStart);
186
224
  }
187
225
  }
188
226
 
189
227
  const line = tree.children.slice(start);
190
228
 
191
- /* v8 ignore start */
192
-
193
229
  // Prepend text from a partial matched earlier text.
194
230
  if (startTextRemainder) {
195
231
  line.unshift({ type: "text", value: startTextRemainder });
196
232
  startTextRemainder = "";
197
233
  }
198
234
 
199
- if (line.length > 0) {
200
- lineNumber += 1;
201
- replacement.push(
202
- createLine(line, lineNumber, startingNumber, showLineNumbers, linesToBeHighlighted),
203
- );
235
+ if (!isEmptyLine(line)) {
236
+ if (line.length > 0) {
237
+ lineNumber += 1;
238
+ replacement.push(
239
+ createLine(
240
+ line,
241
+ lineNumber,
242
+ startingNumber,
243
+ directiveShowLineNumbers,
244
+ linesToBeHighlighted,
245
+ ),
246
+ );
247
+ }
204
248
  }
205
249
 
206
- /* v8 ignore end */
207
-
208
250
  // Replace children with new array.
209
251
  tree.children = replacement;
210
252
  }
211
253
 
254
+ /**
255
+ *
256
+ * get the programming language analyzing the classNames
257
+ *
258
+ */
259
+ function getLanguage(classNames: (string | number)[] | undefined): string | undefined {
260
+ const isLanguageString = (element: string | number): element is string => {
261
+ return String(element).startsWith("language-") || String(element).startsWith("lang-");
262
+ };
263
+
264
+ const languageString = classNames?.find(isLanguageString);
265
+
266
+ if (languageString?.slice(0, 5) === "lang-") {
267
+ return languageString.slice(5).toLowerCase();
268
+ }
269
+
270
+ if (languageString?.slice(0, 9) === "language-") {
271
+ return languageString.slice(9).toLowerCase();
272
+ }
273
+
274
+ return languageString;
275
+ }
276
+
212
277
  /**
213
278
  * Transform.
214
279
  *
@@ -219,61 +284,46 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
219
284
  */
220
285
  return (tree: Root): undefined => {
221
286
  visit(tree, "element", function (node, index, parent): VisitorResult {
222
- /* v8 ignore next */
223
- if (!parent || typeof index === "undefined") return;
224
-
225
- if (node.tagName !== "pre") return CONTINUE;
226
-
227
- const code = node.children[0];
228
-
229
- /* v8 ignore next */
230
- if (!code || code.type !== "element" || code.tagName !== "code") return;
287
+ if (!parent || index === undefined || node.tagName !== "code") {
288
+ return;
289
+ }
231
290
 
232
- let meta = (code.data as CodeData)?.meta?.toLowerCase().trim();
291
+ if (parent.type !== "element" || parent.tagName !== "pre") {
292
+ return;
293
+ }
233
294
 
234
- // handle if there is no language provided in code block
235
- if (Array.isArray(code.properties.className)) {
236
- const testingFunction = (element: string | number): element is string =>
237
- typeof element === "string" && element.startsWith("language-");
295
+ const code = node;
238
296
 
239
- const className = code.properties.className.find(testingFunction);
297
+ const classNames = code.properties.className;
240
298
 
241
- if (className) {
242
- const language = className.slice(9).toLowerCase();
299
+ // only for type narrowing
300
+ /* v8 ignore next */
301
+ if (!Array.isArray(classNames) && classNames !== undefined) return;
243
302
 
244
- if (
245
- language.startsWith("{") ||
246
- language.startsWith("showlinenumbers") ||
247
- language.startsWith("nolinenumbers")
248
- ) {
249
- meta = meta ? language + meta : language;
303
+ let meta = (code.data as CodeData)?.meta?.toLowerCase().trim() || "";
250
304
 
251
- const idx = code.properties.className.findIndex(testingFunction);
305
+ const language = getLanguage(classNames);
252
306
 
253
- if (idx > -1) {
254
- code.properties.className[idx] = "language-unknown";
255
- }
256
- }
257
- }
258
- }
307
+ if (
308
+ language?.startsWith("{") ||
309
+ language?.startsWith("showlinenumbers") ||
310
+ language?.startsWith("nolinenumbers")
311
+ ) {
312
+ // add specifiers to meta
313
+ meta = (language + " " + meta).trim();
259
314
 
260
- if (settings.showLineNumbers) {
261
- if (!meta) {
262
- meta = "showlinenumbers";
263
- } else if (!meta.includes("showlinenumbers")) {
264
- meta = meta + " showlinenumbers";
265
- }
315
+ // remove all classnames like hljs, lang-x, language-x, because of false positive
316
+ code.properties.className = undefined;
266
317
  }
267
318
 
268
- if (!meta) return;
269
-
270
- const showLineNumbers = meta.includes("nolinenumbers")
319
+ const directiveShowLineNumbers = meta.includes("nolinenumbers")
271
320
  ? false
272
- : meta.includes("showlinenumbers");
321
+ : settings.showLineNumbers || meta.includes("showlinenumbers");
273
322
 
274
323
  let startingNumber = 1;
275
324
 
276
- if (showLineNumbers) {
325
+ // find the number where the line number starts, if exists
326
+ if (directiveShowLineNumbers) {
277
327
  const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
278
328
  const start = REGEX1.exec(meta)?.groups?.start;
279
329
  if (start && !isNaN(Number(start))) startingNumber = Number(start);
@@ -284,13 +334,16 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
284
334
  const strLineNumbers = REGEX2.exec(meta)?.groups?.lines?.replace(/\s/g, "");
285
335
  const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
286
336
 
287
- if (!showLineNumbers && linesToBeHighlighted.length === 0) return;
337
+ // if nothing to do for numbering and highlihting, just return
338
+ if (!directiveShowLineNumbers && linesToBeHighlighted.length === 0) return;
288
339
 
289
- // flatten deeper nodes into first level <span>s an texts
290
- code.children = flattenCodeTree(code.children);
340
+ // flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
341
+ if (checkCodeTreeForFlatteningNeed(code)) {
342
+ flattenCodeTree(code);
343
+ }
291
344
 
292
- // add wrapper for each line mutating the code element
293
- gutter(code, showLineNumbers, startingNumber, linesToBeHighlighted);
345
+ // add container for each line mutating the code element
346
+ gutter(code, directiveShowLineNumbers, startingNumber, linesToBeHighlighted);
294
347
  });
295
348
  };
296
349
  };