@mintlify/common 1.0.1133 → 1.0.1135
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/dist/mdx/getMDXOptions.js +9 -2
- package/dist/mdx/plugins/remark/remarkMdxExpandExpressions.d.ts +27 -3
- package/dist/mdx/plugins/remark/remarkMdxExpandExpressions.js +263 -8
- package/dist/mdx/snippets/resolveAllImports.d.ts +1 -1
- package/dist/mdx/snippets/resolveAllImports.js +1 -1
- package/dist/mdx/snippets/resolveImport/exportCollision.d.ts +7 -0
- package/dist/mdx/snippets/resolveImport/exportCollision.js +10 -0
- package/dist/mdx/snippets/resolveImport/index.d.ts +3 -1
- package/dist/mdx/snippets/resolveImport/index.js +19 -9
- package/dist/mdx/snippets/resolveImport/resolveComponentWithContent.d.ts +2 -1
- package/dist/mdx/snippets/resolveImport/resolveComponentWithContent.js +9 -5
- package/dist/mintIgnore.js +2 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -21,13 +21,20 @@ export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], md
|
|
|
21
21
|
// `knownComponents`: components the page defines (`export const X = …`, including snippet
|
|
22
22
|
// imports resolved at prebuild). A fragment tree has no exports of its own, so without this the
|
|
23
23
|
// unknown-JSX filter would strip `<X />` inside the fragment.
|
|
24
|
-
const buildPlugins = (extracts, { fragment = false, knownComponents = [], depth = 0, budget = undefined, } = {}) => ({
|
|
24
|
+
const buildPlugins = (extracts, { fragment = false, knownComponents = [], knownConstants = {}, depth = 0, budget = undefined, } = {}) => ({
|
|
25
25
|
remarkPlugins: [
|
|
26
26
|
// first, so `<Snippet file>` is injected on the page and inside every `<MDX>` fragment
|
|
27
27
|
[remarkMdxInjectSnippets, data.snippetTreeMap],
|
|
28
28
|
[
|
|
29
29
|
remarkMdxExpandExpressions,
|
|
30
|
-
{
|
|
30
|
+
{
|
|
31
|
+
buildPlugins,
|
|
32
|
+
extraPlugins: fragmentPlugins,
|
|
33
|
+
knownComponents,
|
|
34
|
+
knownConstants,
|
|
35
|
+
depth,
|
|
36
|
+
budget,
|
|
37
|
+
},
|
|
31
38
|
],
|
|
32
39
|
remarkMdxStyleStringToObject,
|
|
33
40
|
[remarkResolveRelativeLinks, { path: data.path }],
|
|
@@ -10,6 +10,7 @@ export type RemarkMdxExpandExpressionsOptions = {
|
|
|
10
10
|
buildPlugins: (mdxExtracts: MdxExtracts, options: {
|
|
11
11
|
fragment: true;
|
|
12
12
|
knownComponents: string[];
|
|
13
|
+
knownConstants: KnownConstants;
|
|
13
14
|
depth: number;
|
|
14
15
|
budget: FragmentBudget;
|
|
15
16
|
}) => {
|
|
@@ -22,6 +23,8 @@ export type RemarkMdxExpandExpressionsOptions = {
|
|
|
22
23
|
* fragments still know them; the page-level instance derives them from the tree.
|
|
23
24
|
*/
|
|
24
25
|
knownComponents?: string[];
|
|
26
|
+
/** Literal `export const` values of the page (how prebuild writes snippet props); passed down to fragments. */
|
|
27
|
+
knownConstants?: KnownConstants;
|
|
25
28
|
/** Nesting depth of the document being compiled: 0 for the page, +1 per enclosing fragment. */
|
|
26
29
|
depth?: number;
|
|
27
30
|
/** Expansion budget shared by a page compile and every fragment compiled for it. */
|
|
@@ -47,6 +50,14 @@ export type FragmentBudget = {
|
|
|
47
50
|
*/
|
|
48
51
|
export declare const MAX_MDX_FRAGMENT_DEPTH = 8;
|
|
49
52
|
export declare const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
53
|
+
/**
|
|
54
|
+
* A value a page exports and the evaluator can fold; `undefined` is a real value (a snippet prop
|
|
55
|
+
* that was not passed). Objects are only read through member access, never compared.
|
|
56
|
+
*/
|
|
57
|
+
export type KnownConstant = string | number | boolean | null | undefined | {
|
|
58
|
+
readonly [key: string]: KnownConstant;
|
|
59
|
+
};
|
|
60
|
+
export type KnownConstants = Record<string, KnownConstant>;
|
|
50
61
|
/**
|
|
51
62
|
* Compile-time expansion of `<MDX>` inside `{…}` expressions.
|
|
52
63
|
*
|
|
@@ -92,9 +103,22 @@ export declare const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
|
92
103
|
* Table of contents: the fragment pipeline runs `remarkExtractTableOfContents` (it also rewrites
|
|
93
104
|
* headings to `<Heading>`, so it cannot be skipped) into a fragment-scoped extracts object; the
|
|
94
105
|
* headings are stored on the expression node as `data.tableOfContents` and merged into the page
|
|
95
|
-
* TOC in document order by the page-level run.
|
|
96
|
-
*
|
|
97
|
-
* render
|
|
106
|
+
* TOC in document order by the page-level run. A branch whose condition folds to a page constant
|
|
107
|
+
* (`export const variant = "a"`, how prebuild writes a snippet prop) contributes no headings when
|
|
108
|
+
* it cannot render (`markReachability`); an unknown condition keeps every branch. The branch is
|
|
109
|
+
* still compiled, so a wrong verdict can only affect the TOC. A fragment heading that repeats a
|
|
110
|
+
* page heading's title keeps its own id — a render-time reconcile is the planned follow-up.
|
|
111
|
+
*
|
|
112
|
+
* The folding is a deliberately small evaluator, not a JS engine. It knows: identifiers bound by
|
|
113
|
+
* a page-level `export const` to a literal (string, number, boolean, null, `undefined`, a template
|
|
114
|
+
* without expressions) or to an object literal of such values; member access on those objects;
|
|
115
|
+
* `! - +`; `=== !== == != < <= > >=`; `&& || ??`; ternaries. Anything else — a function call, a
|
|
116
|
+
* `let`, a computed key, a comparison between objects — is unknown, and unknown keeps headings.
|
|
98
117
|
*/
|
|
99
118
|
export declare const remarkMdxExpandExpressions: (options: RemarkMdxExpandExpressionsOptions) => (tree: Root) => Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Literal `export const` values of the page. `let`/`var` may be reassigned and a non-literal
|
|
121
|
+
* initializer is not known, so both are left out, which keeps every branch's headings.
|
|
122
|
+
*/
|
|
123
|
+
export declare const getExportedConstants: (tree: Root) => KnownConstants;
|
|
100
124
|
export declare const dedent: (text: string) => string;
|
|
@@ -38,6 +38,7 @@ const JSXParser = Parser.extend(jsx());
|
|
|
38
38
|
*/
|
|
39
39
|
export const MAX_MDX_FRAGMENT_DEPTH = 8;
|
|
40
40
|
export const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
41
|
+
const UNKNOWN = Symbol('unknown');
|
|
41
42
|
/**
|
|
42
43
|
* Compile-time expansion of `<MDX>` inside `{…}` expressions.
|
|
43
44
|
*
|
|
@@ -83,12 +84,20 @@ export const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
|
83
84
|
* Table of contents: the fragment pipeline runs `remarkExtractTableOfContents` (it also rewrites
|
|
84
85
|
* headings to `<Heading>`, so it cannot be skipped) into a fragment-scoped extracts object; the
|
|
85
86
|
* headings are stored on the expression node as `data.tableOfContents` and merged into the page
|
|
86
|
-
* TOC in document order by the page-level run.
|
|
87
|
-
*
|
|
88
|
-
* render
|
|
87
|
+
* TOC in document order by the page-level run. A branch whose condition folds to a page constant
|
|
88
|
+
* (`export const variant = "a"`, how prebuild writes a snippet prop) contributes no headings when
|
|
89
|
+
* it cannot render (`markReachability`); an unknown condition keeps every branch. The branch is
|
|
90
|
+
* still compiled, so a wrong verdict can only affect the TOC. A fragment heading that repeats a
|
|
91
|
+
* page heading's title keeps its own id — a render-time reconcile is the planned follow-up.
|
|
92
|
+
*
|
|
93
|
+
* The folding is a deliberately small evaluator, not a JS engine. It knows: identifiers bound by
|
|
94
|
+
* a page-level `export const` to a literal (string, number, boolean, null, `undefined`, a template
|
|
95
|
+
* without expressions) or to an object literal of such values; member access on those objects;
|
|
96
|
+
* `! - +`; `=== !== == != < <= > >=`; `&& || ??`; ternaries. Anything else — a function call, a
|
|
97
|
+
* `let`, a computed key, a comparison between objects — is unknown, and unknown keeps headings.
|
|
89
98
|
*/
|
|
90
99
|
export const remarkMdxExpandExpressions = (options) => (tree) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
-
var _a, _b, _c;
|
|
100
|
+
var _a, _b, _c, _d, _e;
|
|
92
101
|
const depth = (_a = options.depth) !== null && _a !== void 0 ? _a : 0;
|
|
93
102
|
decodeFragmentCode(tree, depth > 0);
|
|
94
103
|
const expressions = [];
|
|
@@ -110,12 +119,15 @@ export const remarkMdxExpandExpressions = (options) => (tree) => __awaiter(void
|
|
|
110
119
|
const knownComponents = [
|
|
111
120
|
...new Set([...((_b = options.knownComponents) !== null && _b !== void 0 ? _b : []), ...getExportedFunctionNames(tree)]),
|
|
112
121
|
];
|
|
113
|
-
|
|
122
|
+
// ESM inside a fragment is dropped when it is spliced back, so only the page's exports count
|
|
123
|
+
const knownConstants = depth === 0
|
|
124
|
+
? Object.assign(Object.assign({}, ((_c = options.knownConstants) !== null && _c !== void 0 ? _c : {})), getExportedConstants(tree)) : Object.assign({}, ((_d = options.knownConstants) !== null && _d !== void 0 ? _d : {}));
|
|
125
|
+
const budget = (_e = options.budget) !== null && _e !== void 0 ? _e : { remaining: MAX_MDX_FRAGMENTS_PER_PAGE };
|
|
114
126
|
for (const node of expressions) {
|
|
115
|
-
yield expandExpression(node, options, { knownComponents, depth, budget });
|
|
127
|
+
yield expandExpression(node, options, { knownComponents, knownConstants, depth, budget });
|
|
116
128
|
}
|
|
117
129
|
});
|
|
118
|
-
const expandExpression = (node_1, options_1, _a) => __awaiter(void 0, [node_1, options_1, _a], void 0, function* (node, options, { knownComponents, depth, budget }) {
|
|
130
|
+
const expandExpression = (node_1, options_1, _a) => __awaiter(void 0, [node_1, options_1, _a], void 0, function* (node, options, { knownComponents, knownConstants, depth, budget }) {
|
|
119
131
|
var _b, _c, _d, _e;
|
|
120
132
|
if (!node.value.includes('<MDX'))
|
|
121
133
|
return;
|
|
@@ -132,6 +144,7 @@ const expandExpression = (node_1, options_1, _a) => __awaiter(void 0, [node_1, o
|
|
|
132
144
|
const elements = collectMdxElements(expression);
|
|
133
145
|
if (elements.length === 0)
|
|
134
146
|
return;
|
|
147
|
+
const reachability = markReachability(expression, knownConstants);
|
|
135
148
|
const headings = [];
|
|
136
149
|
let changed = false;
|
|
137
150
|
for (const element of elements) {
|
|
@@ -153,6 +166,7 @@ const expandExpression = (node_1, options_1, _a) => __awaiter(void 0, [node_1, o
|
|
|
153
166
|
const { remarkPlugins, rehypePlugins } = options.buildPlugins(fragmentExtracts, {
|
|
154
167
|
fragment: true,
|
|
155
168
|
knownComponents,
|
|
169
|
+
knownConstants,
|
|
156
170
|
depth: depth + 1,
|
|
157
171
|
budget,
|
|
158
172
|
});
|
|
@@ -181,7 +195,7 @@ const expandExpression = (node_1, options_1, _a) => __awaiter(void 0, [node_1, o
|
|
|
181
195
|
? compiled.children
|
|
182
196
|
: [compiled];
|
|
183
197
|
changed = true;
|
|
184
|
-
if (fragmentExtracts.tableOfContents) {
|
|
198
|
+
if (fragmentExtracts.tableOfContents && reachability.get(element) !== false) {
|
|
185
199
|
headings.push(...fragmentExtracts.tableOfContents);
|
|
186
200
|
}
|
|
187
201
|
}
|
|
@@ -234,6 +248,247 @@ const collectMdxElements = (expression) => {
|
|
|
234
248
|
const isMdxElement = (node) => node.type === 'JSXElement' &&
|
|
235
249
|
node.openingElement.name.type === 'JSXIdentifier' &&
|
|
236
250
|
node.openingElement.name.name === 'MDX';
|
|
251
|
+
/**
|
|
252
|
+
* Literal `export const` values of the page. `let`/`var` may be reassigned and a non-literal
|
|
253
|
+
* initializer is not known, so both are left out, which keeps every branch's headings.
|
|
254
|
+
*/
|
|
255
|
+
export const getExportedConstants = (tree) => {
|
|
256
|
+
const constants = {};
|
|
257
|
+
visit(tree, 'mdxjsEsm', (node) => {
|
|
258
|
+
var _a, _b, _c, _d;
|
|
259
|
+
for (const statement of (_c = (_b = (_a = node.data) === null || _a === void 0 ? void 0 : _a.estree) === null || _b === void 0 ? void 0 : _b.body) !== null && _c !== void 0 ? _c : []) {
|
|
260
|
+
if (statement.type !== 'ExportNamedDeclaration')
|
|
261
|
+
continue;
|
|
262
|
+
if (((_d = statement.declaration) === null || _d === void 0 ? void 0 : _d.type) !== 'VariableDeclaration')
|
|
263
|
+
continue;
|
|
264
|
+
if (statement.declaration.kind !== 'const')
|
|
265
|
+
continue;
|
|
266
|
+
for (const { id, init } of statement.declaration.declarations) {
|
|
267
|
+
if (id.type !== 'Identifier' || !init)
|
|
268
|
+
continue;
|
|
269
|
+
const value = evaluate(init, {});
|
|
270
|
+
if (value !== UNKNOWN)
|
|
271
|
+
constants[id.name] = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
return constants;
|
|
276
|
+
};
|
|
277
|
+
const isObject = (value) => typeof value === 'object' && value !== null;
|
|
278
|
+
const compare = (operator, left, right) => {
|
|
279
|
+
// two object literals are distinct references at runtime; never fold their comparison
|
|
280
|
+
if (isObject(left) || isObject(right))
|
|
281
|
+
return UNKNOWN;
|
|
282
|
+
switch (operator) {
|
|
283
|
+
case '===':
|
|
284
|
+
return left === right;
|
|
285
|
+
case '!==':
|
|
286
|
+
return left !== right;
|
|
287
|
+
case '==':
|
|
288
|
+
// eslint-disable-next-line eqeqeq -- mirrors the author's loose comparison
|
|
289
|
+
return left == right;
|
|
290
|
+
case '!=':
|
|
291
|
+
// eslint-disable-next-line eqeqeq -- mirrors the author's loose comparison
|
|
292
|
+
return left != right;
|
|
293
|
+
default:
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
const ordered = (typeof left === 'number' && typeof right === 'number') ||
|
|
297
|
+
(typeof left === 'string' && typeof right === 'string');
|
|
298
|
+
if (!ordered)
|
|
299
|
+
return UNKNOWN;
|
|
300
|
+
const l = left;
|
|
301
|
+
const r = right;
|
|
302
|
+
switch (operator) {
|
|
303
|
+
case '<':
|
|
304
|
+
return l < r;
|
|
305
|
+
case '<=':
|
|
306
|
+
return l <= r;
|
|
307
|
+
case '>':
|
|
308
|
+
return l > r;
|
|
309
|
+
case '>=':
|
|
310
|
+
return l >= r;
|
|
311
|
+
default:
|
|
312
|
+
return UNKNOWN;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
/** Static value of an expression given the page's constants, or UNKNOWN. */
|
|
316
|
+
const evaluate = (node, constants) => {
|
|
317
|
+
// acorn emits this node only with `preserveParens`; the estree types do not list it
|
|
318
|
+
if (node.type === 'ParenthesizedExpression') {
|
|
319
|
+
return evaluate(node.expression, constants);
|
|
320
|
+
}
|
|
321
|
+
switch (node.type) {
|
|
322
|
+
case 'Literal':
|
|
323
|
+
return 'regex' in node || 'bigint' in node ? UNKNOWN : node.value;
|
|
324
|
+
case 'TemplateLiteral':
|
|
325
|
+
return node.expressions.length === 0
|
|
326
|
+
? node.quasis.map((quasi) => { var _a; return (_a = quasi.value.cooked) !== null && _a !== void 0 ? _a : quasi.value.raw; }).join('')
|
|
327
|
+
: UNKNOWN;
|
|
328
|
+
case 'Identifier':
|
|
329
|
+
if (node.name === 'undefined')
|
|
330
|
+
return undefined;
|
|
331
|
+
return Object.hasOwn(constants, node.name) ? constants[node.name] : UNKNOWN;
|
|
332
|
+
case 'ObjectExpression': {
|
|
333
|
+
const object = {};
|
|
334
|
+
for (const property of node.properties) {
|
|
335
|
+
if (property.type !== 'Property' || property.kind !== 'init' || property.computed) {
|
|
336
|
+
return UNKNOWN;
|
|
337
|
+
}
|
|
338
|
+
const key = property.key.type === 'Identifier'
|
|
339
|
+
? property.key.name
|
|
340
|
+
: property.key.type === 'Literal'
|
|
341
|
+
? String(property.key.value)
|
|
342
|
+
: undefined;
|
|
343
|
+
if (key === undefined)
|
|
344
|
+
return UNKNOWN;
|
|
345
|
+
const value = evaluate(property.value, constants);
|
|
346
|
+
if (value === UNKNOWN)
|
|
347
|
+
return UNKNOWN;
|
|
348
|
+
object[key] = value;
|
|
349
|
+
}
|
|
350
|
+
return object;
|
|
351
|
+
}
|
|
352
|
+
case 'MemberExpression': {
|
|
353
|
+
const object = evaluate(node.object, constants);
|
|
354
|
+
if (object === UNKNOWN || !isObject(object))
|
|
355
|
+
return UNKNOWN;
|
|
356
|
+
const key = node.computed
|
|
357
|
+
? evaluate(node.property, constants)
|
|
358
|
+
: node.property.type === 'Identifier'
|
|
359
|
+
? node.property.name
|
|
360
|
+
: UNKNOWN;
|
|
361
|
+
if (typeof key !== 'string' && typeof key !== 'number')
|
|
362
|
+
return UNKNOWN;
|
|
363
|
+
return Object.hasOwn(object, String(key)) ? object[String(key)] : undefined;
|
|
364
|
+
}
|
|
365
|
+
case 'UnaryExpression': {
|
|
366
|
+
const argument = evaluate(node.argument, constants);
|
|
367
|
+
if (argument === UNKNOWN)
|
|
368
|
+
return UNKNOWN;
|
|
369
|
+
if (node.operator === '!')
|
|
370
|
+
return !argument;
|
|
371
|
+
if (typeof argument === 'number' && node.operator === '-')
|
|
372
|
+
return -argument;
|
|
373
|
+
if (typeof argument === 'number' && node.operator === '+')
|
|
374
|
+
return argument;
|
|
375
|
+
return UNKNOWN;
|
|
376
|
+
}
|
|
377
|
+
case 'BinaryExpression': {
|
|
378
|
+
const left = evaluate(node.left, constants);
|
|
379
|
+
const right = evaluate(node.right, constants);
|
|
380
|
+
return left === UNKNOWN || right === UNKNOWN ? UNKNOWN : compare(node.operator, left, right);
|
|
381
|
+
}
|
|
382
|
+
case 'LogicalExpression': {
|
|
383
|
+
const left = evaluate(node.left, constants);
|
|
384
|
+
if (left === UNKNOWN)
|
|
385
|
+
return UNKNOWN;
|
|
386
|
+
if (node.operator === '&&')
|
|
387
|
+
return left ? evaluate(node.right, constants) : left;
|
|
388
|
+
if (node.operator === '||')
|
|
389
|
+
return left ? left : evaluate(node.right, constants);
|
|
390
|
+
return left != null ? left : evaluate(node.right, constants);
|
|
391
|
+
}
|
|
392
|
+
default:
|
|
393
|
+
return UNKNOWN;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* Names a function binds: parameters and every declaration in its body. Over-approximate on
|
|
398
|
+
* purpose (a destructuring key counts too): a shadowed name becomes unknown, which keeps headings.
|
|
399
|
+
*/
|
|
400
|
+
const collectFunctionBindings = (fn) => {
|
|
401
|
+
const names = new Set();
|
|
402
|
+
const addIdentifiers = (pattern) => {
|
|
403
|
+
if (pattern) {
|
|
404
|
+
walk(pattern, {
|
|
405
|
+
enter(jsNode) {
|
|
406
|
+
if (jsNode.type === 'Identifier')
|
|
407
|
+
names.add(jsNode.name);
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
walk(fn, {
|
|
413
|
+
enter(jsNode) {
|
|
414
|
+
if (isFunction(jsNode)) {
|
|
415
|
+
for (const param of jsNode.params)
|
|
416
|
+
addIdentifiers(param);
|
|
417
|
+
if (jsNode.type === 'FunctionDeclaration')
|
|
418
|
+
addIdentifiers(jsNode.id);
|
|
419
|
+
}
|
|
420
|
+
else if (jsNode.type === 'VariableDeclarator')
|
|
421
|
+
addIdentifiers(jsNode.id);
|
|
422
|
+
else if (jsNode.type === 'ClassDeclaration')
|
|
423
|
+
addIdentifiers(jsNode.id);
|
|
424
|
+
else if (jsNode.type === 'CatchClause')
|
|
425
|
+
addIdentifiers(jsNode.param);
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
return names;
|
|
429
|
+
};
|
|
430
|
+
const isFunction = (node) => node.type === 'ArrowFunctionExpression' ||
|
|
431
|
+
node.type === 'FunctionExpression' ||
|
|
432
|
+
node.type === 'FunctionDeclaration';
|
|
433
|
+
const NON_CHILD_KEYS = new Set(['type', 'start', 'end', 'loc', 'range', 'comments']);
|
|
434
|
+
/**
|
|
435
|
+
* Whether each `<MDX>` element in `expression` can render: `false` only when a guarding
|
|
436
|
+
* conditional or logical operator folds to the other branch, `true` on a taken branch or when
|
|
437
|
+
* any guard is unknown. Names a function binds shadow constants inside it.
|
|
438
|
+
*/
|
|
439
|
+
const markReachability = (expression, constants) => {
|
|
440
|
+
const reachability = new Map();
|
|
441
|
+
const visitNode = (value, reachable, scope) => {
|
|
442
|
+
if (!value || typeof value !== 'object')
|
|
443
|
+
return;
|
|
444
|
+
if (Array.isArray(value)) {
|
|
445
|
+
for (const child of value)
|
|
446
|
+
visitNode(child, reachable, scope);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const node = value;
|
|
450
|
+
if (typeof node.type !== 'string')
|
|
451
|
+
return;
|
|
452
|
+
if (isMdxElement(node)) {
|
|
453
|
+
reachability.set(node, reachable);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (node.type === 'ConditionalExpression') {
|
|
457
|
+
const test = evaluate(node.test, scope);
|
|
458
|
+
visitNode(node.consequent, reachable && (test === UNKNOWN || Boolean(test)), scope);
|
|
459
|
+
visitNode(node.alternate, reachable && (test === UNKNOWN || !test), scope);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (node.type === 'LogicalExpression') {
|
|
463
|
+
const left = evaluate(node.left, scope);
|
|
464
|
+
visitNode(node.left, reachable, scope);
|
|
465
|
+
let rightReachable = reachable;
|
|
466
|
+
if (left !== UNKNOWN) {
|
|
467
|
+
if (node.operator === '&&')
|
|
468
|
+
rightReachable = reachable && Boolean(left);
|
|
469
|
+
else if (node.operator === '||')
|
|
470
|
+
rightReachable = reachable && !left;
|
|
471
|
+
else
|
|
472
|
+
rightReachable = reachable && left == null;
|
|
473
|
+
}
|
|
474
|
+
visitNode(node.right, rightReachable, scope);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
let childScope = scope;
|
|
478
|
+
if (isFunction(node)) {
|
|
479
|
+
childScope = Object.assign({}, scope);
|
|
480
|
+
for (const name of collectFunctionBindings(node))
|
|
481
|
+
delete childScope[name];
|
|
482
|
+
}
|
|
483
|
+
for (const key of Object.keys(node)) {
|
|
484
|
+
if (NON_CHILD_KEYS.has(key))
|
|
485
|
+
continue;
|
|
486
|
+
visitNode(node[key], reachable, childScope);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
visitNode(expression, true, constants);
|
|
490
|
+
return reachability;
|
|
491
|
+
};
|
|
237
492
|
export const dedent = (text) => {
|
|
238
493
|
var _a, _b;
|
|
239
494
|
const lines = text.split('\n');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Root } from 'mdast';
|
|
2
2
|
import type { FileType, FileWithImports } from '../../types/mdx/index.js';
|
|
3
3
|
export type ResolveImportsWarning = {
|
|
4
|
-
type: 'invalid-import-path' | 'missing-file' | 'import-resolution-error' | 'default-import-no-default-export';
|
|
4
|
+
type: 'invalid-import-path' | 'missing-file' | 'import-resolution-error' | 'default-import-no-default-export' | 'export-collision';
|
|
5
5
|
message: string;
|
|
6
6
|
};
|
|
7
7
|
export declare const resolveAllImports: (params: {
|
|
@@ -56,7 +56,7 @@ export const resolveAllImports = (params) => __awaiter(void 0, void 0, void 0, f
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
try {
|
|
59
|
-
const contentWithResolvedImport = yield resolveImport(specifier, ast, importedSnippet.tree, exportMap);
|
|
59
|
+
const contentWithResolvedImport = yield resolveImport(specifier, ast, importedSnippet.tree, exportMap, onWarning !== null && onWarning !== void 0 ? onWarning : ((warning) => console.log(warning.message)));
|
|
60
60
|
if (contentWithResolvedImport == undefined) {
|
|
61
61
|
throw new Error('Import failed to resolve');
|
|
62
62
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ExportCollisionWarning = {
|
|
2
|
+
type: 'export-collision';
|
|
3
|
+
message: string;
|
|
4
|
+
};
|
|
5
|
+
export type OnExportCollision = (warning: ExportCollisionWarning) => void;
|
|
6
|
+
/** Same name, different code: the first declaration wins, so say so instead of failing silently. */
|
|
7
|
+
export declare const warnOnExportCollision: (name: string, existing: string, incoming: string, onWarning?: OnExportCollision) => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const normalizeStatement = (statement) => statement.replace(/\s+/g, ' ').trim();
|
|
2
|
+
/** Same name, different code: the first declaration wins, so say so instead of failing silently. */
|
|
3
|
+
export const warnOnExportCollision = (name, existing, incoming, onWarning) => {
|
|
4
|
+
if (normalizeStatement(existing) === normalizeStatement(incoming))
|
|
5
|
+
return;
|
|
6
|
+
onWarning === null || onWarning === void 0 ? void 0 : onWarning({
|
|
7
|
+
type: 'export-collision',
|
|
8
|
+
message: `"${name}" is declared twice with different definitions; the first one is kept. Import one of them under another name: import { ${name} as Other${name} } from '…'`,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Root } from 'mdast';
|
|
2
2
|
import type { ImportSpecifier } from '../../../types/mdx/snippets/import.js';
|
|
3
|
+
import { type OnExportCollision } from './exportCollision.js';
|
|
4
|
+
export { type ExportCollisionWarning, type OnExportCollision } from './exportCollision.js';
|
|
3
5
|
export { findExport } from './findExport.js';
|
|
4
6
|
export { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
|
|
5
7
|
/**
|
|
@@ -9,4 +11,4 @@ export { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
|
|
|
9
11
|
* @param importedFileContent The content of the file we are importing from
|
|
10
12
|
* @returns
|
|
11
13
|
*/
|
|
12
|
-
export declare const resolveImport: (importSpecifier: ImportSpecifier, destinationPageContent: Root, importedFileContent: Root, exportMap: Record<string, string
|
|
14
|
+
export declare const resolveImport: (importSpecifier: ImportSpecifier, destinationPageContent: Root, importedFileContent: Root, exportMap: Record<string, string>, onWarning?: OnExportCollision) => Promise<Root | undefined>;
|
|
@@ -9,6 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { MdxImportSpecifier } from '../../../types/mdx/snippets/import.js';
|
|
11
11
|
import { getAST } from '../../remark.js';
|
|
12
|
+
import { getExportMapFromTree } from '../getExportMap.js';
|
|
13
|
+
import { warnOnExportCollision } from './exportCollision.js';
|
|
12
14
|
import { findExport } from './findExport.js';
|
|
13
15
|
import { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
|
|
14
16
|
import { resolveComponentWithContent } from './resolveComponentWithContent.js';
|
|
@@ -29,25 +31,33 @@ const OTHER_SPECIFIERS = [
|
|
|
29
31
|
* @param importedFileContent The content of the file we are importing from
|
|
30
32
|
* @returns
|
|
31
33
|
*/
|
|
32
|
-
export const resolveImport = (importSpecifier, destinationPageContent, importedFileContent, exportMap) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
export const resolveImport = (importSpecifier, destinationPageContent, importedFileContent, exportMap, onWarning) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
+
var _a, _b;
|
|
33
36
|
if (VALID_SPECIFIERS.includes(importSpecifier.type)) {
|
|
34
37
|
// normal import: import { Component } from '/snippets/Component.mdx' or jsx file
|
|
35
|
-
if ((importSpecifier.renamedName && exportMap[importSpecifier.renamedName]) ||
|
|
36
|
-
exportMap[importSpecifier.name]) {
|
|
37
|
-
// TODO: Handle collisions
|
|
38
|
-
return destinationPageContent;
|
|
39
|
-
}
|
|
40
38
|
// find "Component" in "/snippets/Component.mdx" or jsx files
|
|
41
39
|
const exportContent = findExport(importSpecifier.name, importedFileContent, importSpecifier.renamedName);
|
|
42
40
|
if (exportContent == undefined)
|
|
43
41
|
throw new Error(`Could not find export ${importSpecifier.name} in snippet`);
|
|
44
|
-
//
|
|
45
|
-
|
|
42
|
+
// `import { X as Y }` binds Y; an existing declaration of that name wins
|
|
43
|
+
const localName = (_a = importSpecifier.renamedName) !== null && _a !== void 0 ? _a : importSpecifier.name;
|
|
44
|
+
const existing = exportMap[localName];
|
|
45
|
+
if (existing !== undefined) {
|
|
46
|
+
warnOnExportCollision(localName, existing, exportContent, onWarning);
|
|
47
|
+
return destinationPageContent;
|
|
48
|
+
}
|
|
49
|
+
// Inject "export const Component = `...`" into the top of the destination page and record
|
|
50
|
+
// every name it declares so a later import or inlined snippet dedupes against it
|
|
51
|
+
const injected = getAST(exportContent);
|
|
52
|
+
injectToTopOfFileOfTree(destinationPageContent, injected.children);
|
|
53
|
+
for (const [declared, statement] of Object.entries(getExportMapFromTree(injected))) {
|
|
54
|
+
(_b = exportMap[declared]) !== null && _b !== void 0 ? _b : (exportMap[declared] = statement);
|
|
55
|
+
}
|
|
46
56
|
return destinationPageContent;
|
|
47
57
|
}
|
|
48
58
|
else if (OTHER_SPECIFIERS.includes(importSpecifier.type)) {
|
|
49
59
|
// default export: import DefaultComponent from '/snippets/DefaultComponent.mdx'
|
|
50
60
|
// replace every instance of DefaultComponent with the content of the imported file
|
|
51
|
-
return yield resolveComponentWithContent(destinationPageContent, importSpecifier.name, importedFileContent, exportMap);
|
|
61
|
+
return yield resolveComponentWithContent(destinationPageContent, importSpecifier.name, importedFileContent, exportMap, onWarning);
|
|
52
62
|
}
|
|
53
63
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Root } from 'mdast';
|
|
2
|
+
import { type OnExportCollision } from './exportCollision.js';
|
|
2
3
|
/**
|
|
3
4
|
*
|
|
4
5
|
* @param tree The tree to inject into
|
|
@@ -6,4 +7,4 @@ import type { Root } from 'mdast';
|
|
|
6
7
|
* @param snippet The snippet to inject in place of the component
|
|
7
8
|
* @param exportMap The export map of the snippet
|
|
8
9
|
*/
|
|
9
|
-
export declare const resolveComponentWithContent: (tree: Root, componentName: string, snippet: Root, exportMap: Record<string, string
|
|
10
|
+
export declare const resolveComponentWithContent: (tree: Root, componentName: string, snippet: Root, exportMap: Record<string, string>, onWarning?: OnExportCollision) => Promise<Root>;
|
|
@@ -16,6 +16,7 @@ import { removeFrontmatterFromAST } from '../../astUtils.js';
|
|
|
16
16
|
import { getAST } from '../../remark.js';
|
|
17
17
|
import { createUniqueVariableName, isMdxJsxFlowElement } from '../../utils.js';
|
|
18
18
|
import { findAndRemoveExports } from '../findAndRemoveExports.js';
|
|
19
|
+
import { warnOnExportCollision } from './exportCollision.js';
|
|
19
20
|
import { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
|
|
20
21
|
/**
|
|
21
22
|
*
|
|
@@ -24,7 +25,7 @@ import { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
|
|
|
24
25
|
* @param snippet The snippet to inject in place of the component
|
|
25
26
|
* @param exportMap The export map of the snippet
|
|
26
27
|
*/
|
|
27
|
-
export const resolveComponentWithContent = (tree, componentName, snippet, exportMap) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
|
+
export const resolveComponentWithContent = (tree, componentName, snippet, exportMap, onWarning) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
29
|
const clonedSnippet = structuredClone(snippet);
|
|
29
30
|
removeFrontmatterFromAST(clonedSnippet);
|
|
30
31
|
const snippetExportMap = findAndRemoveExports(clonedSnippet);
|
|
@@ -51,7 +52,7 @@ export const resolveComponentWithContent = (tree, componentName, snippet, export
|
|
|
51
52
|
treeToInject = treeToInjectClone;
|
|
52
53
|
}
|
|
53
54
|
});
|
|
54
|
-
reinsertExports(tree, exportMap, snippetExportMap);
|
|
55
|
+
reinsertExports(tree, exportMap, snippetExportMap, onWarning);
|
|
55
56
|
return tree;
|
|
56
57
|
});
|
|
57
58
|
/**
|
|
@@ -272,15 +273,18 @@ const replaceCodePlaceholdersWithProps = (tree, propValues) => {
|
|
|
272
273
|
});
|
|
273
274
|
});
|
|
274
275
|
};
|
|
275
|
-
const reinsertExports = (content, exportMap, snippetExportMap) => {
|
|
276
|
+
const reinsertExports = (content, exportMap, snippetExportMap, onWarning) => {
|
|
276
277
|
const nodesToInject = [];
|
|
277
278
|
for (const [key, value] of Object.entries(snippetExportMap)) {
|
|
278
|
-
|
|
279
|
-
if (
|
|
279
|
+
const existing = exportMap[key];
|
|
280
|
+
if (existing == undefined) {
|
|
280
281
|
const exportAST = getAST(value);
|
|
281
282
|
nodesToInject.push(...exportAST.children);
|
|
282
283
|
exportMap[key] = value;
|
|
283
284
|
}
|
|
285
|
+
else {
|
|
286
|
+
warnOnExportCollision(key, existing, value, onWarning);
|
|
287
|
+
}
|
|
284
288
|
}
|
|
285
289
|
if (nodesToInject.length > 0) {
|
|
286
290
|
injectToTopOfFileOfTree(content, nodesToInject);
|