@mui/internal-docs-infra 0.12.1-canary.32 → 0.12.1-canary.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-docs-infra",
3
- "version": "0.12.1-canary.32",
3
+ "version": "0.12.1-canary.34",
4
4
  "author": "MUI Team",
5
5
  "description": "MUI Infra - internal documentation creation tools.",
6
6
  "license": "MIT",
@@ -64,7 +64,7 @@
64
64
  "remark-stringify": "^11.0.0",
65
65
  "remark-typography": "^0.7.3",
66
66
  "sucrase": "^3.35.1",
67
- "typescript-api-extractor": "1.0.0-beta.4",
67
+ "typescript-api-extractor": "1.0.0-beta.6",
68
68
  "uint8-to-base64": "^0.2.1",
69
69
  "unified": "^11.0.5",
70
70
  "unist-util-visit": "^5.1.0",
@@ -804,5 +804,5 @@
804
804
  "bin": {
805
805
  "docs-infra": "./cli/index.mjs"
806
806
  },
807
- "gitSha": "14edbfe69366e1a1e506acaf77d33186d6e4090c"
807
+ "gitSha": "c7ecd7d0578f1fd06710d507fcd4e155afc8bde7"
808
808
  }
@@ -2,4 +2,14 @@
2
2
  * Default root directory for docs-infra build caches. Sits alongside the marker
3
3
  * directories already written under `.next/cache/docs-infra`.
4
4
  */
5
- export declare const DEFAULT_CACHE_DIR = ".next/cache/docs-infra";
5
+ export declare const DEFAULT_CACHE_DIR = ".next/cache/docs-infra";
6
+ /**
7
+ * Version of the shape of cached *output*. Entries are validated by hashing their inputs, so a
8
+ * pipeline change that alters the output for unchanged input is invisible to that hash — and
9
+ * `.next/cache` survives between builds (`@mui/internal-netlify-cache` restores it), so a warm
10
+ * cache would keep serving output produced by the previous code.
11
+ *
12
+ * Bump this whenever such a change lands. Caches whose stored shape can change fold it into their
13
+ * `getCacheContent`, which makes existing entries hash as stale and be recomputed in place.
14
+ */
15
+ export declare const CACHE_SCHEMA_VERSION = 1;
@@ -2,4 +2,15 @@
2
2
  * Default root directory for docs-infra build caches. Sits alongside the marker
3
3
  * directories already written under `.next/cache/docs-infra`.
4
4
  */
5
- export const DEFAULT_CACHE_DIR = '.next/cache/docs-infra';
5
+ export const DEFAULT_CACHE_DIR = '.next/cache/docs-infra';
6
+
7
+ /**
8
+ * Version of the shape of cached *output*. Entries are validated by hashing their inputs, so a
9
+ * pipeline change that alters the output for unchanged input is invisible to that hash — and
10
+ * `.next/cache` survives between builds (`@mui/internal-netlify-cache` restores it), so a warm
11
+ * cache would keep serving output produced by the previous code.
12
+ *
13
+ * Bump this whenever such a change lands. Caches whose stored shape can change fold it into their
14
+ * `getCacheContent`, which makes existing entries hash as stale and be recomputed in place.
15
+ */
16
+ export const CACHE_SCHEMA_VERSION = 1;
@@ -5,5 +5,5 @@ export { saveFileCache } from "./saveFileCache.mjs";
5
5
  export { resolveCachePath } from "./resolveCachePath.mjs";
6
6
  export { withFileCache } from "./withFileCache.mjs";
7
7
  export type { FileCacheTask } from "./withFileCache.mjs";
8
- export { DEFAULT_CACHE_DIR } from "./constants.mjs";
8
+ export { DEFAULT_CACHE_DIR, CACHE_SCHEMA_VERSION } from "./constants.mjs";
9
9
  export type { FileCacheRef, FileCacheEntry } from "./types.mjs";
