@mui/internal-docs-infra 0.12.1-canary.40 → 0.12.1-canary.41

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.40",
3
+ "version": "0.12.1-canary.41",
4
4
  "author": "MUI Team",
5
5
  "description": "MUI Infra - internal documentation creation tools.",
6
6
  "license": "MIT",
@@ -804,5 +804,5 @@
804
804
  "bin": {
805
805
  "docs-infra": "./cli/index.mjs"
806
806
  },
807
- "gitSha": "99c21315b55b03f58d832f7854e628da68343a10"
807
+ "gitSha": "96f05ba632fabe0c395fd6bf647b40c3bb717ee6"
808
808
  }
@@ -0,0 +1,10 @@
1
+ import type { ParserOptions } from 'typescript-api-extractor';
2
+ /**
3
+ * Extraction policy the pipeline hands to `typescript-api-extractor`.
4
+ *
5
+ * The depth and property-count caps decide whether the parser resolves a type or hands
6
+ * back a preserved reference, so anything parsing sources outside the pipeline — tests
7
+ * included — has to apply the same caps or it will resolve shapes the real build leaves
8
+ * alone.
9
+ */
10
+ export declare const PARSER_OPTIONS: ParserOptions;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Extraction policy the pipeline hands to `typescript-api-extractor`.
3
+ *
4
+ * The depth and property-count caps decide whether the parser resolves a type or hands
5
+ * back a preserved reference, so anything parsing sources outside the pipeline — tests
6
+ * included — has to apply the same caps or it will resolve shapes the real build leaves
7
+ * alone.
8
+ */
9
+ export const PARSER_OPTIONS = {
10
+ includeExternalTypes: false,
11
+ shouldInclude: ({
12
+ depth
13
+ }) => depth <= 15,
14
+ shouldResolveObject: ({
15
+ propertyCount,
16
+ depth
17
+ }) => propertyCount <= 50 && depth <= 15
18
+ };
@@ -1,5 +1,6 @@
1
1
  import { uniq } from 'es-toolkit';
2
- import { isExternalType, isIntrinsicType, isUnionType, isObjectType, isLiteralType } from "./typeGuards.mjs";
2
+ import { isExternalType, isIntrinsicType, isUnionType, isObjectType, isLiteralType, isTypeOperatorType, isTypeQueryType } from "./typeGuards.mjs";
3
+ import { groupType, UNION_OR_INTERSECTION } from "./precedence.mjs";
3
4
 
