@mintlify/common 1.0.1135 → 1.0.1136
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.
|
@@ -39,6 +39,15 @@ const JSXParser = Parser.extend(jsx());
|
|
|
39
39
|
export const MAX_MDX_FRAGMENT_DEPTH = 8;
|
|
40
40
|
export const MAX_MDX_FRAGMENTS_PER_PAGE = 500;
|
|
41
41
|
const UNKNOWN = Symbol('unknown');
|
|
42
|
+
/**
|
|
43
|
+
* Bounds on the work reachability analysis may spend on a single expression (nodes visited across
|
|
44
|
+
* binding collection, the reachability walk and constant folding, and recursion depth). The
|
|
45
|
+
* analysis only prunes headings from the table of contents, so exceeding a cap fails open:
|
|
46
|
+
* analysis stops and every remaining `<MDX>` element keeps its headings.
|
|
47
|
+
*/
|
|
48
|
+
const MAX_ANALYSIS_NODES = 50000;
|
|
49
|
+
const MAX_ANALYSIS_DEPTH = 1000;
|
|
50
|
+
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
42
51
|
/**
|
|
43
52
|
* Compile-time expansion of `<MDX>` inside `{…}` expressions.
|
|
44
53
|
*
|
|
@@ -254,6 +263,7 @@ const isMdxElement = (node) => node.type === 'JSXElement' &&
|
|
|
254
263
|
*/
|
|
255
264
|
export const getExportedConstants = (tree) => {
|
|
256
265
|
const constants = {};
|
|
266
|
+
const budget = { remaining: MAX_ANALYSIS_NODES };
|
|
257
267
|
visit(tree, 'mdxjsEsm', (node) => {
|
|
258
268
|
var _a, _b, _c, _d;
|
|
259
269
|
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 : []) {
|
|
@@ -264,9 +274,9 @@ export const getExportedConstants = (tree) => {
|
|
|
264
274
|
if (statement.declaration.kind !== 'const')
|
|
265
275
|
continue;
|
|
266
276
|
for (const { id, init } of statement.declaration.declarations) {
|
|
267
|
-
if (id.type !== 'Identifier' || !init)
|
|
277
|
+
if (id.type !== 'Identifier' || !init || UNSAFE_KEYS.has(id.name))
|
|
268
278
|
continue;
|
|
269
|
-
const value = evaluate(init, {});
|
|
279
|
+
const value = evaluate(init, {}, budget);
|
|
270
280
|
if (value !== UNKNOWN)
|
|
271
281
|
constants[id.name] = value;
|
|
272
282
|
}
|
|
@@ -313,10 +323,13 @@ const compare = (operator, left, right) => {
|
|
|
313
323
|
}
|
|
314
324
|
};
|
|
315
325
|
/** Static value of an expression given the page's constants, or UNKNOWN. */
|
|
316
|
-
const evaluate = (node, constants) => {
|
|
326
|
+
const evaluate = (node, constants, budget, depth = 0) => {
|
|
327
|
+
if (budget.remaining <= 0 || depth >= MAX_ANALYSIS_DEPTH)
|
|
328
|
+
return UNKNOWN;
|
|
329
|
+
budget.remaining -= 1;
|
|
317
330
|
// acorn emits this node only with `preserveParens`; the estree types do not list it
|
|
318
331
|
if (node.type === 'ParenthesizedExpression') {
|
|
319
|
-
return evaluate(node.expression, constants);
|
|
332
|
+
return evaluate(node.expression, constants, budget, depth + 1);
|
|
320
333
|
}
|
|
321
334
|
switch (node.type) {
|
|
322
335
|
case 'Literal':
|
|
@@ -330,7 +343,7 @@ const evaluate = (node, constants) => {
|
|
|
330
343
|
return undefined;
|
|
331
344
|
return Object.hasOwn(constants, node.name) ? constants[node.name] : UNKNOWN;
|
|
332
345
|
case 'ObjectExpression': {
|
|
333
|
-
const object =
|
|
346
|
+
const object = Object.create(null);
|
|
334
347
|
for (const property of node.properties) {
|
|
335
348
|
if (property.type !== 'Property' || property.kind !== 'init' || property.computed) {
|
|
336
349
|
return UNKNOWN;
|
|
@@ -340,9 +353,9 @@ const evaluate = (node, constants) => {
|
|
|
340
353
|
: property.key.type === 'Literal'
|
|
341
354
|
? String(property.key.value)
|
|
342
355
|
: undefined;
|
|
343
|
-
if (key === undefined)
|
|
356
|
+
if (key === undefined || UNSAFE_KEYS.has(key))
|
|
344
357
|
return UNKNOWN;
|
|
345
|
-
const value = evaluate(property.value, constants);
|
|
358
|
+
const value = evaluate(property.value, constants, budget, depth + 1);
|
|
346
359
|
if (value === UNKNOWN)
|
|
347
360
|
return UNKNOWN;
|
|
348
361
|
object[key] = value;
|
|
@@ -350,11 +363,11 @@ const evaluate = (node, constants) => {
|
|
|
350
363
|
return object;
|
|
351
364
|
}
|
|
352
365
|
case 'MemberExpression': {
|
|
353
|
-
const object = evaluate(node.object, constants);
|
|
366
|
+
const object = evaluate(node.object, constants, budget, depth + 1);
|
|
354
367
|
if (object === UNKNOWN || !isObject(object))
|
|
355
368
|
return UNKNOWN;
|
|
356
369
|
const key = node.computed
|
|
357
|
-
? evaluate(node.property, constants)
|
|
370
|
+
? evaluate(node.property, constants, budget, depth + 1)
|
|
358
371
|
: node.property.type === 'Identifier'
|
|
359
372
|
? node.property.name
|
|
360
373
|
: UNKNOWN;
|
|
@@ -363,7 +376,7 @@ const evaluate = (node, constants) => {
|
|
|
363
376
|
return Object.hasOwn(object, String(key)) ? object[String(key)] : undefined;
|
|
364
377
|
}
|
|
365
378
|
case 'UnaryExpression': {
|
|
366
|
-
const argument = evaluate(node.argument, constants);
|
|
379
|
+
const argument = evaluate(node.argument, constants, budget, depth + 1);
|
|
367
380
|
if (argument === UNKNOWN)
|
|
368
381
|
return UNKNOWN;
|
|
369
382
|
if (node.operator === '!')
|
|
@@ -375,58 +388,64 @@ const evaluate = (node, constants) => {
|
|
|
375
388
|
return UNKNOWN;
|
|
376
389
|
}
|
|
377
390
|
case 'BinaryExpression': {
|
|
378
|
-
const left = evaluate(node.left, constants);
|
|
379
|
-
const right = evaluate(node.right, constants);
|
|
391
|
+
const left = evaluate(node.left, constants, budget, depth + 1);
|
|
392
|
+
const right = evaluate(node.right, constants, budget, depth + 1);
|
|
380
393
|
return left === UNKNOWN || right === UNKNOWN ? UNKNOWN : compare(node.operator, left, right);
|
|
381
394
|
}
|
|
382
395
|
case 'LogicalExpression': {
|
|
383
|
-
const left = evaluate(node.left, constants);
|
|
396
|
+
const left = evaluate(node.left, constants, budget, depth + 1);
|
|
384
397
|
if (left === UNKNOWN)
|
|
385
398
|
return UNKNOWN;
|
|
386
399
|
if (node.operator === '&&')
|
|
387
|
-
return left ? evaluate(node.right, constants) : left;
|
|
400
|
+
return left ? evaluate(node.right, constants, budget, depth + 1) : left;
|
|
388
401
|
if (node.operator === '||')
|
|
389
|
-
return left ? left : evaluate(node.right, constants);
|
|
390
|
-
return left != null ? left : evaluate(node.right, constants);
|
|
402
|
+
return left ? left : evaluate(node.right, constants, budget, depth + 1);
|
|
403
|
+
return left != null ? left : evaluate(node.right, constants, budget, depth + 1);
|
|
391
404
|
}
|
|
392
405
|
default:
|
|
393
406
|
return UNKNOWN;
|
|
394
407
|
}
|
|
395
408
|
};
|
|
396
409
|
/**
|
|
397
|
-
* Names
|
|
398
|
-
* purpose (a destructuring key counts too
|
|
410
|
+
* Names bound anywhere in the expression: parameters and every declaration. Collected in a single
|
|
411
|
+
* pass and over-approximate on purpose (a destructuring key counts too, and a name one function
|
|
412
|
+
* binds shadows constants inside every function): a shadowed name becomes unknown, which keeps
|
|
413
|
+
* headings. Returns null when the node budget runs out.
|
|
399
414
|
*/
|
|
400
|
-
const
|
|
415
|
+
const collectBindingNames = (root, budget) => {
|
|
401
416
|
const names = new Set();
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
417
|
+
let capped = false;
|
|
418
|
+
const visitValue = (value, inBinding, depth) => {
|
|
419
|
+
if (capped || !value || typeof value !== 'object')
|
|
420
|
+
return;
|
|
421
|
+
if (Array.isArray(value)) {
|
|
422
|
+
for (const child of value)
|
|
423
|
+
visitValue(child, inBinding, depth);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const node = value;
|
|
427
|
+
if (typeof node.type !== 'string')
|
|
428
|
+
return;
|
|
429
|
+
if (budget.remaining <= 0 || depth >= MAX_ANALYSIS_DEPTH) {
|
|
430
|
+
capped = true;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
budget.remaining -= 1;
|
|
434
|
+
if (inBinding && node.type === 'Identifier')
|
|
435
|
+
names.add(node.name);
|
|
436
|
+
for (const key of Object.keys(node)) {
|
|
437
|
+
if (NON_CHILD_KEYS.has(key))
|
|
438
|
+
continue;
|
|
439
|
+
visitValue(node[key], inBinding || isBindingKey(node, key), depth + 1);
|
|
410
440
|
}
|
|
411
441
|
};
|
|
412
|
-
|
|
413
|
-
|
|
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;
|
|
442
|
+
visitValue(root, false, 0);
|
|
443
|
+
return capped ? null : names;
|
|
429
444
|
};
|
|
445
|
+
const isBindingKey = (node, key) => (isFunction(node) && (key === 'params' || key === 'id')) ||
|
|
446
|
+
(node.type === 'VariableDeclarator' && key === 'id') ||
|
|
447
|
+
(node.type === 'ClassDeclaration' && key === 'id') ||
|
|
448
|
+
(node.type === 'CatchClause' && key === 'param');
|
|
430
449
|
const isFunction = (node) => node.type === 'ArrowFunctionExpression' ||
|
|
431
450
|
node.type === 'FunctionExpression' ||
|
|
432
451
|
node.type === 'FunctionDeclaration';
|
|
@@ -434,34 +453,47 @@ const NON_CHILD_KEYS = new Set(['type', 'start', 'end', 'loc', 'range', 'comment
|
|
|
434
453
|
/**
|
|
435
454
|
* Whether each `<MDX>` element in `expression` can render: `false` only when a guarding
|
|
436
455
|
* conditional or logical operator folds to the other branch, `true` on a taken branch or when
|
|
437
|
-
* any guard is unknown.
|
|
456
|
+
* any guard is unknown. A name bound anywhere in the expression shadows constants inside every
|
|
457
|
+
* function (collected once, so the walk stays linear). Total work is capped by a shared node
|
|
458
|
+
* budget and a depth limit; past either cap the walk stops and every element not yet marked
|
|
459
|
+
* stays reachable.
|
|
438
460
|
*/
|
|
439
461
|
const markReachability = (expression, constants) => {
|
|
440
462
|
const reachability = new Map();
|
|
441
|
-
const
|
|
463
|
+
const budget = { remaining: MAX_ANALYSIS_NODES };
|
|
464
|
+
const bindings = collectBindingNames(expression, budget);
|
|
465
|
+
if (bindings === null)
|
|
466
|
+
return reachability;
|
|
467
|
+
const functionScope = Object.assign({}, constants);
|
|
468
|
+
for (const name of bindings)
|
|
469
|
+
delete functionScope[name];
|
|
470
|
+
const visitNode = (value, reachable, scope, depth) => {
|
|
442
471
|
if (!value || typeof value !== 'object')
|
|
443
472
|
return;
|
|
444
473
|
if (Array.isArray(value)) {
|
|
445
474
|
for (const child of value)
|
|
446
|
-
visitNode(child, reachable, scope);
|
|
475
|
+
visitNode(child, reachable, scope, depth);
|
|
447
476
|
return;
|
|
448
477
|
}
|
|
449
478
|
const node = value;
|
|
450
479
|
if (typeof node.type !== 'string')
|
|
451
480
|
return;
|
|
481
|
+
if (budget.remaining <= 0 || depth >= MAX_ANALYSIS_DEPTH)
|
|
482
|
+
return;
|
|
483
|
+
budget.remaining -= 1;
|
|
452
484
|
if (isMdxElement(node)) {
|
|
453
485
|
reachability.set(node, reachable);
|
|
454
486
|
return;
|
|
455
487
|
}
|
|
456
488
|
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);
|
|
489
|
+
const test = evaluate(node.test, scope, budget);
|
|
490
|
+
visitNode(node.consequent, reachable && (test === UNKNOWN || Boolean(test)), scope, depth + 1);
|
|
491
|
+
visitNode(node.alternate, reachable && (test === UNKNOWN || !test), scope, depth + 1);
|
|
460
492
|
return;
|
|
461
493
|
}
|
|
462
494
|
if (node.type === 'LogicalExpression') {
|
|
463
|
-
const left = evaluate(node.left, scope);
|
|
464
|
-
visitNode(node.left, reachable, scope);
|
|
495
|
+
const left = evaluate(node.left, scope, budget);
|
|
496
|
+
visitNode(node.left, reachable, scope, depth + 1);
|
|
465
497
|
let rightReachable = reachable;
|
|
466
498
|
if (left !== UNKNOWN) {
|
|
467
499
|
if (node.operator === '&&')
|
|
@@ -471,22 +503,17 @@ const markReachability = (expression, constants) => {
|
|
|
471
503
|
else
|
|
472
504
|
rightReachable = reachable && left == null;
|
|
473
505
|
}
|
|
474
|
-
visitNode(node.right, rightReachable, scope);
|
|
506
|
+
visitNode(node.right, rightReachable, scope, depth + 1);
|
|
475
507
|
return;
|
|
476
508
|
}
|
|
477
|
-
|
|
478
|
-
if (isFunction(node)) {
|
|
479
|
-
childScope = Object.assign({}, scope);
|
|
480
|
-
for (const name of collectFunctionBindings(node))
|
|
481
|
-
delete childScope[name];
|
|
482
|
-
}
|
|
509
|
+
const childScope = isFunction(node) ? functionScope : scope;
|
|
483
510
|
for (const key of Object.keys(node)) {
|
|
484
511
|
if (NON_CHILD_KEYS.has(key))
|
|
485
512
|
continue;
|
|
486
|
-
visitNode(node[key], reachable, childScope);
|
|
513
|
+
visitNode(node[key], reachable, childScope, depth + 1);
|
|
487
514
|
}
|
|
488
515
|
};
|
|
489
|
-
visitNode(expression, true, constants);
|
|
516
|
+
visitNode(expression, true, constants, 0);
|
|
490
517
|
return reachability;
|
|
491
518
|
};
|
|
492
519
|
export const dedent = (text) => {
|