@readme/markdown 14.10.3 → 14.11.0

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.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
3
+ *
4
+ * `@param` content
5
+ * `@returns` Content with JSX comments removed
6
+ * `@example`
7
+ * ```typescript
8
+ * removeJSXComments('Text {/* comment *\/} more text') // => 'Text more text'
9
+ * ```
10
+ */
11
+ export declare function removeJSXComments(content: string): string;
@@ -0,0 +1,13 @@
1
+ import type { ImportDeclaration } from 'estree';
2
+ export type ModuleRegistry = Record<string, unknown>;
3
+ export declare const defaultModuleRegistry: ModuleRegistry;
4
+ /**
5
+ * Turn `import` declarations into a flat map of binding → value.
6
+ *
7
+ * A "binding" is a mapping of a local name to the value of it in the module exports
8
+ * Example: `import { useState } from 'react'` yields `{ useState: React.useState }`.
9
+ *
10
+ * Unsupported specifiers are skipped with a warning rather than throwing, so a
11
+ * single unknown import can't break the rest of the document.
12
+ */
13
+ export declare const collectImportValues: (importDeclarations: ImportDeclaration[], registry?: ModuleRegistry) => Record<string, unknown>;
@@ -120921,12 +120921,60 @@ function restoreSnakeCase(placeholderName, mapping) {
120921
120921
  return matchingKey ? mapping[matchingKey] : placeholderName;
120922
120922
  }
120923
120923
 
120924
+ ;// ./processor/transform/mdxish/resolve-esm-imports.ts
120925
+
120926
+ // We provide React as a default module so that components can use hooks
120927
+ // and it's a common use case. Add other common libraries here.
120928
+ const defaultModuleRegistry = { react: (external_react_default()) };
120929
+ const isRecord = (value) => typeof value === 'object' && value !== null;
120930
+ // Get the value of a named export from a module
120931
+ const getModuleExport = (module, importedName) => isRecord(module) && importedName in module ? module[importedName] : undefined;
120932
+ /**
120933
+ * Turn `import` declarations into a flat map of binding → value.
120934
+ *
120935
+ * A "binding" is a mapping of a local name to the value of it in the module exports
120936
+ * Example: `import { useState } from 'react'` yields `{ useState: React.useState }`.
120937
+ *
120938
+ * Unsupported specifiers are skipped with a warning rather than throwing, so a
120939
+ * single unknown import can't break the rest of the document.
120940
+ */
120941
+ const collectImportValues = (importDeclarations, registry = defaultModuleRegistry) => {
120942
+ const importValues = {};
120943
+ importDeclarations.forEach(declaration => {
120944
+ const libraryName = declaration.source.value;
120945
+ if (typeof libraryName !== 'string')
120946
+ return;
120947
+ if (!(libraryName in registry)) {
120948
+ // eslint-disable-next-line no-console
120949
+ console.warn(`[WARNING] Cannot resolve import "${libraryName}"; it is not a supported library.`);
120950
+ return;
120951
+ }
120952
+ const module = registry[libraryName];
120953
+ declaration.specifiers.forEach(spec => {
120954
+ // Default (`import React`) and namespace (`import * as React`) both bind
120955
+ // the whole module under the local name.
120956
+ if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
120957
+ importValues[spec.local.name] = module;
120958
+ }
120959
+ else if (spec.type === 'ImportSpecifier') {
120960
+ // Named imports (e.g. `import { useState } from 'react'`, useState is the import name)
120961
+ const importName = spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value;
120962
+ if (typeof importName === 'string') {
120963
+ importValues[spec.local.name] = getModuleExport(module, importName);
120964
+ }
120965
+ }
120966
+ });
120967
+ });
120968
+ return importValues;
120969
+ };
120970
+
120924
120971
  ;// ./processor/transform/mdxish/evaluate-exports.ts
120925
120972
 
120926
120973
 
120927
120974
 