4
5
  /**
5
6
  * Metadata for an external type discovered during formatting.
@@ -52,6 +53,25 @@ export function isOwnTypeName(typeName, collector) {
52
53
  return false;
53
54
  }
54
55
 
56
+ /**
57
+ * Members of a union, with preserved type operators replaced by the keys they resolve to.
58
+ *
59
+ * `'muted' | keyof Config` reads as a plain literal union to someone reading the docs, and
60
+ * it reached this module as one until the parser started preserving the operator.
61
+ *
62
+ * `maybeCollectExternalUnion` needs this to decide whether every member is a literal. When
63
+ * formatting, the operator branch below would produce the same text on its own; flattening
64
+ * first is what lets `uniq` dedupe a key against a sibling listed next to the operator.
65
+ */
66
+ function resolvedUnionMembers(type) {
67
+ return type.types.flatMap(member => {
68
+ if (!isTypeOperatorType(member) || member.resolvedType === undefined) {
69
+ return member;
70
+ }
71
+ return isUnionType(member.resolvedType) ? member.resolvedType.types : member.resolvedType;
72
+ });
73
+ }
74
+
55
75
  /**
56
76
  * Formats an external type definition as a simple type string.
57
77
  * This produces a concise representation suitable for documentation.
@@ -61,9 +81,21 @@ export function isOwnTypeName(typeName, collector) {
61
81
  export function formatExternalTypeDefinition(type) {
62
82
  if (isUnionType(type)) {
63
83
  // Always expand union types - don't use typeName since we want the full definition
64
- const members = type.types.map(t => formatExternalTypeDefinition(t));
84
+ const members = resolvedUnionMembers(type).map(t => formatExternalTypeDefinition(t));
65
85
  return uniq(members).join(' | ');
66
86
  }
87
+ if (isTypeOperatorType(type)) {
88
+ // This section shows full definitions, so an operator is worth more as the keys it
89
+ // stands for than as its authored syntax.
90
+ if (type.resolvedType !== undefined) {
91
+ return formatExternalTypeDefinition(type.resolvedType);
92
+ }
93
+ // `keyof` binds tighter than both composers, so a composite operand has to be grouped.
94
+ return `${type.operator} ${groupType(formatExternalTypeDefinition(type.type), UNION_OR_INTERSECTION)}`;
95
+ }
96
+ if (isTypeQueryType(type)) {
97
+ return `typeof ${type.expressionName}`;
98
+ }
67
99
  if (isLiteralType(type)) {
68
100
  const value = type.value;
69
101
  // Ensure string literals are quoted with single quotes
@@ -142,7 +174,7 @@ export function maybeCollectExternalUnion(type, collector) {
142
174
  }
143
175
 
144
176
  // Only collect if ALL members are literals
145
- const allMembersAreLiterals = type.types.every(t => isLiteralType(t) || isIntrinsicType(t) && ['string', 'number', 'boolean'].includes(t.intrinsic));
177
+ const allMembersAreLiterals = resolvedUnionMembers(type).every(t => isLiteralType(t) || isIntrinsicType(t) && ['string', 'number', 'boolean'].includes(t.intrinsic));
146
178
  if (allMembersAreLiterals) {
147
179
  collector.collected.set(typeName, {
148
180
  name: typeName,
@@ -1,7 +1,27 @@
1
1
  import { uniq } from 'es-toolkit';
2
- import { isExternalType, isIntrinsicType, isUnionType, isIntersectionType, isObjectType, isAnonymousObjectType, isArrayType, isFunctionType, isLiteralType, isTupleType, isTypeParameterType, isInternalTypeName } from "./typeGuards.mjs";
2
+ import { isExternalType, isIntrinsicType, isUnionType, isIntersectionType, isObjectType, isAnonymousObjectType, isArrayType, isFunctionType, isLiteralType, isTupleType, isTypeParameterType, isTypeOperatorType, isTypeQueryType, isInternalTypeName } from "./typeGuards.mjs";
3
3
  import { isOwnTypeName, maybeCollectExternalUnion, maybeCollectExternalFunction, maybeCollectExternalReference } from "./externalTypes.mjs";
4
4
  import { prettyFormat } from "./format.mjs";
5
+ import { groupType, UNION, UNION_OR_INTERSECTION } from "./precedence.mjs";
6
+ /**
7
+ * The keys a preserved type operator stands for, or `undefined` when it should be shown as
8
+ * the syntax it was written as.
9
+ *
10
+ * A type parameter operand is kept by name in raw declarations: the checker resolves
11
+ * `keyof T` to its base constraint, which holds no type parameter to recover `T` from.
12
+ * Both the union branch and the operator branch ask this, so they cannot disagree about
13
+ * which operators expand. `formatExternalTypeDefinition` takes no options and always
14
+ * expands, so it deliberately does not share this rule.
15
+ */
16
+ function resolvedOperatorKeys(type, preserveTypeParameters) {
17
+ if (!isTypeOperatorType(type) || type.resolvedType === undefined) {
18
+ return undefined;
19
+ }
20
+ if (preserveTypeParameters && isTypeParameterType(type.type)) {
21
+ return undefined;
22
+ }
23
+ return type.resolvedType;
24
+ }
5
25
  export function formatType(type, options) {
6
26
  const {
7
27
  removeUndefined = false,
@@ -93,6 +113,13 @@ export function formatType(type, options) {
93
113
  if (isTypeParameterType(t) && isUnionType(t.constraint)) {
94
114
  return t.constraint.types;
95
115
  }
116
+
117
+ // A `keyof` member stands for its keys, so flatten them in to be deduped and
118
+ // ordered with their siblings rather than joined in as one opaque string.
119
+ const operatorKeys = resolvedOperatorKeys(t, preserveTypeParameters);
120
+ if (operatorKeys !== undefined) {
121
+ return isUnionType(operatorKeys) ? operatorKeys.types : operatorKeys;
122
+ }
96
123
  return t;
97
124
  });
98
125
 
@@ -199,7 +226,9 @@ export function formatType(type, options) {
199
226
  if (formattedMembers.length === 1) {
200
227
  return formattedMembers[0];
201
228
  }
202
- return formattedMembers.join(' & ');
229
+
230
+ // `&` binds tighter than `|`, so a union member has to be grouped to survive the join.
231
+ return formattedMembers.map(member => groupType(member, UNION)).join(' & ');
203
232
  }
204
233
  if (isObjectType(type)) {
205
234
  // Check if the object has an index signature
@@ -428,6 +457,44 @@ export function formatType(type, options) {
428
457
  externalTypesCollector
429
458
  }) : type.name;
430
459
  }