@@ -4,4 +4,4 @@ export { loadFileCacheEntry } from "./loadFileCacheEntry.mjs";
4
4
  export { saveFileCache } from "./saveFileCache.mjs";
5
5
  export { resolveCachePath } from "./resolveCachePath.mjs";
6
6
  export { withFileCache } from "./withFileCache.mjs";
7
- export { DEFAULT_CACHE_DIR } from "./constants.mjs";
7
+ export { DEFAULT_CACHE_DIR, CACHE_SCHEMA_VERSION } from "./constants.mjs";
@@ -2,6 +2,7 @@ import { getHastTextContent } from "../loadServerTypes/hastTypeUtils.mjs";
2
2
  import { calculateFrameRanges } from "../parseSource/calculateFrameRanges.mjs";
3
3
  import { calculateFrameIndent } from "./calculateFrameIndent.mjs";
4
4
  import { restructureFrames } from "../parseSource/restructureFrames.mjs";
5
+ import { hasClassName } from "../parseSource/isFrameSpan.mjs";
5
6
  /**
6
7
  * The prefix used to identify emphasis comments in source code.
7
8
  * Comments starting with this prefix will be processed for emphasis.
@@ -494,7 +495,7 @@ function buildLineElementMap(node) {
494
495
  }
495
496
 
496
497
  // Check if this is a line element
497
- if (child.tagName === 'span' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
498
+ if (child.tagName === 'span' && hasClassName(child, 'line') && typeof child.properties.dataLn === 'number') {
498
499
  map.set(child.properties.dataLn, child);
499
500
  }
500
501
 
@@ -530,9 +531,10 @@ function isCommentOnlyLine(lineElement, commentText) {
530
531
  hasNonWhitespaceContent = true;
531
532
  }
532
533
  } else if (child.type === 'element') {
533
- const className = child.properties?.className;
534
- const classNames = Array.isArray(className) ? className : [className];
535
- if (classNames.includes('pl-c')) {
534
+ // Read once: this runs per token span, and both branches below test the
535
+ // same list.
536
+ const classNames = child.properties?.className;
537
+ if (classNames?.includes('pl-c')) {
536
538
  // This is a comment element - check if it contains the expected text
537
539
  const text = getHastTextContent(child);
538
540
  if (text.includes(commentText)) {
@@ -541,7 +543,7 @@ function isCommentOnlyLine(lineElement, commentText) {
541
543
  // Some other comment
542
544
  hasNonWhitespaceContent = true;
543
545
  }
544
- } else if (classNames.includes('pl-pse')) {
546
+ } else if (classNames?.includes('pl-pse')) {
545
547
  // This is punctuation for special expressions (JSX braces for comments)
546
548
  // Check if it's just `{` or `}` which are used for JSX comment syntax
547
549
  const text = getHastTextContent(child);
@@ -1108,7 +1110,7 @@ function applyEmphasisAndCollectHighlightedElements(node, emphasizedLines, optio
1108
1110
  }
1109
1111
 
1110
1112
  // Check if this is a line element
1111
- if (child.tagName === 'span' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
1113
+ if (child.tagName === 'span' && hasClassName(child, 'line') && typeof child.properties.dataLn === 'number') {
1112
1114
  const lineNumber = child.properties.dataLn;
1113
1115
  const meta = emphasizedLines.get(lineNumber);
1114
1116
  if (meta !== undefined) {
@@ -1242,7 +1244,7 @@ function reconcileLineAndFrameEmphasis(root, emphasizedLines) {
1242
1244
  const frameType = frame.properties?.dataFrameType;
1243
1245
  const isHighlightedFrame = frameType === 'highlighted' || frameType === 'highlighted-unfocused';
1244
1246
  for (const child of frame.children) {
1245
- if (child.type !== 'element' || child.tagName !== 'span' || child.properties?.className !== 'line' || typeof child.properties.dataLn !== 'number') {
1247
+ if (child.type !== 'element' || child.tagName !== 'span' || !hasClassName(child, 'line') || typeof child.properties.dataLn !== 'number') {
1246
1248
  continue;
1247
1249
  }
1248
1250
  const meta = emphasizedLines.get(child.properties.dataLn);
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { patch, clone } from 'jsondiffpatch';
12
12
  import { findExpandingRanges } from "./findExpandingRanges.mjs";
13
- import { isFrameSpan } from "../parseSource/isFrameSpan.mjs";
13
+ import { hasClassName, isFrameSpan } from "../parseSource/isFrameSpan.mjs";
14
14
 
15
15
  /**
16
16
  * Decodes a `VariantSource` to a live `HastRoot` (or `null` for string /
@@ -60,7 +60,7 @@ function renumberLines(root) {
60
60
  const children = frame.children;
61
61
  for (let i = 0; i < children.length; i += 1) {
62
62
  const child = children[i];
63
- if (child.type === 'element' && child.properties != null && child.properties.className === 'line') {
63
+ if (child.type === 'element' && child.properties != null && hasClassName(child, 'line')) {
64
64
  lineNumber += 1;
65
65
  const previous = child.properties.dataLn;
66
66
  if (typeof previous === 'number') {
@@ -120,7 +120,7 @@ function markAddedLinesInPlace(root, ranges) {
120
120
  const children = frame.children;
121
121
  for (let i = 0; i < children.length; i += 1) {
122
122
  const child = children[i];
123
- if (child.type !== 'element' || child.properties == null || child.properties.className !== 'line') {
123
+ if (child.type !== 'element' || child.properties == null || !hasClassName(child, 'line')) {
124
124
  continue;
125
125
  }
126
126
  const lineNumber = child.properties.dataLn;
@@ -1,6 +1,7 @@
1
1
  import { create, patch } from 'jsondiffpatch';
2
2
  import { findExpandingRanges, hasExpandingRanges } from "./findExpandingRanges.mjs";
3
3
  import { getInitialVisibleSourceLines } from "./getInitialVisibleSourceLines.mjs";
4
+ import { hasClassName } from "../parseSource/isFrameSpan.mjs";
4
5
 
5
6
  /**
6
7
  * Async-friendly variant of {@link ParseSource}. The build-time diff path
@@ -27,15 +28,12 @@ const differ = create({
27
28
  if (value === null || typeof value !== 'object') {
28
29
  return `idx:${index}`;
29
30
  }
31
+ // jsondiffpatch hands us `unknown`; the checks below validate the cast.
30
32
  const node = value;
31
- if (node.type === 'element' && node.tagName === 'span') {
32
- const cls = node.properties?.className;
33
- const className = Array.isArray(cls) ? cls.join(' ') : cls;
34
- // Collapse placeholders get a unique identity so jsondiffpatch
35
- // can't morph a wiped line span into a placeholder in place.
36
- if (className === 'collapse') {
37
- return `collapse:${index}`;
38
- }
33
+ // Collapse placeholders get a unique identity so jsondiffpatch
34
+ // can't morph a wiped line span into a placeholder in place.
35
+ if (node.type === 'element' && node.tagName === 'span' && hasClassName(node, 'collapse')) {
36
+ return `collapse:${index}`;
39
37
  }
40
38
  // Everything else (frames, lines, text) falls back to positional
41
39
  // identity — same as jsondiffpatch's default behavior — so we don't
@@ -63,7 +61,7 @@ function isEmptyLine(line) {
63
61
  return false;
64
62
  }
65
63
  function isLineElement(node) {
66
- return !!node && node.type === 'element' && node.tagName === 'span' && node.properties != null && node.properties.className === 'line';
64
+ return !!node && node.type === 'element' && node.tagName === 'span' && node.properties != null && hasClassName(node, 'line');
67
65
  }
68
66
 
69
67
  /**
@@ -84,7 +82,7 @@ function stripLineNumbersInPlace(root) {
84
82
  const lines = frame.children;
85
83
  for (let i = 0; i < lines.length; i += 1) {
86
84
  const line = lines[i];
87
- if (line.type === 'element' && line.properties != null && line.properties.className === 'line' && line.properties.dataLn !== undefined) {
85
+ if (line.type === 'element' && line.properties != null && hasClassName(line, 'line') && line.properties.dataLn !== undefined) {
88
86
  delete line.properties.dataLn;
89
87
  }
90
88
  }
@@ -187,7 +185,7 @@ function renumberLinesInPlace(root) {
187
185
  const children = frame.children;
188
186
  for (let i = 0; i < children.length; i += 1) {
189
187
  const child = children[i];
190
- if (child.type === 'element' && child.properties != null && child.properties.className === 'line') {
188
+ if (child.type === 'element' && child.properties != null && hasClassName(child, 'line')) {
191
189
  lineNumber += 1;
192
190
  child.properties.dataLn = lineNumber;
193
191
  }
@@ -303,7 +301,7 @@ function makePlaceholder(count) {
303
301
  type: 'element',
304
302
  tagName: 'span',
305
303
  properties: {
306
- className: 'collapse',
304
+ className: ['collapse'],
307
305
  dataLines: count
308
306
  },
309
307
  children
@@ -1,7 +1,7 @@
1
1
  import type { HastRoot, Transforms } from "../../CodeHighlighter/types.mjs";
2
2
  /**
3
3
  * Recursively walks a jsondiffpatch delta looking for any inserted hast
4
- * element with `className === 'collapse'`. Used to mark a manifest entry
4
+ * element whose `className` includes `'collapse'`. Used to mark a manifest entry
5
5
  * as layout-affecting (phase 1, coordinated barrier so peers stay in
6
6
  * lockstep) so the runtime doesn't have to decompress the embedded hast
7
7
  * payload on every selection change to classify the swap.
@@ -10,8 +10,9 @@ import type { HastRoot, Transforms } from "../../CodeHighlighter/types.mjs";
10
10
  * opcodes (`[value]` for insert, `[oldValue, 0, 0]` for delete, `_t: 'a'`
11
11
  * + `_N` keys for array ops). The collapse placeholder is only ever
12
12
  * produced by `compactCollapseInTreeInPlace` on the *transform* side of
13
- * the diff, so any hast element with className 'collapse' anywhere in
14
- * the delta tree is necessarily part of an insertion or in-place rewrite.
13
+ * the diff, so any hast element whose `className` includes `'collapse'`
14
+ * anywhere in the delta tree is necessarily part of an insertion or an
15
+ * in-place rewrite.
15
16
  */
