rehype-highlight-code-lines 1.0.5 → 1.0.8

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
@@ -140,6 +140,8 @@ All options are **optional** and have **default values**.
140
140
  ```typescript
141
141
  type HighlightLinesOptions = {
142
142
  showLineNumbers?: boolean; // default is "false"
143
+ lineContainerTagName?: "span" | "div"; // deprecated, always span
144
+ trimBlankLines?: boolean; // default is "false"
143
145
  };
144
146
 
145
147
  use(rehypeHighlightLines, HighlightLinesOptions);
@@ -173,20 +175,53 @@ Sometimes you may want to start the line numbering from a specific number. In th
173
175
 
174
176
  **\`\`\`showLineNumbers=8**
175
177
 
176
- #### `lineContainerTagName`
178
+ #### `lineContainerTagName` (Deprecated)
177
179
 
178
- **It is a deprecated option.** Marked as `@deprecated`, will be removed in the next versions.
180
+ **It is marked as `@deprecated` and will be removed in the next versions.
179
181
 
180
- It was a **union** type option which was **"div" | "span"** for providing custom HTML tag name for code lines.
182
+ It was a **union** option which was **"div" | "span"** for providing custom HTML tag name for code lines.
181
183
 
182
184
  By default, it is `span` which is **inline** level container. If you set it as `div`, the container will still be `span` after deprecation.
183
185
 
184
186
  ```javascript
185
187
  use(rehypeHighlightLines, {
186
- lineContainerTagName: "div", // @deprecated, always "span"
188
+ lineContainerTagName: "div", // @deprecated, effectless, always "span"
187
189
  });
188
190
  ```
189
191
 
192
+ #### `trimBlankLines`
193
+
194
+ It is a **boolean** option. It is designed to delete one blank/empty line at the beginning and one at the end of the code block, if happened due to html parsing `<pre><code /></pre>`.
195
+
196
+ By default, it is `false`.
197
+
198
+ Let's assume you want to highlight `pre`, and `code` element in markdown (not code fence).
199
+
200
+ ```markdown
201
+ Here is markdown content
202
+
203
+ <pre><code class="language-javascript">
204
+ console.log("rehype-highlight-code-lines");
205
+ </code></pre>
206
+ ```
207
+
208
+ For above markdown, the parsed result (for example via `rehype-parse` and `rehype-stringfy`) is going to contain empty/blank code lines due to nature of `pre` preserving whitespaces. In order to prevent having unintentionally blank lines, use the option `trimBlankLines`.
209
+
210
+ ```javascript
211
+ use(rehypeHighlightLines, {
212
+ trimBlankLines: true,
213
+ });
214
+ ```
215
+
216
+ Actually, the trimming could have been the default behaviour. However, some developers may intentionally include empty lines at the beginning and at the end in code fences for specific reasons and may want to preserve them.
217
+ ````markdown
218
+ ```javascript
219
+
220
+ console.log("rehype-highlight-code-lines");
221
+
222
+ ```
223
+ ````
224
+
190
225
  ### Examples:
191
226
 
192
227
  ```typescript
@@ -1,5 +1,10 @@
1
1
  import type { Plugin } from "unified";
2
2
  import type { Root } from "hast";
3
+ declare module "hast" {
4
+ interface Data {
5
+ meta?: string | undefined;
6
+ }
7
+ }
3
8
  export type HighlightLinesOptions = {
4
9
  showLineNumbers?: boolean;
5
10
  /**
@@ -7,6 +12,7 @@ export type HighlightLinesOptions = {
7
12
  * will be removed in the next versions
8
13
  */
9
14
  lineContainerTagName?: "div" | "span";
15
+ trimBlankLines?: boolean;
10
16
  };
11
17
  export declare function clsx(arr: (string | false | null | undefined | 0)[]): string[];
12
18
  /**
package/dist/esm/index.js CHANGED
@@ -3,11 +3,30 @@ import rangeParser from "parse-numeric-range";
3
3
  const DEFAULT_SETTINGS = {
4
4
  showLineNumbers: false,
5
5
  lineContainerTagName: "span",
6
+ trimBlankLines: false,
6
7
  };
7
8
  // a simple util for our use case, like clsx package
8
9
  export function clsx(arr) {
9
10
  return arr.filter((item) => !!item);
10
11
  }
12
+ // check if it is string array
13
+ function isStringArray(value) {
14
+ return (
15
+ // type-coverage:ignore-next-line
16
+ Array.isArray(value) && value.every((item) => typeof item === "string"));
17
+ }
18
+ // check if it is Element which first child is text
19
+ function isElementWithTextNode(node) {
20
+ return (node?.type === "element" && node.children[0]?.type === "text" && "value" in node.children[0]);
21
+ }
22
+ function hasClassName(node, className) {
23
+ return (node?.type === "element" &&
24
+ isStringArray(node.properties.className) &&
25
+ node.properties.className.some((cls) => cls.includes(className)));
26
+ }
27
+ // match all common types of line breaks
28
+ const REGEX_LINE_BREAKS = /\r?\n|\r/g;
29
+ const REGEX_LINE_BREAKS_IN_THE_BEGINNING = /^(\r?\n|\r)+/;
11
30
  /**
12
31
  *
13
32
  * add line numbers to code blocks and allow highlighting of desired code lines
@@ -20,19 +39,20 @@ const plugin = (options) => {
20
39
  * check code element children need flattening or not
21
40
  *
22
41
  */