460
+ if (isTypeOperatorType(type)) {
461
+ // The operator carries the checker result alongside the authored syntax. Format the
462
+ // result so `keyof X` keeps documenting the keys it stands for, whether that result is
463
+ // exact, a generic base constraint, or a fallback.
464
+ const operatorKeys = resolvedOperatorKeys(type, preserveTypeParameters);
465
+ if (operatorKeys !== undefined) {
466
+ return formatType(operatorKeys, {
467
+ expandObjects,
468
+ exportNames,
469
+ typeNameMap,
470
+ externalTypesCollector,
471
+ selfName,
472
+ preserveTypeParameters
473
+ });
474
+ }
475
+
476
+ // Either the operand is kept by name, or syntax-only output omitted the resolved
477
+ // result; the authored operand is all there is to show. `keyof` binds tighter than
478
+ // both composers, so a composite operand has to be grouped.
479
+ const operand = formatType(type.type, {
480
+ exportNames,
481
+ typeNameMap,
482
+ externalTypesCollector,
483
+ selfName,
484
+ preserveTypeParameters
485
+ });
486
+ return `${type.operator} ${groupType(operand, UNION_OR_INTERSECTION)}`;
487
+ }
488
+ if (isTypeQueryType(type)) {
489
+ return `typeof ${type.expressionName}`;
490
+ }
491
+
492
+ // Export-level nodes reach the page through formatClass/formatComponent/formatEnum, so
493
+ // they are the only kinds expected to fall through here. Asserting that keeps a node kind
494
+ // the parser gains later from silently documenting itself as the word `unknown`, which is
495
+ // how preserved `keyof` operators went unnoticed through a parser upgrade.
496
+ // Parenthesized so this reads as an expression rather than a `type` alias declaration.
497
+ type;
431
498
  return 'unknown';
432
499
  }
433
500
 