16
17
  export declare function deltaContainsCollapse(delta: unknown): boolean;
17
18
  /**
@@ -1,6 +1,8 @@
1
+ import { hasClassName } from "../parseSource/isFrameSpan.mjs";
2
+
1
3
  /**
2
4
  * Recursively walks a jsondiffpatch delta looking for any inserted hast
3
- * element with `className === 'collapse'`. Used to mark a manifest entry
5
+ * element whose `className` includes `'collapse'`. Used to mark a manifest entry
4
6
  * as layout-affecting (phase 1, coordinated barrier so peers stay in
5
7
  * lockstep) so the runtime doesn't have to decompress the embedded hast
6
8
  * payload on every selection change to classify the swap.
@@ -9,22 +11,18 @@
9
11
  * opcodes (`[value]` for insert, `[oldValue, 0, 0]` for delete, `_t: 'a'`
10
12
  * + `_N` keys for array ops). The collapse placeholder is only ever
11
13
  * produced by `compactCollapseInTreeInPlace` on the *transform* side of
12
- * the diff, so any hast element with className 'collapse' anywhere in
13
- * the delta tree is necessarily part of an insertion or in-place rewrite.
14
+ * the diff, so any hast element whose `className` includes `'collapse'`
15
+ * anywhere in the delta tree is necessarily part of an insertion or an
16
+ * in-place rewrite.
14
17
  */
