@mui/internal-docs-infra 0.11.1-canary.19 → 0.11.1-canary.20

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.
@@ -204,6 +204,7 @@ export type Code = {
204
204
  export type CollapseMap = Record<number, Array<{
205
205
  offset: number;
206
206
  comments: string[];
207
+ boundary?: true;
207
208
  }>>;
208
209
  export type ControlledVariantExtraFiles = {
209
210
  [fileName: string]: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-docs-infra",
3
- "version": "0.11.1-canary.19",
3
+ "version": "0.11.1-canary.20",
4
4
  "author": "MUI Team",
5
5
  "description": "MUI Infra - internal documentation creation tools.",
6
6
  "license": "MIT",
@@ -768,5 +768,5 @@
768
768
  "bin": {
769
769
  "docs-infra": "./cli/index.mjs"
770
770
  },
771
- "gitSha": "5c3a1cfc4629c88f92e1f1c2833ae30c2d53c4cf"
771
+ "gitSha": "b61bf7023e4336cc842f108768ebbdfd79e56fad"
772
772
  }
@@ -19,13 +19,19 @@ export function decompressHast(base64, textContent) {
19
19
  const dictionary = buildDictionary(textContent);
20
20
  if (textContent != null) {
21
21
  verifyChecksum(raw, dictionary);
22
- return strFromU8(inflateSync(raw.subarray(CHECKSUM_BYTES), {
22
+ }
23
+ try {
24
+ const deflated = textContent != null ? raw.subarray(CHECKSUM_BYTES) : raw;
25
+ return strFromU8(inflateSync(deflated, {
23
26
  dictionary
24
27
  }));
28
+ } catch (error) {
29
+ // A raw inflate failure (e.g. fflate's "unexpected EOF") is almost always a
30
+ // payload that was compressed with a fallback dictionary being decoded
31
+ // without one — the checksum prefix is then read as deflate data. Surface
32
+ // that cause instead of the cryptic `{code:0}` the raw error stringifies to.
33
+ throw new Error(`Failed to decompress payload${textContent == null ? ' — if it was compressed with a fallback dictionary, that dictionary must be provided' : ''}: ${error instanceof Error ? error.message : String(error)}`);
25
34
  }
26
- return strFromU8(inflateSync(raw, {
27
- dictionary
28
- }));
29
35
  }
30
36
 
31
37
  /**
@@ -28,13 +28,13 @@ export function hastOrJsonToJsx(hastOrJson, components, fallback) {
28
28
  try {
29
29
  hast = JSON.parse(hastOrJson.hastJson);
30
30
  } catch (error) {
31
- throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`);
31
+ throw new Error(`Failed to parse hastJson: ${error instanceof Error ? error.message : String(error)}`);
32
32
  }
33
33
  } else if ('hastCompressed' in hastOrJson) {
34
34
  try {
35
35
  hast = JSON.parse(decompressHast(hastOrJson.hastCompressed, fallbackDictionary(fallback)));
36
36
  } catch (error) {
37
- throw new Error(`Failed to parse hastCompressed: ${JSON.stringify(error)}`);
37
+ throw new Error(`Failed to parse hastCompressed: ${error instanceof Error ? error.message : String(error)}`);
38
38
  }
39
39
  } else {
40
40
  hast = hastOrJson;
@@ -55,13 +55,13 @@ export function stringOrHastToString(source, fallback) {
55
55
  try {
56
56
  hast = JSON.parse(source.hastJson);
57
57
  } catch (error) {
58
- throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`);
58
+ throw new Error(`Failed to parse hastJson: ${error instanceof Error ? error.message : String(error)}`);
59
59
  }
60
60
  } else if ('hastCompressed' in source) {
61
61
  try {
62
62
  hast = JSON.parse(decompressHast(source.hastCompressed, fallbackDictionary(fallback)));
63
63
  } catch (error) {
64
- throw new Error(`Failed to parse hastCompressed: ${JSON.stringify(error)}`);
64
+ throw new Error(`Failed to parse hastCompressed: ${error instanceof Error ? error.message : String(error)}`);
65
65
  }
66
66
  } else {
67
67
  hast = source;
@@ -79,13 +79,13 @@ export function stringOrHastToJsx(source, highlighted, components, fallback) {
79
79
  try {
80
80
  hast = JSON.parse(source.hastJson);
81
81
  } catch (error) {
82
- throw new Error(`Failed to parse hastJson: ${JSON.stringify(error)}`);
82
+ throw new Error(`Failed to parse hastJson: ${error instanceof Error ? error.message : String(error)}`);
83
83
  }
84
84
  } else if ('hastCompressed' in source) {
85
85
  try {
86
86
  hast = JSON.parse(decompressHast(source.hastCompressed, fallbackDictionary(fallback)));
87
87
  } catch (error) {
88
- throw new Error(`Failed to parse hastCompressed: ${JSON.stringify(error)}`);
88
+ throw new Error(`Failed to parse hastCompressed: ${error instanceof Error ? error.message : String(error)}`);
89
89
  }
90
90
  } else {
91
91
  hast = source;
@@ -297,13 +297,16 @@ function reportPreview(context, sourceCode, {
297
297
  if (options.wrapReturn && returnStatement) {
298
298
  const hasParens = hasReturnParens(sourceCode, returnStatement);
299
299
  if (hasParens) {
300
- // Already `return (...)` — just insert the comment inside
300
+ // Already `return (...)` — just insert the comment inside. The focus
301
+ // sits one level deeper than the function body, so `@padding 2` keeps
302
+ // the `return (` line and the function signature/closing brace visible
303
+ // as context when collapsed.
301
304
  const lineStartOffset = sourceCode.getIndexFromLoc({
302
305
  line: firstNode.loc.start.line,
303
306
  column: 0
304
307
  });
305
308
  if (isSingleLine) {
306
- return fixer.insertTextBeforeRange([lineStartOffset, lineStartOffset], `${indentation}// @focus\n`);
309
+ return fixer.insertTextBeforeRange([lineStartOffset, lineStartOffset], `${indentation}// @focus @padding 2\n`);
307
310
  }
308
311
  const lastLineStartOffset = sourceCode.getIndexFromLoc({
309
312
  line: lastNode.loc.end.line,
@@ -311,7 +314,7 @@ function reportPreview(context, sourceCode, {
311
314
  });
312
315
  const lastLine = sourceCode.lines[lastNode.loc.end.line - 1];
313
316
  const lastIndentation = lastLine.match(/^\s*/)?.[0] ?? '';
314
- return [fixer.insertTextBeforeRange([lineStartOffset, lineStartOffset], `${indentation}// @focus-start\n`), fixer.insertTextAfterRange([lastLineStartOffset, lastLineStartOffset + lastLine.length], `\n${lastIndentation}// @focus-end`)];
317
+ return [fixer.insertTextBeforeRange([lineStartOffset, lineStartOffset], `${indentation}// @focus-start @padding 2\n`), fixer.insertTextAfterRange([lastLineStartOffset, lastLineStartOffset + lastLine.length], `\n${lastIndentation}// @focus-end`)];
315
318
  }
316
319
 
317
320
  // No parens — wrap `return <X>` into `return (\n // comment\n <X>\n)`
@@ -319,9 +322,9 @@ function reportPreview(context, sourceCode, {
319
322
  const returnIndentation = sourceCode.lines[returnStatement.loc.start.line - 1].match(/^\s*/)?.[0] ?? '';
320
323
  const innerIndentation = `${returnIndentation} `;
321
324
  if (isSingleLine) {
322
- return [fixer.replaceTextRange([returnKeywordEnd, firstNode.range[0]], ` (\n${innerIndentation}// @focus\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${returnIndentation})`)];
325
+ return [fixer.replaceTextRange([returnKeywordEnd, firstNode.range[0]], ` (\n${innerIndentation}// @focus @padding 2\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${returnIndentation})`)];
323
326
  }
324
- return [fixer.replaceTextRange([returnKeywordEnd, firstNode.range[0]], ` (\n${innerIndentation}// @focus-start\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${innerIndentation}// @focus-end\n${returnIndentation})`)];
327
+ return [fixer.replaceTextRange([returnKeywordEnd, firstNode.range[0]], ` (\n${innerIndentation}// @focus-start @padding 2\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${innerIndentation}// @focus-end\n${returnIndentation})`)];
325
328
  }
326
329
 
327
330
  // Non-wrapper: wrapReturn with implicit-return arrow — wrap expression in parens with comment.
@@ -332,9 +335,9 @@ function reportPreview(context, sourceCode, {
332
335
  const arrowIndentation = sourceCode.lines[arrowToken.loc.start.line - 1].match(/^\s*/)?.[0] ?? '';
333
336
  const innerIndentation = `${arrowIndentation} `;
334
337
  if (isSingleLine) {
335
- return [fixer.replaceTextRange([arrowToken.range[1], firstNode.range[0]], ` (\n${innerIndentation}// @focus\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${arrowIndentation})`)];
338
+ return [fixer.replaceTextRange([arrowToken.range[1], firstNode.range[0]], ` (\n${innerIndentation}// @focus @padding 2\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${arrowIndentation})`)];
336
339
  }
337
- return [fixer.replaceTextRange([arrowToken.range[1], firstNode.range[0]], ` (\n${innerIndentation}// @focus-start\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${innerIndentation}// @focus-end\n${arrowIndentation})`)];
340
+ return [fixer.replaceTextRange([arrowToken.range[1], firstNode.range[0]], ` (\n${innerIndentation}// @focus-start @padding 2\n${innerIndentation}`), fixer.insertTextAfterRange(lastNode.range, `\n${innerIndentation}// @focus-end\n${arrowIndentation})`)];
338
341
  }
339
342
  }
340
343
 
@@ -0,0 +1,19 @@
1
+ import type { VariantSource } from "../../CodeHighlighter/types.mjs";
2
+ import type { FallbackNode } from "../../CodeHighlighter/fallbackFormat.mjs";
3
+ /**
4
+ * Decode a `VariantSource` into a form that carries no serialization: a plain
5
+ * string stays a string, while a serialized `hastCompressed` / `hastJson`
6
+ * payload (or a live `HastRoot`) resolves to a live `HastRoot`. The
7
+ * `{ hastJson }` / `{ hastCompressed }` shapes never leak out, so consumers can
8
+ * read the source as text (`stringOrHastToString`) or inspect / transform the
9
+ * HAST tree directly without handling a DEFLATE dictionary.
10
+ *
11
+ * Decoding reuses the shared `decodeHastSource` cache — so a source already
12
+ * decoded for rendering is not inflated again — then returns a
13
+ * `structuredClone` of the tree. The clone matters: `decodeHastSource` hands
14
+ * back a read-only tree shared with the live render, and the result here is
15
+ * handed to user code (the `transformVariant` hook), which must be able to
16
+ * mutate it without corrupting that shared tree. `fallback` is the DEFLATE
17
+ * dictionary for a `hastCompressed` source.
18
+ */
19
+ export declare function decodeSource(source: VariantSource, fallback?: FallbackNode[]): VariantSource;
@@ -0,0 +1,25 @@
1
+ import { decodeHastSource } from "./decodeHastSource.mjs";
2
+
3
+ /**
4
+ * Decode a `VariantSource` into a form that carries no serialization: a plain
5
+ * string stays a string, while a serialized `hastCompressed` / `hastJson`
6
+ * payload (or a live `HastRoot`) resolves to a live `HastRoot`. The
7
+ * `{ hastJson }` / `{ hastCompressed }` shapes never leak out, so consumers can
8
+ * read the source as text (`stringOrHastToString`) or inspect / transform the
9
+ * HAST tree directly without handling a DEFLATE dictionary.
10
+ *
11
+ * Decoding reuses the shared `decodeHastSource` cache — so a source already
12
+ * decoded for rendering is not inflated again — then returns a
13
+ * `structuredClone` of the tree. The clone matters: `decodeHastSource` hands
14
+ * back a read-only tree shared with the live render, and the result here is
15
+ * handed to user code (the `transformVariant` hook), which must be able to
16
+ * mutate it without corrupting that shared tree. `fallback` is the DEFLATE
17
+ * dictionary for a `hastCompressed` source.
18
+ */
19
+ export function decodeSource(source, fallback) {
20
+ if (typeof source === 'string') {
21
+ return source;
22
+ }
23
+ const root = decodeHastSource(source, fallback);
24
+ return root ? structuredClone(root) : source;
25
+ }
@@ -0,0 +1,17 @@
1
+ import type { VariantSource } from "../../CodeHighlighter/types.mjs";
2
+ import type { FallbackNode } from "../../CodeHighlighter/fallbackFormat.mjs";
3
+ /**
4
+ * Decode a `VariantSource` to its plain text, reusing the shared
5
+ * `decodeHastSource` cache so a `hastCompressed` / `hastJson` payload that was
6
+ * already decoded for rendering is not inflated and parsed a second time.
7
+ *
8
+ * String sources are returned unchanged and need no `fallback`. For an encoded
9
+ * source, `fallback` supplies the DEFLATE dictionary required to decode a
10
+ * `hastCompressed` payload (the file's compact fallback text); omitting it for
11
+ * such a payload surfaces a descriptive error from `decodeHastSource` rather
12
+ * than a cryptic inflate failure.
13
+ *
14
+ * `null` / `undefined` sources resolve to an empty string so callers can treat
15
+ * a missing source the same as an empty file.
16
+ */
17
+ export declare function decodeSourceToText(source: VariantSource | null | undefined, fallback?: FallbackNode[]): string;
@@ -0,0 +1,26 @@
1
+ import { toText } from 'hast-util-to-text';
2
+ import { decodeHastSource } from "./decodeHastSource.mjs";
3
+
4
+ /**
5
+ * Decode a `VariantSource` to its plain text, reusing the shared
6
+ * `decodeHastSource` cache so a `hastCompressed` / `hastJson` payload that was
7
+ * already decoded for rendering is not inflated and parsed a second time.
8
+ *
9
+ * String sources are returned unchanged and need no `fallback`. For an encoded
10
+ * source, `fallback` supplies the DEFLATE dictionary required to decode a
11
+ * `hastCompressed` payload (the file's compact fallback text); omitting it for
12
+ * such a payload surfaces a descriptive error from `decodeHastSource` rather
13
+ * than a cryptic inflate failure.
14
+ *
15
+ * `null` / `undefined` sources resolve to an empty string so callers can treat
16
+ * a missing source the same as an empty file.
17
+ */
18
+ export function decodeSourceToText(source, fallback) {
19
+ if (source == null || typeof source === 'string') {
20
+ return source ?? '';
21
+ }
22
+ const root = decodeHastSource(source, fallback);
23
+ return root ? toText(root, {
24
+ whitespace: 'pre'
25
+ }) : '';
26
+ }
@@ -4,7 +4,7 @@
4
4
  * Uses addPathsToVariant for the core logic, then flattens the result
5
5
  */
6
6
 
7
- import { stringOrHastToString } from "../hastUtils/index.mjs";
7
+ import { decodeSourceToText } from "./decodeSourceToText.mjs";
8
8
  import { addPathsToVariant } from "./addCodeVariantPaths.mjs";
9
9
  /**
10
10
  * Flatten a VariantCode into a flat files structure
@@ -21,8 +21,9 @@ export function flattenCodeVariant(variant) {
21
21
  if (variantWithPaths.path && variantWithPaths.source !== undefined) {
22
22
  result[variantWithPaths.path] = {
23
23
  // The source may be `hastCompressed`; its `fallback` is the DEFLATE
24
- // dictionary needed to decode it back to text.
25
- source: stringOrHastToString(variantWithPaths.source, variantWithPaths.fallback)
24
+ // dictionary needed to decode it back to text. `decodeSourceToText` reuses
25
+ // the shared decode cache rather than re-inflating on every export.
26
+ source: decodeSourceToText(variantWithPaths.source, variantWithPaths.fallback)
26
27
  };
27
28
  }
28
29
 
@@ -39,7 +40,7 @@ export function flattenCodeVariant(variant) {
39
40
  continue;
40
41
  }
41
42
  result[fileWithPath.path] = {
42
- source: stringOrHastToString(fileWithPath.source || '', fileWithPath.fallback),
43
+ source: decodeSourceToText(fileWithPath.source, fileWithPath.fallback),
43
44
  ...(fileWithPath.metadata && {
44
45
  metadata: fileWithPath.metadata
45
46
  })
@@ -159,7 +159,17 @@ export function starryNightGutter(tree, sourceLines, frameSize = 120) {
159
159
  const startLine = Number(lineChildren[0].properties.dataLn) - 1;
160
160
  const endLine = Number(lineChildren[lineChildren.length - 1].properties.dataLn);
161
161
  const joined = sourceLines.slice(startLine, endLine).join('\n');
162
- const text = frameIndex < lastIndex ? `${joined}\n` : joined;
162
+ // Non-final frames are always followed by more content, so their text
163
+ // ends with the line separator. The final frame's text ends with a
164
+ // newline only when the source itself does — mirroring the highlighted
165
+ // render, whose last `.line` span is then followed by a trailing `\n`
166
+ // text node. `sourceLines` has one more entry than `lineNumber` exactly
167
+ // when the source ended with a newline (the empty segment after it
168
+ // never became a line). This keeps the plain-text fallback the same
169
+ // height as the highlighted render (no hydration jump) AND makes the
170
+ // root fallback dictionary an exact match for the raw source text.
171
+ const sourceEndsWithNewline = sourceLines.length > lineNumber;
172
+ const text = frameIndex < lastIndex || sourceEndsWithNewline ? `${joined}\n` : joined;
163
173
  // Cast to `ElementData` because `hast-util-from-parse5` augments
164
174
  // it with a required `position` field (upstream bug — should be
165
175
  // optional). We're not running through that parser here, so the