120928
120975
 
120929
120976
 
120977
+
120930
120978
  /**
120931
120979
  * Recursively extract all identifier names introduced by a binding pattern.
120932
120980
  * Handles destructuring (`{ a, b }`, `[x, y]`), rest elements (`...rest`),
@@ -120974,6 +121022,7 @@ const collectExportNames = (declaration) => {
120974
121022
  const evaluateExports = () => (tree, file) => {
120975
121023
  const programBody = [];
120976
121024
  const exportNames = [];
121025
+ const importDeclarations = [];
120977
121026
  const nodesToRemove = [];
120978
121027
  lib_visit(tree, isMDXEsm, (node, index, parent) => {
120979
121028
  if (parent && typeof index === 'number') {
@@ -120987,6 +121036,10 @@ const evaluateExports = () => (tree, file) => {
120987
121036
  // handled the same as a named export — the inner declaration carries the
120988
121037
  // binding name
120989
121038
  estreeBody.forEach(statement => {
121039
+ if (statement.type === 'ImportDeclaration') {
121040
+ importDeclarations.push(statement);
121041
+ return;
121042
+ }
120990
121043
  let declaration = null;
120991
121044
  if (statement.type === 'ExportNamedDeclaration' && statement.declaration) {
120992
121045
  declaration = statement.declaration;
@@ -121014,9 +121067,11 @@ const evaluateExports = () => (tree, file) => {
121014
121067
  const program = { type: 'Program', sourceType: 'module', body: programBody };
121015
121068
  buildJsx(program, { runtime: 'classic', pragma: 'React.createElement', pragmaFrag: 'React.Fragment' });
121016
121069
  const { value: source } = toJs(program);
121070
+ // Make sure import values are available in the scope
121071
+ const importValues = collectImportValues(importDeclarations);
121017
121072
  // Evaluate and build on the expression source
121018
121073
  // Use react to compile JSX codes
121019
- const evaluatedExports = evaluate(`(() => { ${source}\nreturn { ${exportNames.join(', ')} }; })()`, { React: (external_react_default()) });
121074
+ const evaluatedExports = evaluate(`(() => { ${source}\nreturn { ${exportNames.join(', ')} }; })()`, { React: (external_react_default()), ...importValues });
121020
121075
  Object.assign(scope, evaluatedExports);
121021
121076
  }
121022
121077
  catch (error) {
@@ -124388,214 +124443,21 @@ const normalizeMdxJsxNodes = () => tree => {
124388
124443
  */
124389
124444
  const JSX_COMMENT_REGEX = /\{\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\/\}/g;
124390
124445
 
124391
- ;// ./processor/transform/mdxish/preprocess-jsx-expressions.ts
124392
-
124446
+ ;// ./processor/transform/mdxish/remove-jsx-comments.ts
124393
124447
 
124394
124448
  /**
124395
124449
  * Removes JSX-style comments (e.g., { /* comment *\/ }) from content.
124396
124450
  *
124397
- * @param content
124398
- * @returns Content with JSX comments removed
124399
- * @example
124451
+ * `@param` content
124452
+ * `@returns` Content with JSX comments removed
124453
+ * `@example`
124400
124454
  * ```typescript
124401
- * removeJSXComments('Text { /* comment *\/ } more text')
124402
- * // Returns: 'Text more text'
124455
+ * removeJSXComments('Text {/* comment *\/} more text') // => 'Text more text'
124403
124456
  * ```
124404
124457
  */
124405
124458
  function removeJSXComments(content) {
124406
124459
  return content.replace(JSX_COMMENT_REGEX, '');
124407
124460
  }