15
18
  export function deltaContainsCollapse(delta) {
16
19
  if (delta === null || typeof delta !== 'object') {
17
20
  return false;
18
21
  }
22
+ // The delta holds arbitrary values; the `type` check below validates the cast.
19
23
  const candidate = delta;
20
- if (candidate.type === 'element') {
21
- const cls = candidate.properties?.className;
22
- if (cls === 'collapse') {
23
- return true;
24
- }
25
- if (Array.isArray(cls) && cls.includes('collapse')) {
26
- return true;
27
- }
24
+ if (candidate.type === 'element' && hasClassName(candidate, 'collapse')) {
25
+ return true;
28
26
  }
29
27
  if (Array.isArray(delta)) {
30
28
  for (const item of delta) {
@@ -1,5 +1,5 @@
1
1
  import { COLLAPSED_VISIBLE_FRAME_TYPES } from "../parseSource/frameVisibility.mjs";
2
- import { isFrameSpan } from "../parseSource/isFrameSpan.mjs";
2
+ import { hasClassName, isFrameSpan } from "../parseSource/isFrameSpan.mjs";
3
3
 
4
4
  /**
5
5
  * Returns the set of 1-indexed source line numbers that are visible when
@@ -35,7 +35,7 @@ export function getInitialVisibleSourceLines(tree) {
35
35
  hasVisibleEmphasisFrame = true;
36
36
  }
37
37
  for (const grandChild of frame.children) {
38
- if (grandChild.type === 'element' && grandChild.properties?.className === 'line') {
38
+ if (grandChild.type === 'element' && hasClassName(grandChild, 'line')) {
39
39
  lineNumber += 1;
40
40
  if (frameVisible) {
41
41
  visible.add(lineNumber);
@@ -61,7 +61,7 @@ export function getInitialVisibleSourceLines(tree) {
61
61
  }
62
62
  const frame = child;
63
63
  for (const grandChild of frame.children) {
64
- if (grandChild.type === 'element' && grandChild.properties?.className === 'line') {
64
+ if (grandChild.type === 'element' && hasClassName(grandChild, 'line')) {
65
65
  fallbackLine += 1;
66
66
  visible.add(fallbackLine);
67
67
  }
@@ -6,12 +6,12 @@
6
6
  */
7
7
  import type { Root as HastRoot, Element } from 'hast';
8
8
  import { getHastTextContent, getShallowTextContent } from "../hastUtils/index.mjs";
9
+ import { hasClassName } from "../parseSource/isFrameSpan.mjs";
9
10
  export { getHastTextContent, getShallowTextContent };
10
11
  /**
11
12
  * Checks if a HAST element has a specific CSS class.
12
- * Handles both string and array class representations.
13
13
  */
14
- export declare function hasClass(element: Element, className: string): boolean;
14
+ export declare const hasClass: typeof hasClassName;
15
15
  /**
16
16
  * Checks if a HAST element is a span with class `line`.
17
17
  */
@@ -6,22 +6,13 @@
6
6
  */
7
7
 
8
8
  import { compressHast, getHastTextContent, getShallowTextContent } from "../hastUtils/index.mjs";
9
+ import { hasClassName } from "../parseSource/isFrameSpan.mjs";
9
10
  export { getHastTextContent, getShallowTextContent };
10
11
 
11
12
  /**
12
13
  * Checks if a HAST element has a specific CSS class.
13
- * Handles both string and array class representations.
14
14
  */
15
- export function hasClass(element, className) {
16
- const classes = element.properties?.className;
17
- if (Array.isArray(classes)) {
18
- return classes.includes(className);
19
- }
20
- if (typeof classes === 'string') {
21
- return classes.split(' ').includes(className);
22
- }
23
- return false;
24
- }
15
+ export const hasClass = hasClassName;
25
16
 
26
17
  /**
27
18
  * Checks if a HAST element is a span with class `line`.
@@ -7,7 +7,7 @@ import { highlightTypesMeta } from "./highlightTypesMeta.mjs";
7
7
  import { syncTypes } from "../syncTypes/index.mjs";
8
8
  import { loadServerTypesText } from "../loadServerTypesText/index.mjs";
9
9
  import { resolveTypesCacheKey } from "../loadServerTypesText/resolveTypesCacheKey.mjs";
10
- import { withFileCache } from "../cacheUtils/index.mjs";
10
+ import { CACHE_SCHEMA_VERSION, withFileCache } from "../cacheUtils/index.mjs";
11
11
  const functionName = 'Load Server Types';
12
12
 
13
13
  /** Cache namespace for the enhanced (highlighted) loadServerTypes result. */
@@ -66,6 +66,9 @@ export async function loadServerTypes(options) {
66
66
  ref: cacheRef,
67
67
  readOrigin: () => syncResult,
68
68
  getCacheContent: source => JSON.stringify({
69
+ // The cached value is hast, whose shape can change between releases — see
70
+ // CACHE_SCHEMA_VERSION. Dropping this lets a warm cache serve the old shape.
71
+ cacheSchemaVersion: CACHE_SCHEMA_VERSION,
69
72
  // Exclude allDependencies (re-attached fresh below) so a dep-list change does not needlessly
70
73
  // invalidate the cached highlighting, and the transient `updated` flag for hash stability.
71
74
  source: {
@@ -13,7 +13,9 @@ export declare function resolveTypesCacheKey(typesMarkdownPath: string, rootCont
13
13
  /**
14
14
  * Builds the hash-validation content for the types.md parse cache. Includes the
15
15
  * ordering config because it changes `parseTypesMarkdown`'s output, so the cache is
16
- * invalidated when either the markdown or the ordering changes. The writer (syncTypes)
17
- * and reader (loadServerTypesText) must build this identically for their hashes to match.
16
+ * invalidated when either the markdown or the ordering changes, and CACHE_SCHEMA_VERSION
17
+ * because the parsed value embeds hast (descriptions), whose shape can change between
18
+ * releases. The writer (syncTypes) and reader (loadServerTypesText) must build this
19
+ * identically for their hashes to match.
18
20
  */
19
21
  export declare function buildTypesTextCacheContent(markdown: string, ordering?: OrderingConfig): string;
@@ -1,3 +1,4 @@
1
+ import { CACHE_SCHEMA_VERSION } from "../cacheUtils/index.mjs";
1
2
  import { extractPrefixAndTitle } from "../loadServerPageIndex/extractPrefixAndTitle.mjs";
2
3
  /** Cache namespace for the types.md parse cache (TypesSourceData), mirroring the page-index cache. */
3
4
  export const TYPES_TEXT_CACHE_NAMESPACE = 'types-text';
@@ -21,9 +22,11 @@ export function resolveTypesCacheKey(typesMarkdownPath, rootContext) {
21
22
  /**
22
23
  * Builds the hash-validation content for the types.md parse cache. Includes the
23
24
  * ordering config because it changes `parseTypesMarkdown`'s output, so the cache is
24
- * invalidated when either the markdown or the ordering changes. The writer (syncTypes)
25
- * and reader (loadServerTypesText) must build this identically for their hashes to match.
25
+ * invalidated when either the markdown or the ordering changes, and CACHE_SCHEMA_VERSION
26
+ * because the parsed value embeds hast (descriptions), whose shape can change between
27
+ * releases. The writer (syncTypes) and reader (loadServerTypesText) must build this
28
+ * identically for their hashes to match.
26
29
  */
27
30
  export function buildTypesTextCacheContent(markdown, ordering) {
28
- return `${JSON.stringify(ordering ?? null)}\n${markdown}`;
31
+ return `${CACHE_SCHEMA_VERSION}\n${JSON.stringify(ordering ?? null)}\n${markdown}`;
29
32
  }
@@ -1,7 +1,7 @@
1
1
  // Example copied from https://github.com/wooorm/starry-night#example-adding-line-numbers
2
2
 
3
3
  import { createFrame } from "./createFrame.mjs";
4
- import { isFrameSpan } from "./isFrameSpan.mjs";
4
+ import { hasClassName, isFrameSpan } from "./isFrameSpan.mjs";
5
5
 
6
6
  /**
7
7
  * Counts the number of lines in a HAST tree without mutating it.
@@ -154,7 +154,7 @@ export function starryNightGutter(tree, sourceLines, frameSize = 120) {
154
154
  const frame = replacement[frameIndex];
155
155
  if (frame.type === 'element' && frame.tagName === 'span' && isFrameSpan(frame)) {
156
156
  // Extract line range from child .line elements
157
- const lineChildren = frame.children.filter(c => c.type === 'element' && c.properties?.className === 'line' && typeof c.properties.dataLn === 'number');
157
+ const lineChildren = frame.children.filter(c => c.type === 'element' && hasClassName(c, 'line') && typeof c.properties.dataLn === 'number');
158
158
  if (lineChildren.length > 0) {
159
159
  const startLine = Number(lineChildren[0].properties.dataLn) - 1;
160
160
  const endLine = Number(lineChildren[lineChildren.length - 1].properties.dataLn);
@@ -204,7 +204,7 @@ function createLine(children, line) {
204
204
  type: 'element',
205
205
  tagName: 'span',
206
206
  properties: {
207
- className: 'line',
207
+ className: ['line'],
208
208
  dataLn: line
209
209
  },
210
210
  children
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export function createFrame(children, frameType, indentLevel, truncated) {
8
8
  const properties = {
9
- className: 'frame',
9
+ className: ['frame'],
10
10
  dataLined: ''
11
11
  };
12
12
  if (frameType && frameType !== 'normal') {
@@ -1,19 +1,14 @@
1
1
  import type { Element as HastElement } from 'hast';
2
2
  /**
3
- * Returns `true` when a HAST element carries the given class name, accepting
4
- * both shapes `className` can take:
3
+ * Returns `true` when a HAST element carries the given class name.
5
4
  *
6
- * - the string form (`className: 'frame'`), used by freshly parsed / live HAST
7
- * (e.g. `createFrame`), and
8
- * - the array form (`className: ['frame']`), produced by `fallbackToHast` and by
9
- * any HAST that round-trips through serialization.
10
- *
11
- * Matching only the string silently skips real fallback frames, so class checks
12
- * on HAST that may come from either path must go through this helper.
5
+ * `className` is always the array shape (`['frame']`): it is what the
6
+ * highlighter, `fallbackToHast` and any HAST that round-trips through
7
+ * serialization produce, and what the compression dictionary encodes.
13
8
  */
14
9
  export declare function hasClassName(element: HastElement, name: string): boolean;
15
10
  /**
16
11
  * Returns `true` when a HAST element is a code frame span — its `className`
17
- * includes `'frame'` in either the string or array shape (see {@link hasClassName}).
12
+ * includes `'frame'` (see {@link hasClassName}).
18
13
  */
19
14
  export declare function isFrameSpan(element: HastElement): boolean;
@@ -1,23 +1,17 @@
1
1
  /**
2
- * Returns `true` when a HAST element carries the given class name, accepting
3
- * both shapes `className` can take:
2
+ * Returns `true` when a HAST element carries the given class name.
4
3
  *
5
- * - the string form (`className: 'frame'`), used by freshly parsed / live HAST
6
- * (e.g. `createFrame`), and
7
- * - the array form (`className: ['frame']`), produced by `fallbackToHast` and by
8
- * any HAST that round-trips through serialization.
9
- *
10
- * Matching only the string silently skips real fallback frames, so class checks
11
- * on HAST that may come from either path must go through this helper.
4
+ * `className` is always the array shape (`['frame']`): it is what the
5
+ * highlighter, `fallbackToHast` and any HAST that round-trips through
6
+ * serialization produce, and what the compression dictionary encodes.
12
7
  */
13
8
  export function hasClassName(element, name) {
14
- const className = element.properties?.className;
15
- return className === name || Array.isArray(className) && className.includes(name);
9
+ return element.properties?.className?.includes(name) ?? false;
16
10
  }
17
11
 
18
12
  /**
19
13
  * Returns `true` when a HAST element is a code frame span — its `className`
20
- * includes `'frame'` in either the string or array shape (see {@link hasClassName}).
14
+ * includes `'frame'` (see {@link hasClassName}).
21
15
  */
22
16
  export function isFrameSpan(element) {
23
17
  return hasClassName(element, 'frame');
@@ -1,5 +1,5 @@
1
1
  import { createFrame } from "./createFrame.mjs";
2
- import { isFrameSpan } from "./isFrameSpan.mjs";
2
+ import { hasClassName, isFrameSpan } from "./isFrameSpan.mjs";
3
3
  import { redistributeFrameFallbacks } from "./redistributeFrameFallbacks.mjs";
4
4
 
5
5
  /**
@@ -15,7 +15,7 @@ import { redistributeFrameFallbacks } from "./redistributeFrameFallbacks.mjs";
15
15
  */
16
16
 
17
17
  function isCollapsedLinesPlaceholder(node) {
18
- return node.type === 'element' && node.tagName === 'span' && node.properties != null && node.properties.className === 'collapse';
18
+ return node.type === 'element' && node.tagName === 'span' && node.properties != null && hasClassName(node, 'collapse');
19
19
  }
20
20
 
21
21
  /**
@@ -38,7 +38,7 @@ function flattenLineEntries(root) {
38
38
  const children = frame.children ?? [];
39
39
  for (let i = 0; i < children.length; i += 1) {
40
40
  const child = children[i];
41
- if (child.type === 'element' && child.tagName === 'span' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
41
+ if (child.type === 'element' && child.tagName === 'span' && hasClassName(child, 'line') && typeof child.properties.dataLn === 'number') {
42
42
  const lineNumber = child.properties.dataLn;
43
43
  // Resolve any placeholders waiting on a following line.
44
44
  for (const pending of pendingNextAnchor) {
@@ -92,7 +92,7 @@ function collectFrameFallbacks(root) {
92
92
  let startLine = Infinity;
93
93
  let endLine = -Infinity;
94
94
  for (const child of frame.children) {
95
- if (child.type === 'element' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
95
+ if (child.type === 'element' && hasClassName(child, 'line') && typeof child.properties.dataLn === 'number') {
96
96
  const lineNumber = child.properties.dataLn;
97
97
  if (lineNumber < startLine) {
98
98
  startLine = lineNumber;
@@ -255,7 +255,7 @@ export const transformMarkdownCode = (options = {}) => {
255
255
 
256
256
  // Add normalized language as class
257
257
  if (langFromMeta) {
258
- codeHProperties.className = `language-${normalizeLanguage(langFromMeta)}`;
258
+ codeHProperties.className = [`language-${normalizeLanguage(langFromMeta)}`];
259
259
  }
260
260
 
261
261
  // Add all props as data attributes (in camelCase)
@@ -534,7 +534,7 @@ export const transformMarkdownCode = (options = {}) => {
534
534
 
535
535
  // Add normalized language as class
536
536
  if (block.actualLang) {
537
- codeHProperties.className = `language-${normalizeLanguage(block.actualLang)}`;
537
+ codeHProperties.className = [`language-${normalizeLanguage(block.actualLang)}`];
538
538
  }
539
539
 
540
540
  // Add additional props as data attributes (in camelCase)
@@ -693,7 +693,7 @@ export const transformMarkdownCode = (options = {}) => {
693
693
 
694
694
  // Add normalized language as class
695
695
  if (block.actualLang) {
696
- codeHProperties.className = `language-${normalizeLanguage(block.actualLang)}`;
696
+ codeHProperties.className = [`language-${normalizeLanguage(block.actualLang)}`];
697
697
  }
698
698
 
699
699
  // Add additional props as data attributes (in camelCase)