@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 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.
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 collectFunctionBindings = (fn) => {
415
+ const collectBindingNames = (root, budget) => {
401
416
  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
- });
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
- 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;
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. Names a function binds shadow constants inside it.
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 visitNode = (value, reachable, scope) => {
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
- let childScope = scope;
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) => {