@@ -0,0 +1,24 @@
1
+ import type * as tae from 'typescript-api-extractor';
2
+ export interface ParseTestSourcesOptions {
3
+ /**
4
+ * Declarations placed in the virtual default library. Types declared here are external
5
+ * to the parsed sources, which is what makes the parser preserve constructs such as
6
+ * `keyof` rather than expanding them away — a locally declared operand is expanded.
7
+ * Omit to parse without a standard library at all.
8
+ */
9
+ lib?: string;
10
+ /** Overrides merged over the pipeline's own extraction policy. */
11
+ parserOptions?: tae.ParserOptions;
12
+ }
13
+ /**
14
+ * Parses TypeScript sources the way the pipeline does, without touching the filesystem.
15
+ *
16
+ * Tests that assert on parsed exports should start from source rather than hand-built
17
+ * nodes, so they stay honest about what `typescript-api-extractor` actually emits. The
18
+ * extraction policy is the pipeline's own, so a shape the real build declines to resolve
19
+ * is left unresolved here too.
20
+ *
21
+ * Sources are keyed by file name (e.g. `ComponentRootDataAttributes.ts`) and may import
22
+ * each other by relative path. The first entry is the entrypoint.
23
+ */
24
+ export declare function parseTestSources(sources: Record<string, string>, options?: ParseTestSourcesOptions): tae.ExportNode[];
@@ -0,0 +1,55 @@
1
+ import ts from 'typescript';
2
+ import { parseFromProgram } from 'typescript-api-extractor';
3
+ import { PARSER_OPTIONS } from "./constants.mjs";
4
+ const ROOT = '/virtual';
5
+ const LIB_PATH = `${ROOT}/lib.d.ts`;
6
+ /**
7
+ * Parses TypeScript sources the way the pipeline does, without touching the filesystem.
8
+ *
9
+ * Tests that assert on parsed exports should start from source rather than hand-built
10
+ * nodes, so they stay honest about what `typescript-api-extractor` actually emits. The
11
+ * extraction policy is the pipeline's own, so a shape the real build declines to resolve
12
+ * is left unresolved here too.
13
+ *
14
+ * Sources are keyed by file name (e.g. `ComponentRootDataAttributes.ts`) and may import
15
+ * each other by relative path. The first entry is the entrypoint.
16
+ */
17
+ export function parseTestSources(sources, options = {}) {
18
+ const {
19
+ lib,
20
+ parserOptions
21
+ } = options;
22
+ const sourceFiles = new Map();
23
+ if (lib !== undefined) {
24
+ sourceFiles.set(LIB_PATH, ts.createSourceFile(LIB_PATH, lib, ts.ScriptTarget.ESNext, true));
25
+ }
26
+ const entryPaths = [];
27
+ for (const [name, text] of Object.entries(sources)) {
28
+ const filePath = `${ROOT}/${name}`;
29
+ sourceFiles.set(filePath, ts.createSourceFile(filePath, text, ts.ScriptTarget.ESNext, true));
30
+ entryPaths.push(filePath);
31
+ }
32
+ const host = {
33
+ getSourceFile: fileName => sourceFiles.get(fileName),
34
+ getDefaultLibFileName: () => LIB_PATH,
35
+ writeFile: () => {},
36
+ getCurrentDirectory: () => ROOT,
37
+ getCanonicalFileName: fileName => fileName,
38
+ useCaseSensitiveFileNames: () => true,
39
+ getNewLine: () => '\n',
40
+ fileExists: fileName => sourceFiles.has(fileName),
41
+ readFile: fileName => sourceFiles.get(fileName)?.text
42
+ };
43
+ const program = ts.createProgram(entryPaths, {
44
+ target: ts.ScriptTarget.ESNext,
45
+ module: ts.ModuleKind.ESNext,
46
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
47
+ strict: true,
48
+ rootDir: ROOT,
49
+ noLib: lib === undefined
50
+ }, host);
51
+ return parseFromProgram(entryPaths[0], program, {
52
+ ...PARSER_OPTIONS,
53
+ ...parserOptions
54
+ }).exports;
55
+ }
@@ -0,0 +1,13 @@
1
+ /** Composers that bind looser than `keyof`, and than each other in this order. */
2
+ export declare const UNION = "|";
3
+ export declare const UNION_OR_INTERSECTION = "|&";
4
+ /**
5
+ * Whether a formatted type composes at its top level with one of `operators`, so it needs
6
+ * grouping before being placed somewhere that binds tighter.
7
+ *
8
+ * Operators nested inside the type — an object's property type, a type argument, a function
9
+ * signature — are already grouped by their own brackets and are left alone.
10
+ */
11
+ export declare function hasTopLevelOperator(formatted: string, operators: string): boolean;
12
+ /** Wraps a formatted type in parentheses when it would otherwise re-associate. */
13
+ export declare function groupType(formatted: string, operators: string): string;
@@ -0,0 +1,40 @@
1
+ /** Composers that bind looser than `keyof`, and than each other in this order. */
2
+ export const UNION = '|';
3
+ export const UNION_OR_INTERSECTION = '|&';
4
+
5
+ /**
6
+ * Whether a formatted type composes at its top level with one of `operators`, so it needs
7
+ * grouping before being placed somewhere that binds tighter.
8
+ *
9
+ * Operators nested inside the type — an object's property type, a type argument, a function
10
+ * signature — are already grouped by their own brackets and are left alone.
11
+ */
12
+ export function hasTopLevelOperator(formatted, operators) {
13
+ let depth = 0;
14
+ let quote = '';
15
+ for (let index = 0; index < formatted.length; index += 1) {
16
+ const char = formatted[index];
17
+ if (quote) {
18
+ if (char === quote) {
19
+ quote = '';
20
+ }
21
+ } else if (char === "'" || char === '"') {
22
+ quote = char;
23
+ } else if (char === '(' || char === '[' || char === '{' || char === '<') {
24
+ depth += 1;
25
+ } else if (char === ')' || char === ']' || char === '}') {
26
+ depth -= 1;
27
+ } else if (char === '>' && formatted[index - 1] !== '=') {
28
+ // `=>` is an arrow, not the end of a type argument list.
29
+ depth -= 1;
30
+ } else if (depth === 0 && operators.includes(char)) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ }
36
+
37
+ /** Wraps a formatted type in parentheses when it would otherwise re-associate. */
38
+ export function groupType(formatted, operators) {
39
+ return hasTopLevelOperator(formatted, operators) ? `(${formatted})` : formatted;
40
+ }
@@ -5,6 +5,7 @@ import ts from 'typescript';
5
5
  import { createOptimizedProgram } from "./createOptimizedProgram.mjs";
6
6
  import { augmentComponentsWithInheritedProps } from "./inheritedExternalProps.mjs";
7
7
  import { transformConstantGroup } from "./transformConstantGroup.mjs";
8
+ import { PARSER_OPTIONS } from "./constants.mjs";
8
9
  import { extractJSDocText, isJSDocNodeArray } from "./extractJSDocText.mjs";
9
10
  import { PerformanceTracker } from "./performanceTracking.mjs";
10
11
  import { nameMark } from "../loadPrecomputedCodeHighlighter/performanceLogger.mjs";
@@ -269,16 +270,6 @@ export async function processTypes(request) {
269
270
  const programWrapperEnd = tracker.mark(nameMark(functionName, 'Program Creation End', [request.relativePath], true));
270
271
  tracker.measure(nameMark(functionName, 'Program Creation', [request.relativePath], true), programWrapperStart, programWrapperEnd);
271
272
  const internalTypesCache = {};
272
- const parserOptions = {
273
- includeExternalTypes: false,
274
- shouldInclude: ({
275
- depth
276
- }) => depth <= 15,
277
- shouldResolveObject: ({
278
- propertyCount,
279
- depth
280
- }) => propertyCount <= 50 && depth <= 15
281
- };
282
273
 
283
274
  // Process variants in parallel
284
275
  const resolvedVariantMap = new Map(request.resolvedVariantMap);
@@ -299,7 +290,7 @@ export async function processTypes(request) {
299
290
  // type aliases, and re-exports properly
300
291
  const {
301
292
  exports
302
- } = parseFromProgram(entrypoint, program, parserOptions);
293
+ } = parseFromProgram(entrypoint, program, PARSER_OPTIONS);
303
294
 
304
295
  // Re-add configured props that the parser dropped because they are
305
296
  // inherited from an externally declared type in node_modules
@@ -343,7 +334,7 @@ export async function processTypes(request) {
343
334
  }
344
335
  const {
345
336
  exports: internalExport
346
- } = parseFromProgram(file, program, parserOptions);
337
+ } = parseFromProgram(file, program, PARSER_OPTIONS);
347
338
 
348
339
  // Metadata files may declare their members as an enum or as named constants;
349
340
  // normalize both to a single constant group named after the file.
@@ -61,6 +61,16 @@ export declare function isTypeParameterType(type: unknown): type is tae.TypePara
61
61
  * Type guard to check if a type node is a component type.
62
62
  */
63
63
  export declare function isComponentType(type: unknown): type is tae.ComponentNode;
64
+ /**
65
+ * Type guard to check if a type node is a preserved type operator, such as `keyof T`.
66
+ * The operand is carried in `type` and the checker result in `resolvedType`.
67
+ */
68
+ export declare function isTypeOperatorType(type: unknown): type is tae.TypeOperatorNode;
69
+ /**
70
+ * Type guard to check if a type node is a preserved type query, such as `typeof value`.
71
+ * Type queries carry only the authored expression, never a resolved value shape.
72
+ */
73
+ export declare function isTypeQueryType(type: unknown): type is tae.TypeQueryNode;
64
74
  /**
65
75
  * Checks if a type name is a TypeScript internal symbol name.
66
76
  * Internal names like __object, __type, __function are used by TypeScript
@@ -108,6 +108,22 @@ export function isComponentType(type) {
108
108
  return hasKind(type, 'component');
109
109
  }
110
110
 
111
+ /**
112
+ * Type guard to check if a type node is a preserved type operator, such as `keyof T`.
113
+ * The operand is carried in `type` and the checker result in `resolvedType`.
114
+ */
115
+ export function isTypeOperatorType(type) {
116
+ return hasKind(type, 'typeOperator');
117
+ }
118
+
119
+ /**
120
+ * Type guard to check if a type node is a preserved type query, such as `typeof value`.
121
+ * Type queries carry only the authored expression, never a resolved value shape.
122
+ */
123
+ export function isTypeQueryType(type) {
124
+ return hasKind(type, 'typeQuery');
125
+ }
126
+
111
127
  /**
112
128
  * Checks if a type name is a TypeScript internal symbol name.
113
129
  * Internal names like __object, __type, __function are used by TypeScript