23
- function checkCodeTreeForFlatteningNeed(code) {
42
+ function hasFlatteningNeed(code) {
24
43
  const elementContents = code.children;
25
44
  // type ElementContent = Comment | Element | Text
26
45
  for (const elemenContent of elementContents) {
27
- if (elemenContent.type === "element")
28
- if (elemenContent.children.length >= 1 && elemenContent.children[0].type === "element")
46
+ if (elemenContent.type === "element" && Boolean(elemenContent.children.length))
47
+ if (elemenContent.children.some((ec) => ec.type === "element"))
29
48
  return true;
30
49
  }
31
50
  return false;
32
51
  }
33
52
  /**
34
53
  *
35
- * flatten code element children, recursively
54
+ * flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
55
+ * mutates the code, recursively
36
56
  * inspired from https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/highlight.js
37
57
  *
38
58
  */
@@ -65,14 +85,10 @@ const plugin = (options) => {
65
85
  * construct the line element
66
86
  *
67
87
  */
68
- const createLine = (children, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted) => {
88
+ const createLine = (children, lineNumber, directiveShowLineNumbers, directiveHighlightLines) => {
69
89
  const firstChild = children[0];
70
- const isAddition = firstChild?.type === "element" &&
71
- Array.isArray(firstChild.properties.className) &&
72
- firstChild.properties.className.some((cls) => typeof cls === "string" && cls.includes("addition"));
73
- const isDeletion = firstChild?.type === "element" &&
74
- Array.isArray(firstChild.properties.className) &&
75
- firstChild.properties.className.some((cls) => typeof cls === "string" && cls.includes("deletion"));
90
+ const isAddition = hasClassName(firstChild, "addition");
91
+ const isDeletion = hasClassName(firstChild, "deletion");
76
92
  return {
77
93
  type: "element",
78
94
  tagName: "span", // now it is always "span"
@@ -81,40 +97,110 @@ const plugin = (options) => {
81
97
  className: clsx([
82
98
  "code-line",
83
99
  directiveShowLineNumbers && "numbered-code-line",
84
- linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
100
+ directiveHighlightLines.includes(lineNumber) && "highlighted-code-line",
85
101
  isAddition && "inserted",
86
102
  isDeletion && "deleted",
87
103
  ]),
88
- dataLineNumber: directiveShowLineNumbers ? startingNumber - 1 + lineNumber : undefined,
104
+ dataLineNumber: typeof directiveShowLineNumbers === "number"
105
+ ? directiveShowLineNumbers - 1 + lineNumber
106
+ : directiveShowLineNumbers
107
+ ? lineNumber
108
+ : undefined,
89
109
  },
90
110
  };
91
111
  };
92
- // match all common types of line breaks
93
- const REGEX_LINE_BREAKS = /\r?\n|\r/g;
94
112
  /**
95
113
  *
96
- * check the code line is empty or with value only spaces
114
+ * handle elements which is multi line comment in code
115
+ * mutates the code
116
+ *
117
+ */
118
+ function handleMultiLineComments(code) {
119
+ for (let index = 0; index < code.children.length; index++) {
120
+ const replacement = [];
121
+ const child = code.children[index];
122
+ if (!isElementWithTextNode(child))
123
+ continue;
124
+ if (!hasClassName(child, "comment"))
125
+ continue;
126
+ const textNode = child.children[0];
127
+ if (!REGEX_LINE_BREAKS.test(textNode.value))
128
+ continue;
129
+ const comments = textNode.value.split(REGEX_LINE_BREAKS);
130
+ for (let i = 0; i < comments.length; i++) {
131
+ const newChild = structuredClone(child);
132
+ newChild.children[0].value = comments[i];
133
+ replacement.push(newChild);
134
+ if (i < comments.length - 1) {
135
+ replacement.push({ type: "text", value: "\n" }); // eol
136
+ }
137
+ }
138
+ code.children = [
139
+ ...code.children.slice(0, index),
140
+ ...replacement,
141
+ ...code.children.slice(index + 1),
142
+ ];
143
+ }
144
+ }
145
+ /**
146
+ *
147
+ * handle eol characters in the beginning of the only first element
148
+ * (because gutter function does not check the first element in the HAST whether contain eol at the beginning)
149
+ * mutates the code
97
150
  *
98
151
  */
99
- function isEmptyLine(line) {
100
- return (line.length === 0 ||
101
- (line.length === 1 && line[0].type === "text" && line[0].value.trim() === ""));
152
+ function handleFirstElementContent(code) {
153
+ const elementNode = code.children[0];
154
+ if (!isElementWithTextNode(elementNode))
155
+ return;
156
+ const textNode = elementNode.children[0];
157
+ if (!REGEX_LINE_BREAKS_IN_THE_BEGINNING.test(textNode.value))
158
+ return;
159
+ const match = REGEX_LINE_BREAKS_IN_THE_BEGINNING.exec(textNode.value);
160
+ if (match) {
161
+ code.children.unshift({ type: "text", value: match[0] });
162
+ textNode.value = textNode.value.replace(REGEX_LINE_BREAKS_IN_THE_BEGINNING, "");
163
+ }
102
164
  }
103
- function gutter(tree, directiveShowLineNumbers, startingNumber, linesToBeHighlighted) {
165
+ /**
166
+ *
167
+ * handle trimming blank line for the last text node
168
+ *
169
+ */
170
+ function handleTrimmingBlankLines(code, directiveTrimBlankLines) {
171
+ if (!directiveTrimBlankLines)
172
+ return;
173
+ const lastChild = code.children[code.children.length - 1];
174
+ if (lastChild.type === "text") {
175
+ if (/(\r?\n|\r)(\1)$/.test(lastChild.value)) {
176
+ lastChild.value = lastChild.value.replace(/(\r?\n|\r)(\1)$/, "$1");
177
+ }
178
+ }
179
+ }
180
+ /**
181
+ *
182
+ * extract the lines from HAST of code element
183
+ * mutates the code
184
+ *
185
+ */
186
+ function gutter(code, { directiveShowLineNumbers, directiveHighlightLines, directiveTrimBlankLines, }) {
187
+ hasFlatteningNeed(code) && flattenCodeTree(code); // mutates the code
188
+ handleMultiLineComments(code); // mutates the code
189
+ handleFirstElementContent(code); // mutates the code
190
+ handleTrimmingBlankLines(code, directiveTrimBlankLines);
104
191
  const replacement = [];
105
- let index = -1;
106
192
  let start = 0;
107
193
  let startTextRemainder = "";
108
194
  let lineNumber = 0;
109
- while (++index < tree.children.length) {
110
- const child = tree.children[index];
195
+ for (let index = 0; index < code.children.length; index++) {
196
+ const child = code.children[index];
111
197
  if (child.type !== "text")
112
198
  continue;
113
199
  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);
200
+ const matches = Array.from(child.value.matchAll(REGEX_LINE_BREAKS));
201
+ matches.forEach((match, iteration) => {
202
+ // Nodes in this line. (current child (index) is exclusive)
203
+ const line = code.children.slice(start, index);
118
204
  // Prepend text from a partial matched earlier text.
119
205
  if (startTextRemainder) {
120
206
  line.unshift({ type: "text", value: startTextRemainder });
@@ -125,36 +211,35 @@ const plugin = (options) => {
125
211
  const value = child.value.slice(textStart, match.index);
126
212
  line.push({ type: "text", value });
127
213
  }
128
- if (!isEmptyLine(line)) {
214
+ const isFirstIteration = index === 0 && iteration === 0;
215
+ if (isFirstIteration && directiveTrimBlankLines && line.length === 0) {
216
+ replacement.push({ type: "text", value: match[0] }); // eol
217
+ }
218
+ else {
129
219
  lineNumber += 1;
130
- replacement.push(createLine(line, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted));
220
+ replacement.push(createLine(line, lineNumber, directiveShowLineNumbers, directiveHighlightLines), { type: "text", value: match[0] });
131
221
  }
132
- // Add eol
133
- replacement.push({ type: "text", value: match[0] });
134
222
  start = index + 1;
135
223
  textStart = match.index + match[0].length;
136
- // iterate the match
137
- match = REGEX_LINE_BREAKS.exec(child.value);
138
- }
224
+ });
139
225
  // If we matched, make sure to not drop the text after the last line ending.
140
226
  if (start === index + 1) {
141
227
  startTextRemainder = child.value.slice(textStart);
142
228
  }
143
229
  }
144
- const line = tree.children.slice(start);
230
+ const line = code.children.slice(start);
145
231
  // Prepend text from a partial matched earlier text.
146
232
  if (startTextRemainder) {
147
233
  line.unshift({ type: "text", value: startTextRemainder });
148
234
  startTextRemainder = "";
149
235
  }
150
- if (!isEmptyLine(line)) {
151
- if (line.length > 0) {
152
- lineNumber += 1;
153
- replacement.push(createLine(line, lineNumber, startingNumber, directiveShowLineNumbers, linesToBeHighlighted));
154
- }
236
+ if (line.length > 0) {
237
+ directiveTrimBlankLines
238
+ ? replacement.push(...line)
239
+ : replacement.push(createLine(line, ++lineNumber, directiveShowLineNumbers, directiveHighlightLines));
155
240
  }
156
241
  // Replace children with new array.
157
- tree.children = replacement;
242
+ code.children = replacement;
158
243
  }
159
244
  /**
160
245
  *
@@ -183,53 +268,58 @@ const plugin = (options) => {
183
268
  * Nothing.
184
269
  */
185
270
  return (tree) => {
186
- visit(tree, "element", function (node, index, parent) {
187
- if (!parent || index === undefined || node.tagName !== "code") {
271
+ visit(tree, "element", function (code, index, parent) {
272
+ if (!parent || index === undefined || code.tagName !== "code") {
188
273
  return;
189
274
  }
190
275
  if (parent.type !== "element" || parent.tagName !== "pre") {
191
276
  return;
192
277
  }
193
- const code = node;
194
278
  const classNames = code.properties.className;
195
279
  // only for type narrowing
196
280
  /* v8 ignore next */
197
- if (!Array.isArray(classNames) && classNames !== undefined)
281
+ if (!isStringArray(classNames) && classNames !== undefined)
198
282
  return;
199
- let meta = code.data?.meta?.toLowerCase().trim() || "";
283
+ let meta = code.data?.meta?.toLowerCase().trim() ?? "";
200
284
  const language = getLanguage(classNames);
201
285
  if (language?.startsWith("{") ||
202
286
  language?.startsWith("showlinenumbers") ||
203
- language?.startsWith("nolinenumbers")) {
287
+ language?.startsWith("nolinenumbers") ||
288
+ language?.startsWith("trimblanklines")) {
204
289
  // add specifiers to meta
205
290
  meta = (language + " " + meta).trim();
206
- // remove all classnames like hljs, lang-x, language-x, because of false positive
291
+ // correct the code's meta
292
+ code.data && (code.data.meta = meta);
293
+ // remove all classnames like hljs, lang-{1,3}, language-showLineNumbers, because of false positive
207
294
  code.properties.className = undefined;
208
295
  }
209
- const directiveShowLineNumbers = meta.includes("nolinenumbers")
296
+ let directiveShowLineNumbers = meta.includes("nolinenumbers")
210
297
  ? false
211
298
  : settings.showLineNumbers || meta.includes("showlinenumbers");
212
- let startingNumber = 1;
213
299
  // find the number where the line number starts, if exists
214
- if (directiveShowLineNumbers) {
215
- const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
216
- const start = REGEX1.exec(meta)?.groups?.start;
217
- if (start && !isNaN(Number(start)))
218
- startingNumber = Number(start);
219
- }
300
+ const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
301
+ const start = REGEX1.exec(meta)?.groups?.start;
302
+ if (start && !isNaN(Number(start)))
303
+ directiveShowLineNumbers = Number(start);
220
304
  // find number range string within curly braces and parse it
221
305
  const REGEX2 = /{(?<lines>[\d\s,-]+)}/g;
222
306
  const strLineNumbers = REGEX2.exec(meta)?.groups?.lines?.replace(/\s/g, "");
223
- const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
224
- // if nothing to do for numbering and highlihting, just return
225
- if (!directiveShowLineNumbers && linesToBeHighlighted.length === 0)
307
+ const directiveHighlightLines = strLineNumbers ? rangeParser(strLineNumbers) : [];
308
+ // find the directive for trimming blank lines
309
+ const REGEX3 = /trimblanklines/i;
310
+ const directiveTrimBlankLines = settings.trimBlankLines || REGEX3.test(meta);
311
+ // if nothing to do for numbering, highlihting or trimming blank lines, just return;
312
+ if (directiveShowLineNumbers === false &&
313
+ directiveHighlightLines.length === 0 &&
314
+ directiveTrimBlankLines === false) {
226
315
  return;
227
- // flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
228
- if (checkCodeTreeForFlatteningNeed(code)) {
229
- flattenCodeTree(code);
230
316
  }
231
317
  // add container for each line mutating the code element
232
- gutter(code, directiveShowLineNumbers, startingNumber, linesToBeHighlighted);
318
+ gutter(code, {
319
+ directiveShowLineNumbers,
320
+ directiveHighlightLines,
321
+ directiveTrimBlankLines,
322
+ });
233
323
  });
234
324
  };
235
325
  };
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rehype-highlight-code-lines",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
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,10 @@
13
13
  "lint": "eslint .",
14
14
  "test": "vitest --watch=false",
15
15
  "test:watch": "vitest",
16
- "test:file": "vitest test.html.spec.ts",
16
+ "test:file1": "vitest test.md.spec.ts",
17
+ "test:file2": "vitest test.html.spec.ts",
18
+ "test:file3": "vitest test.cases.spec.ts",
19
+ "test:file4": "vitest from-rehype-highlight/test.rhcl2.spec.ts",
17
20
  "prepack": "npm run build",
18
21
  "prepublishOnly": "npm run test && npm run format && npm run test-coverage",
19
22
  "test-coverage": "vitest run --coverage"
@@ -51,16 +54,16 @@
51
54
  "url": "https://github.com/ipikuka/rehype-highlight-code-lines/issues"
52
55
  },
53
56
  "devDependencies": {
54
- "@eslint/js": "^9.19.0",
57
+ "@eslint/js": "^9.20.0",
55
58
  "@types/dedent": "^0.7.2",
56
59
  "@types/node": "^22.13.1",
57
60
  "@vitest/coverage-v8": "^3.0.5",
58
- "@vitest/eslint-plugin": "^1.1.25",
61
+ "@vitest/eslint-plugin": "^1.1.27",
59
62
  "dedent": "^1.5.3",
60
- "eslint": "^9.19.0",
63
+ "eslint": "^9.20.0",
61
64
  "eslint-config-prettier": "^10.0.1",
62
65
  "eslint-plugin-prettier": "^5.2.3",
63
- "prettier": "^3.4.2",
66
+ "prettier": "^3.5.0",
64
67
  "rehype": "^13.0.2",
65
68
  "rehype-highlight": "^7.0.2",
66
69
  "rehype-parse": "^9.0.1",
@@ -74,14 +77,17 @@
74
77
  "typescript-eslint": "^8.23.0",
75
78
  "unist-util-remove-position": "^5.0.0",
76
79
  "vfile": "^6.0.3",
77
- "vitest": "^3.0.5"
80
+ "vitest": "^3.0.5",
81
+ "unified": "^11.0.5"
78
82
  },
79
83
  "dependencies": {
80
84
  "@types/hast": "^3.0.4",
81
85
  "parse-numeric-range": "^1.3.0",
82
- "unified": "^11.0.5",
83
86
  "unist-util-visit": "^5.0.0"
84
87
  },
88
+ "peerDependencies": {
89
+ "unified": "^11"
90
+ },
85
91
  "typeCoverage": {
86
92
  "atLeast": 100,
87
93
  "detail": true,
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from "unified";
2
- import type { Root, Element, ElementContent, ElementData } from "hast";
2
+ import type { Root, Element, Text, ElementContent } from "hast";
3
3
  import { visit, type VisitorResult } from "unist-util-visit";
4
4
  import rangeParser from "parse-numeric-range";
5
5
 
@@ -7,6 +7,16 @@ type Prettify<T> = { [K in keyof T]: T[K] } & {};
7
7
 
8
8
  type PartiallyRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
9
9
 
10
+ declare module "hast" {
11
+ interface Data {
12
+ meta?: string | undefined;
13
+ }
14
+ }
15
+
16
+ type ElementWithTextNode = Element & {
17
+ children: [Text, ...ElementContent[]];
18
+ };
19
+
10
20
  export type HighlightLinesOptions = {
11
21
  showLineNumbers?: boolean;
12
22
  /**
@@ -14,19 +24,23 @@ export type HighlightLinesOptions = {
14
24
  * will be removed in the next versions
15
25
  */
16
26
  lineContainerTagName?: "div" | "span";
27
+ trimBlankLines?: boolean;
17
28
  };
18
29
 
19
30
  const DEFAULT_SETTINGS: HighlightLinesOptions = {
20
31
  showLineNumbers: false,
21
32
  lineContainerTagName: "span",
33
+ trimBlankLines: false,
22
34
  };
23
35
 
24
36
  type PartiallyRequiredHighlightLinesOptions = Prettify<
25
37
  PartiallyRequired<HighlightLinesOptions, "showLineNumbers" | "lineContainerTagName">
26
38
  >;
27
39
 
28
- type CodeData = ElementData & {
29
- meta?: string;
40
+ type GutterOptions = {
41
+ directiveShowLineNumbers: boolean | number;
42
+ directiveHighlightLines: number[];
43
+ directiveTrimBlankLines: boolean;
30
44
  };
31
45
 
32
46
  // a simple util for our use case, like clsx package
@@ -34,6 +48,33 @@ export function clsx(arr: (string | false | null | undefined | 0)[]): string[] {
34
48
  return arr.filter((item): item is string => !!item);
35
49
  }
36
50
 
51
+ // check if it is string array
52
+ function isStringArray(value: unknown): value is string[] {
53
+ return (
54
+ // type-coverage:ignore-next-line
55
+ Array.isArray(value) && value.every((item) => typeof item === "string")
56
+ );
57
+ }
58
+
59
+ // check if it is Element which first child is text
60
+ function isElementWithTextNode(node: ElementContent | undefined): node is ElementWithTextNode {
61
+ return (
62
+ node?.type === "element" && node.children[0]?.type === "text" && "value" in node.children[0]
63
+ );
64
+ }
65
+
66
+ function hasClassName(node: ElementContent | undefined, className: string): boolean {
67
+ return (
68
+ node?.type === "element" &&
69
+ isStringArray(node.properties.className) &&
70
+ node.properties.className.some((cls) => cls.includes(className))
71
+ );
72
+ }
73
+
74
+ // match all common types of line breaks
75
+ const REGEX_LINE_BREAKS = /\r?\n|\r/g;
76
+ const REGEX_LINE_BREAKS_IN_THE_BEGINNING = /^(\r?\n|\r)+/;
77
+
37
78
  /**
38
79
  *
39
80
  * add line numbers to code blocks and allow highlighting of desired code lines
@@ -51,14 +92,13 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
51
92
  * check code element children need flattening or not
52
93
  *
53
94
  */
54
- function checkCodeTreeForFlatteningNeed(code: Element): boolean {
95
+ function hasFlatteningNeed(code: Element): boolean {
55
96
  const elementContents = code.children;
56
97
 
57
98
  // type ElementContent = Comment | Element | Text
58
99
  for (const elemenContent of elementContents) {
59
- if (elemenContent.type === "element")
60
- if (elemenContent.children.length >= 1 && elemenContent.children[0].type === "element")
61
- return true;
100
+ if (elemenContent.type === "element" && Boolean(elemenContent.children.length))
101
+ if (elemenContent.children.some((ec) => ec.type === "element")) return true;
62
102
  }
63
103
 
64
104
  return false;
@@ -66,7 +106,8 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
66
106
 
67
107
  /**
68
108
  *
69
- * flatten code element children, recursively
109
+ * flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
110
+ * mutates the code, recursively
70
111
  * inspired from https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/highlight.js
71
112
  *
72
113
  */
@@ -106,25 +147,12 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
106
147
  const createLine = (
107
148
  children: ElementContent[],
108
149
  lineNumber: number,
109
- startingNumber: number,
110
- directiveShowLineNumbers: boolean,
111
- linesToBeHighlighted: number[],
150
+ directiveShowLineNumbers: boolean | number,
151
+ directiveHighlightLines: number[],
112
152
  ): Element => {
113
153
  const firstChild = children[0];
114
-
115
- const isAddition =
116
- firstChild?.type === "element" &&
117
- Array.isArray(firstChild.properties.className) &&
118
- firstChild.properties.className.some(
119
- (cls) => typeof cls === "string" && cls.includes("addition"),
120
- );
121
-
122
- const isDeletion =
123
- firstChild?.type === "element" &&
124
- Array.isArray(firstChild.properties.className) &&
125
- firstChild.properties.className.some(
126
- (cls) => typeof cls === "string" && cls.includes("deletion"),
127
- );
154
+ const isAddition = hasClassName(firstChild, "addition");
155
+ const isDeletion = hasClassName(firstChild, "deletion");
128
156
 
129
157
  return {
130
158
  type: "element",
@@ -134,54 +162,136 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
134
162
  className: clsx([
135
163
  "code-line",
136
164
  directiveShowLineNumbers && "numbered-code-line",
137
- linesToBeHighlighted.includes(lineNumber) && "highlighted-code-line",
165
+ directiveHighlightLines.includes(lineNumber) && "highlighted-code-line",
138
166
  isAddition && "inserted",
139
167
  isDeletion && "deleted",
140
168
  ]),
141
- dataLineNumber: directiveShowLineNumbers ? startingNumber - 1 + lineNumber : undefined,
169
+ dataLineNumber:
170
+ typeof directiveShowLineNumbers === "number"
171
+ ? directiveShowLineNumbers - 1 + lineNumber
172
+ : directiveShowLineNumbers
173
+ ? lineNumber
174
+ : undefined,
142
175
  },
143
176
  };
144
177
  };
145
178
 
146
- // match all common types of line breaks
147
- const REGEX_LINE_BREAKS = /\r?\n|\r/g;
179
+ /**
180
+ *
181
+ * handle elements which is multi line comment in code
182
+ * mutates the code
183
+ *
184
+ */
185
+ function handleMultiLineComments(code: Element): undefined {
186
+ for (let index = 0; index < code.children.length; index++) {
187
+ const replacement: ElementContent[] = [];
188
+
189
+ const child = code.children[index];
190
+
191
+ if (!isElementWithTextNode(child)) continue;
192
+ if (!hasClassName(child, "comment")) continue;
193
+
194
+ const textNode = child.children[0];
195
+ if (!REGEX_LINE_BREAKS.test(textNode.value)) continue;
196
+ const comments = textNode.value.split(REGEX_LINE_BREAKS);
197
+
198
+ for (let i = 0; i < comments.length; i++) {
199
+ const newChild = structuredClone(child);
200
+ newChild.children[0].value = comments[i];
201
+ replacement.push(newChild);
202
+
203
+ if (i < comments.length - 1) {
204
+ replacement.push({ type: "text", value: "\n" }); // eol
205
+ }
206
+ }
207
+
208
+ code.children = [
209
+ ...code.children.slice(0, index),
210
+ ...replacement,
211
+ ...code.children.slice(index + 1),
212
+ ];
213
+ }
214
+ }
148
215
 
149
216
  /**
150
217
  *
151
- * check the code line is empty or with value only spaces
218
+ * handle eol characters in the beginning of the only first element
219
+ * (because gutter function does not check the first element in the HAST whether contain eol at the beginning)
220
+ * mutates the code
152
221
  *
153
222
  */
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
- );
223
+ function handleFirstElementContent(code: Element): undefined {
224
+ const elementNode = code.children[0];
225
+ if (!isElementWithTextNode(elementNode)) return;
226
+
227
+ const textNode = elementNode.children[0];
228
+ if (!REGEX_LINE_BREAKS_IN_THE_BEGINNING.test(textNode.value)) return;
229
+
230
+ const match = REGEX_LINE_BREAKS_IN_THE_BEGINNING.exec(textNode.value);
231
+
232
+ if (match) {
233
+ code.children.unshift({ type: "text", value: match[0] });
234
+
235
+ textNode.value = textNode.value.replace(REGEX_LINE_BREAKS_IN_THE_BEGINNING, "");
236
+ }
159
237
  }
160
238
 
239
+ /**
240
+ *
241
+ * handle trimming blank line for the last text node
242
+ *
243
+ */
244
+ function handleTrimmingBlankLines(code: Element, directiveTrimBlankLines: boolean) {
245
+ if (!directiveTrimBlankLines) return;
246
+
247
+ const lastChild = code.children[code.children.length - 1];
248
+
249
+ if (lastChild.type === "text") {
250
+ if (/(\r?\n|\r)(\1)$/.test(lastChild.value)) {
251
+ lastChild.value = lastChild.value.replace(/(\r?\n|\r)(\1)$/, "$1");
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ *
258
+ * extract the lines from HAST of code element
259
+ * mutates the code
260
+ *
261
+ */
161
262
  function gutter(
162
- tree: Element,
163
- directiveShowLineNumbers: boolean,
164
- startingNumber: number,
165
- linesToBeHighlighted: number[],
263
+ code: Element,
264
+ {
265
+ directiveShowLineNumbers,
266
+ directiveHighlightLines,
267
+ directiveTrimBlankLines,
268
+ }: GutterOptions,
166
269
  ) {
270
+ hasFlatteningNeed(code) && flattenCodeTree(code); // mutates the code
271
+
272
+ handleMultiLineComments(code); // mutates the code
273
+
274
+ handleFirstElementContent(code); // mutates the code
275
+
276
+ handleTrimmingBlankLines(code, directiveTrimBlankLines);
277
+
167
278
  const replacement: ElementContent[] = [];
168
279
 
169
- let index = -1;
170
280
  let start = 0;
171
281
  let startTextRemainder = "";
172
282
  let lineNumber = 0;
173
283
 
174
- while (++index < tree.children.length) {
175
- const child = tree.children[index];
284
+ for (let index = 0; index < code.children.length; index++) {
285
+ const child = code.children[index];
176
286
 
177
287
  if (child.type !== "text") continue;
178
288
 
179
289
  let textStart = 0;
180
- let match = REGEX_LINE_BREAKS.exec(child.value);
181
290
 
182
- while (match !== null) {
183
- // Nodes in this line. (current child is exclusive)
184
- const line = tree.children.slice(start, index);
291
+ const matches = Array.from(child.value.matchAll(REGEX_LINE_BREAKS));
292
+ matches.forEach((match, iteration) => {
293
+ // Nodes in this line. (current child (index) is exclusive)
294
+ const line = code.children.slice(start, index);
185
295
 
186
296
  // Prepend text from a partial matched earlier text.
187
297
  if (startTextRemainder) {
@@ -195,28 +305,21 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
195
305
  line.push({ type: "text", value });
196
306
  }
197
307
 
198
- if (!isEmptyLine(line)) {
308
+ const isFirstIteration = index === 0 && iteration === 0;
309
+
310
+ if (isFirstIteration && directiveTrimBlankLines && line.length === 0) {
311
+ replacement.push({ type: "text", value: match[0] }); // eol
312
+ } else {
199
313
  lineNumber += 1;
200
314
  replacement.push(
201
- createLine(
202
- line,
203
- lineNumber,
204
- startingNumber,
205
- directiveShowLineNumbers,
206
- linesToBeHighlighted,
207
- ),
315
+ createLine(line, lineNumber, directiveShowLineNumbers, directiveHighlightLines),
316
+ { type: "text", value: match[0] }, // eol
208
317
  );
209
318
  }
210
319
 
211
- // Add eol
212
- replacement.push({ type: "text", value: match[0] });
213
-
214
320
  start = index + 1;
215
321
  textStart = match.index + match[0].length;
216
-
217
- // iterate the match
218
- match = REGEX_LINE_BREAKS.exec(child.value);
219
- }
322
+ });
220
323
 
221
324
  // If we matched, make sure to not drop the text after the last line ending.
222
325
  if (start === index + 1) {
@@ -224,7 +327,7 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
224
327
  }
225
328
  }
226
329
 
227
- const line = tree.children.slice(start);
330
+ const line = code.children.slice(start);
228
331
 
229
332
  // Prepend text from a partial matched earlier text.
230
333
  if (startTextRemainder) {
@@ -232,23 +335,16 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
232
335
  startTextRemainder = "";
233
336
  }
234
337
 
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
- }
338
+ if (line.length > 0) {
339
+ directiveTrimBlankLines
340
+ ? replacement.push(...line)
341
+ : replacement.push(
342
+ createLine(line, ++lineNumber, directiveShowLineNumbers, directiveHighlightLines),
343
+ );
248
344
  }
249
345
 
250
346
  // Replace children with new array.
251
- tree.children = replacement;
347
+ code.children = replacement;
252
348
  }
253
349
 
254
350
  /**
@@ -283,8 +379,8 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
283
379
  * Nothing.
284
380
  */
285
381
  return (tree: Root): undefined => {
286
- visit(tree, "element", function (node, index, parent): VisitorResult {
287
- if (!parent || index === undefined || node.tagName !== "code") {
382
+ visit(tree, "element", function (code, index, parent): VisitorResult {
383
+ if (!parent || index === undefined || code.tagName !== "code") {
288
384
  return;
289
385
  }
290
386
 
@@ -292,58 +388,65 @@ const plugin: Plugin<[HighlightLinesOptions?], Root> = (options) => {
292
388
  return;
293
389
  }
294
390
 
295
- const code = node;
296
-
297
391
  const classNames = code.properties.className;
298
392
 
299
393
  // only for type narrowing
300
394
  /* v8 ignore next */
301
- if (!Array.isArray(classNames) && classNames !== undefined) return;
395
+ if (!isStringArray(classNames) && classNames !== undefined) return;
302
396
 
303
- let meta = (code.data as CodeData)?.meta?.toLowerCase().trim() || "";
397
+ let meta = code.data?.meta?.toLowerCase().trim() ?? "";
304
398
 
305
399
  const language = getLanguage(classNames);
306
400
 
307
401
  if (
308
402
  language?.startsWith("{") ||
309
403
  language?.startsWith("showlinenumbers") ||
310
- language?.startsWith("nolinenumbers")
404
+ language?.startsWith("nolinenumbers") ||
405
+ language?.startsWith("trimblanklines")
311
406
  ) {
312
407
  // add specifiers to meta
313
408
  meta = (language + " " + meta).trim();
314
409
 
315
- // remove all classnames like hljs, lang-x, language-x, because of false positive
410
+ // correct the code's meta
411
+ code.data && (code.data.meta = meta);
412
+
413
+ // remove all classnames like hljs, lang-{1,3}, language-showLineNumbers, because of false positive
316
414
  code.properties.className = undefined;
317
415
  }
318
416
 
319
- const directiveShowLineNumbers = meta.includes("nolinenumbers")
417
+ let directiveShowLineNumbers: boolean | number = meta.includes("nolinenumbers")
320
418
  ? false
321
419
  : settings.showLineNumbers || meta.includes("showlinenumbers");
322
420
 
323
- let startingNumber = 1;
324
-
325
421
  // find the number where the line number starts, if exists
326
- if (directiveShowLineNumbers) {
327
- const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
328
- const start = REGEX1.exec(meta)?.groups?.start;
329
- if (start && !isNaN(Number(start))) startingNumber = Number(start);
330
- }
422
+ const REGEX1 = /showlinenumbers=(?<start>\d+)/i;
423
+ const start = REGEX1.exec(meta)?.groups?.start;
424
+ if (start && !isNaN(Number(start))) directiveShowLineNumbers = Number(start);
331
425
 
332
426
  // find number range string within curly braces and parse it
333
427
  const REGEX2 = /{(?<lines>[\d\s,-]+)}/g;
334
428
  const strLineNumbers = REGEX2.exec(meta)?.groups?.lines?.replace(/\s/g, "");
335
- const linesToBeHighlighted = strLineNumbers ? rangeParser(strLineNumbers) : [];
429
+ const directiveHighlightLines = strLineNumbers ? rangeParser(strLineNumbers) : [];
336
430
 
337
- // if nothing to do for numbering and highlihting, just return
338
- if (!directiveShowLineNumbers && linesToBeHighlighted.length === 0) return;
431
+ // find the directive for trimming blank lines
432
+ const REGEX3 = /trimblanklines/i;
433
+ const directiveTrimBlankLines = settings.trimBlankLines || REGEX3.test(meta);
339
434
 
340
- // flatten deeper nodes into first level <span> and text, especially for languages like jsx, tsx
341
- if (checkCodeTreeForFlatteningNeed(code)) {
342
- flattenCodeTree(code);
435
+ // if nothing to do for numbering, highlihting or trimming blank lines, just return;
436
+ if (
437
+ directiveShowLineNumbers === false &&
438
+ directiveHighlightLines.length === 0 &&
439
+ directiveTrimBlankLines === false
440
+ ) {
441
+ return;
343
442
  }
344
443
 
345
444
  // add container for each line mutating the code element
346
- gutter(code, directiveShowLineNumbers, startingNumber, linesToBeHighlighted);
445
+ gutter(code, {
446
+ directiveShowLineNumbers,
447
+ directiveHighlightLines,
448
+ directiveTrimBlankLines,
449
+ });
347
450
  });
348
451
  };
349
452
  };