124408
- const HTML_ELEM_PLACEHOLDER_PREFIX = '___MDXISH_HTML_ELEM_';
124409
- const HTML_ELEM_PLACEHOLDER = new RegExp(`${HTML_ELEM_PLACEHOLDER_PREFIX}(\\d+)___`, 'g');
124410
- // Matches an HTML element that starts at a line boundary and ends at a line boundary.
124411
- // Allows optional leading indentation and lazily matches until the same closing tag.
124412
- const BLOCK_HTML_RE = /(?<=^|\n)[ \t]*<([a-z][a-zA-Z0-9]*)(?:\s[^>]*)?>[\s\S]*?<\/\1>[ \t]*(?=\n|$)/g;
124413
- /**
124414
- * Hides line-anchored HTML elements from the brace-escaping pass so we don't leak `\{`
124415
- * into rendered output (rehypeRaw renders the `\` literally, e.g. `<div>{foo</div>`).
124416
- *
124417
- * One carve-out: if an interior line at column 0 has bare text containing `{`, mdxish
124418
- * parses that line as a paragraph and the mdxExpression step would throw without an
124419
- * escape — so we leave that case to the brace balancer.
124420
- */
124421
- function protectHTMLElements(content) {
124422
- const htmlElements = [];
124423
- const protectedContent = content.replace(BLOCK_HTML_RE, match => {
124424
- // Look at the lines between the open and close tags. If any of them starts
124425
- // at column 0 with bare text (not whitespace, not another tag) and contains
124426
- // `{`, mdxish will parse that line as a paragraph and the brace as an MDX
124427
- // expression, which would throw an error. So we let the brace balancer escape it.
124428
- // Otherwise, we need to extract the sequence to protect it from the brace escaping.
124429
- const interior = match.split('\n').slice(1, -1);
124430
- const hazard = interior.some(line => line.length > 0 && line[0] !== ' ' && line[0] !== '\t' && line[0] !== '<' && line.includes('{'));
124431
- if (hazard)
124432
- return match;
124433
- htmlElements.push(match);
124434
- return `${HTML_ELEM_PLACEHOLDER_PREFIX}${htmlElements.length - 1}___`;
124435
- });
124436
- return { htmlElements, protectedContent };
124437
- }
124438
- function restoreHTMLElements(content, htmlElements) {
124439
- if (htmlElements.length === 0)
124440
- return content;
124441
- return content.replace(HTML_ELEM_PLACEHOLDER, (_m, idx) => htmlElements[parseInt(idx, 10)]);
124442
- }
124443
- const ESM_DECLARATION_START = /^(?:export|import)\b/;
124444
- /**
124445
- * Whether a line begins a top-level `export`/`import` declaration. Its braces
124446
- * are valid JS owned by the mdxjsEsm tokenizer (which tolerates blank lines),
124447
- * so escaping them would corrupt the source and break acorn parsing.
124448
- */
124449
- function startsEsmDeclaration(chars, lineStart) {
124450
- return ESM_DECLARATION_START.test(chars.slice(lineStart, lineStart + 7).join(''));
124451
- }
124452
- function isBlankLine(chars, lineStart) {
124453
- for (let i = lineStart; i < chars.length; i += 1) {
124454
- if (chars[i] === '\n')
124455
- return true;
124456
- if (chars[i] !== ' ' && chars[i] !== '\t')
124457
- return false;
124458
- }
124459
- return true;
124460
- }
124461
- /**
124462
- * Escapes unbalanced and paragraph-spanning braces so MDX doesn't trip on them.
124463
- */
124464
- function escapeProblematicBraces(content) {
124465
- const { htmlElements, protectedContent } = protectHTMLElements(content);
124466
- let strDelim = null;
124467
- let strEscaped = false;
124468
- // Track position of last newline (outside strings) to detect blank lines
124469
- // -2 means no recent newline
124470
- let lastNewlinePos = -2;
124471
- // Character state machine trackers
124472
- const toEscape = new Set();
124473
- // Convert to array of Unicode code points so that emojis and multi-byte characters are correctly tracked
124474
- const chars = Array.from(protectedContent);
124475
- const openStack = [];
124476
- // Whether the current top-level statement is an `export`/`import` declaration.
124477
- let insideEsmDeclaration = false;
124478
- for (let i = 0; i < chars.length; i += 1) {
124479
- const ch = chars[i];
124480
- // At a top-level (brace depth 0) line start, decide whether we're inside an
124481
- // ESM declaration. The flag persists across the declaration's own lines.
124482
- if (openStack.length === 0 && (i === 0 || chars[i - 1] === '\n')) {
124483
- if (startsEsmDeclaration(chars, i))
124484
- insideEsmDeclaration = true;
124485
- else if (isBlankLine(chars, i))
124486
- insideEsmDeclaration = false;
124487
- }
124488
- // Track string delimiters inside expressions to ignore braces within them
124489
- if (openStack.length > 0) {
124490
- if (strDelim) {
124491
- if (strEscaped)
124492
- strEscaped = false;
124493
- else if (ch === '\\')
124494
- strEscaped = true;
124495
- else if (ch === strDelim)
124496
- strDelim = null;
124497
- // eslint-disable-next-line no-continue
124498
- continue;
124499
- }
124500
- if (ch === '"' || ch === "'" || ch === '`') {
124501
- strDelim = ch;
124502
- // eslint-disable-next-line no-continue
124503
- continue;
124504
- }
124505
- if (ch === '\n') {
124506
- if (lastNewlinePos >= 0) {
124507
- const between = chars.slice(lastNewlinePos + 1, i).join('');
124508
- if (/^[ \t]*$/.test(between)) {
124509
- openStack.forEach(entry => { entry.hasBlankLine = true; });
124510
- }
124511
- }
124512
- lastNewlinePos = i;
124513
- }
124514
- }
124515
- // Skip already-escaped braces (odd run of preceding backslashes).
124516
- if (ch === '{' || ch === '}') {
124517
- let bs = 0;
124518
- for (let j = i - 1; j >= 0 && chars[j] === '\\'; j -= 1)
124519
- bs += 1;
124520
- if (bs % 2 === 1) {
124521
- // eslint-disable-next-line no-continue
124522
- continue;
124523
- }
124524
- }
124525
- if (ch === '{') {
124526
- // `=` (after whitespace) before `{` ⇒ JSX attribute expression. The
124527
- // mdxComponent tokenizer captures the whole component, so blank lines
124528
- // inside attribute values are harmless. Nested `{` inherits the flag.
124529
- let isAttrExpr = false;
124530
- for (let j = i - 1; j >= 0; j -= 1) {
124531
- const pc = chars[j];
124532
- if (pc === '=') {
124533
- isAttrExpr = true;
124534
- break;
124535
- }
124536
- if (pc !== ' ' && pc !== '\t')
124537
- break;
124538
- }
124539
- // Nested `{ ... }` inside an attribute value (e.g. `data={[{ ... }]}` or
124540
- // `data={{ a: { b: 1 } }}`) must inherit the same exemption; only the
124541
- // outer `{` is directly after `=`.
124542
- if (!isAttrExpr && openStack.length > 0 && openStack[openStack.length - 1].isAttrExpr) {
124543
- isAttrExpr = true;
124544
- }
124545
- openStack.push({ pos: i, hasBlankLine: false, isAttrExpr, isEsmDeclaration: insideEsmDeclaration });
124546
- lastNewlinePos = -2;
124547
- }
124548
- else if (ch === '}') {
124549
- if (openStack.length > 0) {
124550
- const entry = openStack.pop();
124551
- // The declaration's braces are closed; later top-level braces are not ESM.
124552
- if (openStack.length === 0)
124553
- insideEsmDeclaration = false;
124554
- // Pure `{/* ... */}` comments are handled downstream by the jsxComment
124555
- // tokenizer — escaping their braces would prevent it from running.
124556
- const isPureJsxComment = chars[entry.pos + 1] === '/' &&
124557
- chars[entry.pos + 2] === '*' &&
124558
- chars[i - 1] === '/' &&
124559
- chars[i - 2] === '*';
124560
- if (entry.hasBlankLine && !isPureJsxComment && !entry.isAttrExpr && !entry.isEsmDeclaration) {
124561
- toEscape.add(entry.pos);
124562
- toEscape.add(i);
124563
- }
124564
- }
124565
- else {
124566
- toEscape.add(i);
124567
- }
124568
- }
124569
- else if (ch === ';' && openStack.length === 0) {
124570
- // A top-level `;` ends the current statement, ESM or otherwise.
124571
- insideEsmDeclaration = false;
124572
- }
124573
- }
124574
- // Anything still open is unbalanced.
124575
- openStack.forEach(entry => {
124576
- if (!entry.isEsmDeclaration)
124577
- toEscape.add(entry.pos);
124578
- });
124579
- // Reconstruct the content with the escaped braces.
124580
- const escapedContent = toEscape.size === 0 ? protectedContent : chars.map((ch, i) => (toEscape.has(i) ? `\\${ch}` : ch)).join('');
124581
- return restoreHTMLElements(escapedContent, htmlElements);
124582
- }
124583
- /**
124584
- * Preprocesses JSX-like markdown content before parsing.
124585
- *
124586
- * JSX attribute expressions (`href={baseUrl}`) are no longer rewritten here —
124587
- * they flow through the tokenizer as `mdxJsxAttributeValueExpression` nodes
124588
- * and are evaluated at the hast handler step.
124589
- *
124590
- * @param content
124591
- * @returns Preprocessed content ready for markdown parsing
124592
- */
124593
- function preprocessJSXExpressions(content) {
124594
- const { protectedCode, protectedContent } = protectCodeBlocks(content);
124595
- let processed = escapeProblematicBraces(protectedContent);
124596
- processed = restoreCodeBlocks(processed, protectedCode);
124597
- return processed;
124598
- }
124599
124461
 
124600
124462
  ;// ./processor/transform/mdxish/resolve-deferred-attribute-expression-props.ts
124601
124463
 
@@ -125570,6 +125432,85 @@ function syntax_jsxTable() {
125570
125432
  ;// ./lib/micromark/jsx-table/index.ts
125571
125433
 
125572
125434
 
125435
+ ;// ./lib/micromark/mdx-expression-lenient/syntax.ts
125436
+
125437
+
125438
+ /**
125439
+ * Lenient MDX text-expression tokenizer (agnostic / no acorn).
125440
+ *
125441
+ * Matches a balanced `{ ... }` run — tracking nested braces and spanning soft
125442
+ * line breaks — and emits the standard `mdxTextExpression*` tokens so the
125443
+ * upstream `mdxExpressionFromMarkdown()` builds the node.
125444
+ *
125445
+ * Reimplements the official micromark mdxExpression, but an unbalanced brace that
125446
+ * reaches end of input returns `nok` instead of throwing: micromark rolls back
125447
+ * and the `{` renders as literal text, making the pipeline forgiving of stray
125448
+ * braces that upstream would hard-error on.
125449
+ */
125450
+ function tokenizeTextExpression(effects, ok, nok) {
125451
+ let depth = 0;
125452
+ return start;
125453
+ function start(code) {
125454
+ if (code !== codes.leftCurlyBrace)
125455
+ return nok(code);
125456
+ effects.enter('mdxTextExpression');
125457
+ effects.enter('mdxTextExpressionMarker');
125458
+ effects.consume(code);
125459
+ effects.exit('mdxTextExpressionMarker');
125460
+ return before;
125461
+ }
125462
+ function before(code) {
125463
+ if (code === codes.eof) {
125464
+ effects.exit('mdxTextExpression');
125465
+ return nok(code);
125466
+ }
125467
+ if (markdownLineEnding(code)) {
125468
+ effects.enter('lineEnding');
125469
+ effects.consume(code);
125470
+ effects.exit('lineEnding');
125471
+ return before;
125472
+ }
125473
+ if (code === codes.rightCurlyBrace && depth === 0) {
125474
+ return close(code);
125475
+ }
125476
+ effects.enter('mdxTextExpressionChunk');
125477
+ return inside(code);
125478
+ }
125479
+ function inside(code) {
125480
+ if (code === codes.eof || markdownLineEnding(code)) {
125481
+ effects.exit('mdxTextExpressionChunk');
125482
+ return before(code);
125483
+ }
125484
+ if (code === codes.rightCurlyBrace && depth === 0) {
125485
+ effects.exit('mdxTextExpressionChunk');
125486
+ return close(code);
125487
+ }
125488
+ if (code === codes.leftCurlyBrace)
125489
+ depth += 1;
125490
+ else if (code === codes.rightCurlyBrace)
125491
+ depth -= 1;
125492
+ effects.consume(code);
125493
+ return inside;
125494
+ }
125495
+ function close(code) {
125496
+ effects.enter('mdxTextExpressionMarker');
125497
+ effects.consume(code);
125498
+ effects.exit('mdxTextExpressionMarker');
125499
+ effects.exit('mdxTextExpression');
125500
+ return ok;
125501
+ }
125502
+ }
125503
+ function mdxExpressionLenient() {
125504
+ return {
125505
+ text: {
125506
+ [codes.leftCurlyBrace]: {
125507
+ name: 'mdxTextExpression',
125508
+ tokenize: tokenizeTextExpression,
125509
+ },
125510
+ },
125511
+ };
125512
+ }
125513
+
125573
125514
  ;// ./lib/utils/mdxish/mdxish-load-components.ts
125574
125515
 
125575
125516
 
@@ -125682,7 +125623,7 @@ const defaultTransformers = [
125682
125623
  * 1. Normalize malformed table separator syntax (e.g., `|: ---` → `| :---`)
125683
125624
  * 2. Terminate HTML flow blocks so subsequent content isn't swallowed
125684
125625
  * 3. Close invalid "self-closing" HTML tags (e.g., `<i />` → `<i></i>`)
125685
- * 4. Escape problematic braces so MDX expression parsing doesn't choke
125626
+ * 4. Normalize compact ATX headings (e.g., `#Heading` `# Heading`)
125686
125627
  * 5. Replace snake_case component names with parser-safe placeholders
125687
125628
  */
125688
125629
  function preprocessContent(content, opts) {
@@ -125691,7 +125632,6 @@ function preprocessContent(content, opts) {
125691
125632
  result = terminateHtmlFlowBlocks(result);
125692
125633
  result = closeSelfClosingHtmlTags(result);
125693
125634
  result = normalizeCompactHeadings(result);
125694
- result = preprocessJSXExpressions(result);
125695
125635
  return processSnakeCaseComponent(result, { knownComponents });
125696
125636
  }
125697
125637
  function mdxishAstProcessor(mdContent, opts = {}) {
@@ -125708,12 +125648,8 @@ function mdxishAstProcessor(mdContent, opts = {}) {
125708
125648
  acc[key] = String(value);
125709
125649
  return acc;
125710
125650
  }, {});
125711
- // Get mdxExpression extension and remove its flow construct to prevent
125712
- // `{...}` from interrupting paragraphs (which breaks multiline magic blocks)
125713
- const mdxExprExt = syntax_mdxExpression({ allowEmpty: true });
125714
- const mdxExprTextOnly = {
125715
- text: mdxExprExt.text,
125716
- };
125651
+ // Parser extension for MDX expressions {}
125652
+ const mdxExprTextOnly = mdxExpressionLenient();
125717
125653
  const micromarkExts = [
125718
125654
  syntax_jsxTable(),
125719
125655
  syntax_magicBlock(),