@mintlify/common 1.0.1269 → 1.0.1270

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.
Files changed (26) hide show
  1. package/dist/mdx/estreeReferences.d.ts +39 -0
  2. package/dist/mdx/estreeReferences.js +963 -0
  3. package/dist/mdx/plugins/remark/firstPartyMarkdownComponents.d.ts +3 -0
  4. package/dist/mdx/plugins/remark/firstPartyMarkdownComponents.js +29 -0
  5. package/dist/mdx/plugins/remark/remarkMdxRemoveUnknownJsx/index.d.ts +1 -1
  6. package/dist/mdx/plugins/remark/remarkVisibilityForMarkdown.d.ts +7 -1
  7. package/dist/mdx/plugins/remark/remarkVisibilityForMarkdown.js +581 -177
  8. package/dist/mdx/plugins/remark/staticEvaluator.d.ts +55 -0
  9. package/dist/mdx/plugins/remark/staticEvaluator.js +639 -0
  10. package/dist/mdx/plugins/remark/unfoldComponentsForMarkdown.d.ts +71 -0
  11. package/dist/mdx/plugins/remark/unfoldComponentsForMarkdown.js +1414 -0
  12. package/dist/mdx/remark-core.d.ts +7 -0
  13. package/dist/mdx/remark-core.js +32 -0
  14. package/dist/mdx/remark.d.ts +1 -7
  15. package/dist/mdx/remark.js +2 -32
  16. package/dist/mdx/snippets/getExportMap.js +33 -5
  17. package/dist/mdx/snippets/resolveAllImports.js +3 -2
  18. package/dist/mdx/snippets/resolveImport/exportCollision.d.ts +1 -0
  19. package/dist/mdx/snippets/resolveImport/exportCollision.js +2 -1
  20. package/dist/mdx/snippets/resolveImport/index.d.ts +11 -1
  21. package/dist/mdx/snippets/resolveImport/index.js +129 -16
  22. package/dist/mdx/snippets/resolveImport/resolveComponentWithContent.js +38 -5
  23. package/dist/mdx/snippets/resolveImport/snippetDeclarations.d.ts +56 -0
  24. package/dist/mdx/snippets/resolveImport/snippetDeclarations.js +655 -0
  25. package/dist/tsconfig.build.tsbuildinfo +1 -1
  26. package/package.json +2 -2
@@ -0,0 +1,7 @@
1
+ import type { Root } from 'mdast';
2
+ import type { PluggableList } from 'unified';
3
+ export declare const coreRemarkMdxPlugins: PluggableList;
4
+ export declare const coreRemark: import("unified").Processor<Root, undefined, undefined, Root, string>;
5
+ export declare const getAST: (str: string, filePath?: string) => Root;
6
+ export declare const stringifyTree: (tree: Root) => string;
7
+ export declare const stringifyTreeForAgents: (tree: Root) => string;
@@ -0,0 +1,32 @@
1
+ import { remark } from 'remark';
2
+ import remarkFrontmatter from 'remark-frontmatter';
3
+ import remarkGfm from 'remark-gfm';
4
+ import remarkMath from 'remark-math';
5
+ import remarkMdx from 'remark-mdx';
6
+ import remarkStringify from 'remark-stringify';
7
+ import { preprocessCustomHeadingIds } from './preprocessCustomHeadingIds.js';
8
+ import { preprocessMdxFragmentCode } from './preprocessMdxFragmentCode.js';
9
+ import { getJsxEsmTree } from './snippets/getJsxEsmTree.js';
10
+ import { isJsxOrTsx } from './snippets/isJsxOrTsx.js';
11
+ export const coreRemarkMdxPlugins = [
12
+ remarkMdx,
13
+ remarkGfm,
14
+ [remarkFrontmatter, ['yaml', 'toml']],
15
+ remarkMath,
16
+ ];
17
+ export const coreRemark = remark().use(coreRemarkMdxPlugins).freeze();
18
+ const agentRemarkMdxPlugins = [
19
+ remarkMdx,
20
+ [remarkGfm, { tablePipeAlign: false }],
21
+ [remarkFrontmatter, ['yaml', 'toml']],
22
+ remarkMath,
23
+ ];
24
+ const agentRemark = remark().use(agentRemarkMdxPlugins).freeze();
25
+ export const getAST = (str, filePath) => {
26
+ if (isJsxOrTsx(filePath)) {
27
+ return getJsxEsmTree(str, filePath);
28
+ }
29
+ return coreRemark().parse(preprocessCustomHeadingIds(preprocessMdxFragmentCode(str)));
30
+ };
31
+ export const stringifyTree = (tree) => coreRemark().use(remarkStringify).stringify(tree);
32
+ export const stringifyTreeForAgents = (tree) => agentRemark().use(remarkStringify).stringify(tree);
@@ -1,10 +1,4 @@
1
- import type { Root } from 'mdast';
2
- import type { PluggableList } from 'unified';
3
1
  import { type RemarkUserMentionsOptions } from './plugins/remark/remarkUserMentions.js';
4
- export declare const coreRemarkMdxPlugins: PluggableList;
5
- export declare const coreRemark: import("unified").Processor<Root, undefined, undefined, Root, string>;
6
- export declare const getAST: (str: string, filePath?: string) => Root;
7
- export declare const stringifyTree: (tree: Root) => string;
8
- export declare const stringifyTreeForAgents: (tree: Root) => string;
2
+ export { coreRemark, coreRemarkMdxPlugins, getAST, stringifyTree, stringifyTreeForAgents, } from './remark-core.js';
9
3
  export declare function stripVisibilityForMarkdown(mdx: string): string;
10
4
  export declare function transformContentForMarkdown(mdx: string, mentionOptions?: RemarkUserMentionsOptions): string;
@@ -1,37 +1,7 @@
1
- import { remark } from 'remark';
2
- import remarkFrontmatter from 'remark-frontmatter';
3
- import remarkGfm from 'remark-gfm';
4
- import remarkMath from 'remark-math';
5
- import remarkMdx from 'remark-mdx';
6
- import remarkStringify from 'remark-stringify';
7
1
  import { remarkUserMentions, } from './plugins/remark/remarkUserMentions.js';
8
2
  import { remarkVisibilityForMarkdown } from './plugins/remark/remarkVisibilityForMarkdown.js';
9
- import { preprocessCustomHeadingIds } from './preprocessCustomHeadingIds.js';
10
- import { preprocessMdxFragmentCode } from './preprocessMdxFragmentCode.js';
11
- import { getJsxEsmTree } from './snippets/getJsxEsmTree.js';
12
- import { isJsxOrTsx } from './snippets/isJsxOrTsx.js';
13
- export const coreRemarkMdxPlugins = [
14
- remarkMdx,
15
- remarkGfm,
16
- [remarkFrontmatter, ['yaml', 'toml']],
17
- remarkMath,
18
- ];
19
- export const coreRemark = remark().use(coreRemarkMdxPlugins).freeze();
20
- const agentRemarkMdxPlugins = [
21
- remarkMdx,
22
- [remarkGfm, { tablePipeAlign: false }],
23
- [remarkFrontmatter, ['yaml', 'toml']],
24
- remarkMath,
25
- ];
26
- const agentRemark = remark().use(agentRemarkMdxPlugins).freeze();
27
- export const getAST = (str, filePath) => {
28
- if (isJsxOrTsx(filePath)) {
29
- return getJsxEsmTree(str, filePath);
30
- }
31
- return coreRemark().parse(preprocessCustomHeadingIds(preprocessMdxFragmentCode(str)));
32
- };
33
- export const stringifyTree = (tree) => coreRemark().use(remarkStringify).stringify(tree);
34
- export const stringifyTreeForAgents = (tree) => agentRemark().use(remarkStringify).stringify(tree);
3
+ import { getAST, stringifyTreeForAgents } from './remark-core.js';
4
+ export { coreRemark, coreRemarkMdxPlugins, getAST, stringifyTree, stringifyTreeForAgents, } from './remark-core.js';
35
5
  export function stripVisibilityForMarkdown(mdx) {
36
6
  const tree = getAST(mdx);
37
7
  remarkVisibilityForMarkdown()(tree);
@@ -1,5 +1,6 @@
1
1
  import { jsx, toJs } from 'estree-util-to-js';
2
2
  import { visit } from 'unist-util-visit';
3
+ import { collectPatternNames } from '../estreeReferences.js';
3
4
  import { moduleExportName } from './importedExportName.js';
4
5
  import { nodeIncludesExport } from './nodeIncludesExport.js';
5
6
  import { addExportPrefix } from './resolveImport/addExportPrefix.js';
@@ -11,6 +12,7 @@ export const getExportMapFromTree = (tree) => {
11
12
  var _a;
12
13
  const exportMap = {};
13
14
  visit(tree, (node) => {
15
+ var _a;
14
16
  if (!nodeIncludesExport(node))
15
17
  return;
16
18
  for (const bodyChild of node.data.estree.body) {
@@ -39,8 +41,8 @@ export const getExportMapFromTree = (tree) => {
39
41
  for (const specifier of bodyChild.specifiers) {
40
42
  const localName = specifier.local.name;
41
43
  const exportedName = moduleExportName(specifier.exported);
42
- // Find the original declaration
43
- const originalDeclaration = findDeclarationInTree(localName, node);
44
+ // Find the original declaration, which may itself be exported
45
+ const originalDeclaration = (_a = findDeclarationInTree(localName, node)) !== null && _a !== void 0 ? _a : findExportedDeclaration(localName, tree);
44
46
  if (originalDeclaration) {
45
47
  const wrappedNode = structuredClone(node);
46
48
  wrappedNode.data.estree.body = [originalDeclaration];
@@ -55,15 +57,15 @@ export const getExportMapFromTree = (tree) => {
55
57
  // Variable declarations
56
58
  if (bodyChild.declaration.type === 'VariableDeclaration') {
57
59
  for (const declaration of bodyChild.declaration.declarations) {
58
- if (declaration.id.type !== 'Identifier')
59
- continue;
60
60
  if (declaration.init == null)
61
61
  continue;
62
62
  // Sometimes when more than one exports are defined next to each other, the body includes multiple exports so we isolate the export we are looking for.
63
63
  const isolatedExport = structuredClone(node);
64
64
  isolatedExport.data.estree.body = [bodyChild];
65
65
  const value = toJs(isolatedExport.data.estree, { handlers: jsx }).value;
66
- exportMap[declaration.id.name] = value;
66
+ // A destructured declaration binds every name in its pattern with the same statement
67
+ for (const name of patternNames(declaration.id))
68
+ exportMap[name] = value;
67
69
  }
68
70
  }
69
71
  // Function declarations
@@ -89,6 +91,32 @@ export const getExportMapFromTree = (tree) => {
89
91
  }
90
92
  return exportMap;
91
93
  };
94
+ function patternNames(pattern) {
95
+ const names = new Set();
96
+ collectPatternNames(pattern, names);
97
+ return names;
98
+ }
99
+ function findExportedDeclaration(name, tree) {
100
+ let found = null;
101
+ visit(tree, (node) => {
102
+ var _a;
103
+ if (found !== null || !nodeIncludesExport(node))
104
+ return;
105
+ for (const bodyChild of node.data.estree.body) {
106
+ if (bodyChild.type !== 'ExportNamedDeclaration' || bodyChild.declaration == null)
107
+ continue;
108
+ const declaration = bodyChild.declaration;
109
+ const declares = declaration.type === 'VariableDeclaration'
110
+ ? declaration.declarations.some((declarator) => patternNames(declarator.id).has(name))
111
+ : ((_a = declaration.id) === null || _a === void 0 ? void 0 : _a.name) === name;
112
+ if (declares) {
113
+ found = bodyChild;
114
+ return;
115
+ }
116
+ }
117
+ });
118
+ return found;
119
+ }
92
120
  /**
93
121
  * Find a declaration by name in the tree
94
122
  */
@@ -16,7 +16,7 @@ import { estreeIsProgram, isImportDeclaration, isMdxJsEsm } from '../utils.js';
16
16
  import { findAndRemoveImports } from './findAndRemoveImports.js';
17
17
  import { getExportMapFromTree } from './getExportMap.js';
18
18
  import { getSpecifierPackageName } from './isBareSpecifier.js';
19
- import { resolveImport } from './resolveImport/index.js';
19
+ import { createImportResolutionState, resolveImport } from './resolveImport/index.js';
20
20
  import { injectToTopOfFileOfTree } from './resolveImport/injectToTopOfFile.js';
21
21
  import { resolveImportPath } from './resolveImportPath.js';
22
22
  const hasImportDeclaration = (node) => isMdxJsEsm(node) && estreeIsProgram(node) && node.data.estree.body.some(isImportDeclaration);
@@ -119,6 +119,7 @@ export const resolveAllImports = (params) => __awaiter(void 0, void 0, void 0, f
119
119
  ? takePackageReExports(ast, packages.packageNames)
120
120
  : [];
121
121
  const exportMap = getExportMapFromTree(ast);
122
+ const importState = createImportResolutionState();
122
123
  const strippedSnippets = new Map();
123
124
  for (const source of Object.keys(fileWithImports.importMap)) {
124
125
  const resolvedPath = resolveImportPath(source, fileWithImports.filename);
@@ -198,7 +199,7 @@ export const resolveAllImports = (params) => __awaiter(void 0, void 0, void 0, f
198
199
  else {
199
200
  console.log(located.message);
200
201
  }
201
- });
202
+ }, importState);
202
203
  if (contentWithResolvedImport == undefined) {
203
204
  throw new Error('Import failed to resolve');
204
205
  }
@@ -3,5 +3,6 @@ export type ExportCollisionWarning = {
3
3
  message: string;
4
4
  };
5
5
  export type OnExportCollision = (warning: ExportCollisionWarning) => void;
6
+ export declare const isSameStatement: (left: string, right: string) => boolean;
6
7
  /** Same name, different code: the first declaration wins, so say so instead of failing silently. */
7
8
  export declare const warnOnExportCollision: (name: string, existing: string, incoming: string, onWarning?: OnExportCollision) => void;
@@ -1,7 +1,8 @@
1
1
  const normalizeStatement = (statement) => statement.replace(/\s+/g, ' ').trim();
2
+ export const isSameStatement = (left, right) => normalizeStatement(left) === normalizeStatement(right);
2
3
  /** Same name, different code: the first declaration wins, so say so instead of failing silently. */
3
4
  export const warnOnExportCollision = (name, existing, incoming, onWarning) => {
4
- if (normalizeStatement(existing) === normalizeStatement(incoming))
5
+ if (isSameStatement(existing, incoming))
5
6
  return;
6
7
  onWarning === null || onWarning === void 0 ? void 0 : onWarning({
7
8
  type: 'export-collision',
@@ -1,14 +1,24 @@
1
1
  import type { Root } from 'mdast';
2
2
  import type { ImportSpecifier } from '../../../types/mdx/snippets/import.js';
3
+ import { collectContextFromTree } from '../../plugins/remark/staticEvaluator.js';
3
4
  import { type OnExportCollision } from './exportCollision.js';
4
5
  export { type ExportCollisionWarning, type OnExportCollision } from './exportCollision.js';
5
6
  export { findExport } from './findExport.js';
6
7
  export { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
8
+ /** What one page's imports have resolved so far. Pass the same state for every import of a page. */
9
+ export interface ImportResolutionState {
10
+ /** The local name the page declares for each binding of each imported snippet */
11
+ bindings: Map<Root, Map<string, string>>;
12
+ /** Evaluated module scope of each imported snippet */
13
+ contexts: Map<Root, ReturnType<typeof collectContextFromTree>>;
14
+ }
15
+ export declare const createImportResolutionState: () => ImportResolutionState;
7
16
  /**
8
17
  *
9
18
  * @param importSpecifier The name of the imported component we want to replace
10
19
  * @param destinationPageContent The content of the page we want to inject the import into
11
20
  * @param importedFileContent The content of the file we are importing from
21
+ * @param state Shared by every import of one page, so readers of a snippet binding share one value
12
22
  * @returns
13
23
  */
14
- export declare const resolveImport: (importSpecifier: ImportSpecifier, destinationPageContent: Root, importedFileContent: Root, exportMap: Record<string, string>, onWarning?: OnExportCollision) => Promise<Root | undefined>;
24
+ export declare const resolveImport: (importSpecifier: ImportSpecifier, destinationPageContent: Root, importedFileContent: Root, exportMap: Record<string, string>, onWarning?: OnExportCollision, state?: ImportResolutionState) => Promise<Root | undefined>;
@@ -8,18 +8,39 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { MdxImportSpecifier } from '../../../types/mdx/snippets/import.js';
11
+ import { collectContextFromTree, serializeStaticValue, } from '../../plugins/remark/staticEvaluator.js';
11
12
  import { getAST } from '../../remark.js';
12
13
  import { getExportMapFromTree } from '../getExportMap.js';
13
14
  import { warnOnExportCollision } from './exportCollision.js';
14
15
  import { findExport } from './findExport.js';
15
16
  import { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
16
17
  import { resolveComponentWithContent } from './resolveComponentWithContent.js';
18
+ import { collectExportedNames, collectTopLevelBindingNames, declarationSource, exportedBindingName, injectBelowEagerDependencies, isolateExportedDeclarator, patternSiblingNames, prepareSnippetSiblings, } from './snippetDeclarations.js';
17
19
  export { findExport } from './findExport.js';
18
20
  export { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
19
21
  const VALID_SPECIFIERS = [
20
22
  MdxImportSpecifier.ImportSpecifier,
21
23
  MdxImportSpecifier.RenamedImportSpecifier,
22
24
  ];
25
+ export const createImportResolutionState = () => ({
26
+ bindings: new Map(),
27
+ contexts: new Map(),
28
+ });
29
+ /** The local names this page already declares for each binding of `snippet`. */
30
+ function heldSnippetBindings(state, snippet) {
31
+ var _a;
32
+ const held = (_a = state.bindings.get(snippet)) !== null && _a !== void 0 ? _a : new Map();
33
+ state.bindings.set(snippet, held);
34
+ return held;
35
+ }
36
+ function snippetContext(state, tree) {
37
+ const cached = state.contexts.get(tree);
38
+ if (cached !== undefined)
39
+ return cached;
40
+ const context = collectContextFromTree(tree);
41
+ state.contexts.set(tree, context);
42
+ return context;
43
+ }
23
44
  const OTHER_SPECIFIERS = [
24
45
  MdxImportSpecifier.ImportDefaultSpecifier,
25
46
  MdxImportSpecifier.ImportNamespaceSpecifier,
@@ -29,29 +50,121 @@ const OTHER_SPECIFIERS = [
29
50
  * @param importSpecifier The name of the imported component we want to replace
30
51
  * @param destinationPageContent The content of the page we want to inject the import into
31
52
  * @param importedFileContent The content of the file we are importing from
53
+ * @param state Shared by every import of one page, so readers of a snippet binding share one value
32
54
  * @returns
33
55
  */
34
- export const resolveImport = (importSpecifier, destinationPageContent, importedFileContent, exportMap, onWarning) => __awaiter(void 0, void 0, void 0, function* () {
35
- var _a, _b;
56
+ export const resolveImport = (importSpecifier_1, destinationPageContent_1, importedFileContent_1, exportMap_1, onWarning_1, ...args_1) => __awaiter(void 0, [importSpecifier_1, destinationPageContent_1, importedFileContent_1, exportMap_1, onWarning_1, ...args_1], void 0, function* (importSpecifier, destinationPageContent, importedFileContent, exportMap, onWarning, state = createImportResolutionState()) {
57
+ var _a, _b, _c;
36
58
  if (VALID_SPECIFIERS.includes(importSpecifier.type)) {
37
59
  // normal import: import { Component } from '/snippets/Component.mdx' or jsx file
38
- // find "Component" in "/snippets/Component.mdx" or jsx files
39
- const exportContent = findExport(importSpecifier.name, importedFileContent, importSpecifier.renamedName);
40
- if (exportContent == undefined)
41
- throw new Error(`Could not find export ${importSpecifier.name} in snippet`);
42
- // `import { X as Y }` binds Y; an existing declaration of that name wins
43
60
  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);
61
+ const destinationBindings = collectTopLevelBindingNames(destinationPageContent);
62
+ const destinationExports = collectExportedNames(destinationPageContent);
63
+ // An existing binding or export of the name wins; warn when its definition differs. MDX
64
+ // modules cannot declare unexported bindings, so a page export alias also keeps the name.
65
+ const keepExisting = (incoming) => {
66
+ var _a, _b;
67
+ const injected = exportMap[localName];
68
+ if (injected !== undefined) {
69
+ warnOnExportCollision(localName, injected, incoming, onWarning);
70
+ return true;
71
+ }
72
+ if (!destinationBindings.has(localName) && !destinationExports.has(localName))
73
+ return false;
74
+ warnOnExportCollision(localName, (_a = declarationSource(destinationPageContent, localName)) !== null && _a !== void 0 ? _a : '', (_b = declarationSource(getAST(incoming), localName)) !== null && _b !== void 0 ? _b : incoming, onWarning);
75
+ return true;
76
+ };
77
+ const binding = exportedBindingName(importedFileContent, importSpecifier.name);
78
+ const held = heldSnippetBindings(state, importedFileContent);
79
+ const holder = held.get(binding);
80
+ // A binding this page already declares, explicitly or as a private sibling, is aliased so
81
+ // every reader shares one value, as it would in a real module
82
+ if (holder !== undefined && holder !== localName) {
83
+ const statement = `export const ${localName} = ${holder};`;
84
+ if (keepExisting(statement))
85
+ return destinationPageContent;
86
+ injectBelowEagerDependencies(destinationPageContent, getAST(statement).children);
87
+ exportMap[localName] = statement;
88
+ return destinationPageContent;
89
+ }
90
+ const exportContent = findExport(importSpecifier.name, structuredClone(importedFileContent), importSpecifier.renamedName);
91
+ if (exportContent == undefined) {
92
+ const sourceScope = snippetContext(state, importedFileContent).moduleScope;
93
+ if (!collectExportedNames(importedFileContent).has(importSpecifier.name) ||
94
+ !sourceScope.has(binding)) {
95
+ throw new Error(`Could not find export ${importSpecifier.name} in snippet`);
96
+ }
97
+ const serialize = (name) => {
98
+ const stored = sourceScope.get(name);
99
+ return serializeStaticValue(stored === undefined ? undefined : stored);
100
+ };
101
+ const statement = `export const ${localName} = ${serialize(binding)};`;
102
+ if (keepExisting(statement))
103
+ return destinationPageContent;
104
+ // The other names a destructuring export binds are declared privately too, so a sibling
105
+ // that reads one later shares it instead of copying the whole pattern again
106
+ const siblings = [];
107
+ for (const [name, privateName] of patternSiblingNames(importedFileContent, binding)) {
108
+ if (held.has(name) || !sourceScope.has(name))
109
+ continue;
110
+ const sibling = `export const ${privateName} = ${serialize(name)};`;
111
+ siblings.push(sibling);
112
+ (_b = exportMap[privateName]) !== null && _b !== void 0 ? _b : (exportMap[privateName] = sibling);
113
+ held.set(name, privateName);
114
+ }
115
+ injectToTopOfFileOfTree(destinationPageContent, getAST([...siblings, statement].join('\n')).children);
116
+ exportMap[localName] = statement;
117
+ held.set(binding, localName);
47
118
  return destinationPageContent;
48
119
  }
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);
120
+ if (keepExisting(exportContent))
121
+ return destinationPageContent;
122
+ const statementTree = getAST(exportContent);
123
+ // A grouped statement is injected whole unless another of its names is already taken.
124
+ // `findExport` keeps the binding's original name when renaming it is unsafe, so the local name
125
+ // is then declared as an alias and no other declarator may take it.
126
+ const exportedNames = collectTopLevelBindingNames(statementTree);
127
+ const exportedDeclarator = exportedNames.has(binding) ? binding : localName;
128
+ const localAlias = exportedDeclarator === localName
129
+ ? []
130
+ : getAST(`export const ${localName} = ${exportedDeclarator};`).children;
131
+ const redeclaresDestination = [...exportedNames].some((declared) => declared !== exportedDeclarator &&
132
+ (declared === localName ||
133
+ held.has(declared) ||
134
+ destinationBindings.has(declared) ||
135
+ destinationExports.has(declared)));
136
+ const isolated = redeclaresDestination
137
+ ? isolateExportedDeclarator(statementTree, exportedDeclarator)
138
+ : statementTree;
139
+ const { siblings, statement, trailing, privateNames } = prepareSnippetSiblings(destinationPageContent, importedFileContent, isolated, { name: importSpecifier.name, localName }, held);
140
+ const injected = {
141
+ type: 'root',
142
+ children: [...siblings, ...statement.children, ...localAlias, ...trailing],
143
+ };
144
+ injectBelowEagerDependencies(destinationPageContent, injected.children);
145
+ // `findExport` keeps the original name when renaming the declaration is unsafe. The binding's
146
+ // own name is checked first, since a grouped statement can also declare a different
147
+ // declarator under the local name.
148
+ const statementNames = collectTopLevelBindingNames(statement);
149
+ if (statementNames.has(binding))
150
+ held.set(binding, binding);
151
+ else if (statementNames.has(localName))
152
+ held.set(binding, localName);
153
+ // A grouped statement declares its other snippet bindings on the page under their own names
154
+ const snippetBindings = collectTopLevelBindingNames(importedFileContent);
155
+ for (const declared of statementNames) {
156
+ if (declared !== localName && snippetBindings.has(declared) && !held.has(declared)) {
157
+ held.set(declared, declared);
158
+ }
159
+ }
160
+ for (const [original, privateName] of privateNames) {
161
+ if (!held.has(original))
162
+ held.set(original, privateName);
163
+ }
164
+ // Record every exported name, private siblings included, so a later import or inlined
165
+ // snippet dedupes against it
166
+ for (const [declared, source] of Object.entries(getExportMapFromTree(injected))) {
167
+ (_c = exportMap[declared]) !== null && _c !== void 0 ? _c : (exportMap[declared] = source);
55
168
  }
56
169
  return destinationPageContent;
57
170
  }
@@ -15,8 +15,8 @@ import { JSXParser } from '../../lib/jsxParser.js';
15
15
  import { getAST } from '../../remark.js';
16
16
  import { createUniqueVariableName, isMdxJsxFlowElement } from '../../utils.js';
17
17
  import { findAndRemoveExports } from '../findAndRemoveExports.js';
18
- import { warnOnExportCollision } from './exportCollision.js';
19
- import { injectToTopOfFileOfTree } from './injectToTopOfFile.js';
18
+ import { isSameStatement, warnOnExportCollision, } from './exportCollision.js';
19
+ import { injectBelowEagerDependencies, isPrivateSiblingName, pruneDeclaredNames, statementSourceNames, takePrivateDeclarations, } from './snippetDeclarations.js';
20
20
  /**
21
21
  *
22
22
  * @param tree The tree to inject into
@@ -274,19 +274,52 @@ const replaceCodePlaceholdersWithProps = (tree, propValues) => {
274
274
  });
275
275
  };
276
276
  const reinsertExports = (content, exportMap, snippetExportMap, onWarning) => {
277
+ var _a;
277
278
  const nodesToInject = [];
279
+ const injectedStatements = new Set();
278
280
  for (const [key, value] of Object.entries(snippetExportMap)) {
279
281
  const existing = exportMap[key];
282
+ // An alias maps to its local's declaration, which does not declare the alias itself. The
283
+ // declaration is still inserted when nothing else supplies it, but the alias is not claimed.
284
+ const declaresKey = key === 'default' || statementSourceNames(value).has(key);
285
+ if (existing == undefined && !declaresKey) {
286
+ const declared = [...statementSourceNames(value)];
287
+ const unsupplied = declared.every((name) => exportMap[name] === undefined && snippetExportMap[name] === undefined);
288
+ if (unsupplied && !injectedStatements.has(value)) {
289
+ nodesToInject.push(...getAST(value).children);
290
+ injectedStatements.add(value);
291
+ for (const name of declared)
292
+ exportMap[name] = value;
293
+ }
294
+ continue;
295
+ }
280
296
  if (existing == undefined) {
281
- const exportAST = getAST(value);
282
- nodesToInject.push(...exportAST.children);
297
+ // A destructured export maps each of its names to the same statement: insert it once, and
298
+ // without the names the page already declares
299
+ const taken = new Set([...statementSourceNames(value)].filter((name) => name !== key && exportMap[name] !== undefined && exportMap[name] !== value));
300
+ const statement = taken.size === 0
301
+ ? value
302
+ : pruneDeclaredNames(value, taken, (name) => exportMap[name] !== undefined);
303
+ if (statement === undefined) {
304
+ for (const name of taken)
305
+ warnOnExportCollision(name, (_a = exportMap[name]) !== null && _a !== void 0 ? _a : '', value, onWarning);
306
+ continue;
307
+ }
308
+ if (!injectedStatements.has(statement))
309
+ nodesToInject.push(...getAST(statement).children);
310
+ injectedStatements.add(statement);
283
311
  exportMap[key] = value;
284
312
  }
313
+ else if (isPrivateSiblingName(key) && isSameStatement(existing, value)) {
314
+ // The same snippet helper, already injected: move it up so this snippet's code reads it
315
+ // after it is declared. Different code under a reserved name falls through to a warning.
316
+ nodesToInject.push(...takePrivateDeclarations(content, [key]));
317
+ }
285
318
  else {
286
319
  warnOnExportCollision(key, existing, value, onWarning);
287
320
  }
288
321
  }
289
322
  if (nodesToInject.length > 0) {
290
- injectToTopOfFileOfTree(content, nodesToInject);
323
+ injectBelowEagerDependencies(content, nodesToInject);
291
324
  }
292
325
  };
@@ -0,0 +1,56 @@
1
+ import type { Root, RootContent } from 'mdast';
2
+ export declare function isolateExportedDeclarator(tree: Root, name: string): Root;
3
+ /** Names a module makes importable: named export declarations, specifiers, and default. */
4
+ export declare function collectExportedNames(tree: Root): Set<string>;
5
+ /** Every top-level lexical binding in the tree's ESM statements, exported or not. */
6
+ export declare function collectTopLevelBindingNames(tree: Root): Set<string>;
7
+ /** The local binding behind an export name, following `export { local as name }`. */
8
+ export declare function exportedBindingName(tree: Root, exportName: string): string;
9
+ /** Source of the top-level declaration that binds `name`, without any `export` keyword. */
10
+ export declare function declarationSource(tree: Root, name: string): string | undefined;
11
+ /** Names an export statement's source declares, destructured patterns included. */
12
+ export declare function statementSourceNames(source: string): Set<string>;
13
+ /**
14
+ * Rewrites a single variable export so it no longer declares the names in `drop`. A declarator
15
+ * that only binds dropped names and that no kept declarator reads is removed, so its initializer
16
+ * never runs. Any other dropped name is renamed to a private name throughout the statement, so
17
+ * rest elements still receive the same keys and kept declarators still read the snippet's values.
18
+ */
19
+ export declare function pruneDeclaredNames(source: string, drop: ReadonlySet<string>, isNameTaken?: (name: string) => boolean): string | undefined;
20
+ export declare function isPrivateSiblingName(name: string): boolean;
21
+ /**
22
+ * Removes the private sibling declarations for `names` from the tree, with the private siblings
23
+ * they reference, and returns them dependencies first so they can be re-inserted above new readers.
24
+ */
25
+ export declare function takePrivateDeclarations(tree: Root, names: Iterable<string>): RootContent[];
26
+ /**
27
+ * Injects each node as high as it can go: at the top of the page, or just below the last
28
+ * declaration it reads while initializing, such as a reused helper that could not move above a
29
+ * page binding. Placing nodes one at a time keeps helpers that need no such dependency at the top,
30
+ * above any existing code that reads them.
31
+ */
32
+ export declare function injectBelowEagerDependencies(tree: Root, nodes: RootContent[]): void;
33
+ export type SnippetInjection = {
34
+ /** Declared above the imported statement. */
35
+ siblings: RootContent[];
36
+ statement: Root;
37
+ /** Declared below the imported statement, because they read it while initializing. */
38
+ trailing: RootContent[];
39
+ /** The final name of each snippet binding that is now declared privately on the page. */
40
+ privateNames: Map<string, string>;
41
+ };
42
+ /**
43
+ * Collects the snippet declarations the injected statement transitively references. They stay
44
+ * private to the import, as in a real module: each gets a reserved name derived from its snippet,
45
+ * and dependencies are declared first. A sibling an earlier import already injected is reused and
46
+ * moved up with its own dependencies, so it stays above every reader.
47
+ */
48
+ /**
49
+ * The private names that the other bindings of `binding`'s declarator take on a page, such as
50
+ * `other` for `export const [binding, other] = …`.
51
+ */
52
+ export declare function patternSiblingNames(imported: Root, binding: string): Map<string, string>;
53
+ export declare function prepareSnippetSiblings(destination: Root, imported: Root, statement: Root, requested: {
54
+ name: string;
55
+ localName: string;
56
+ }, heldBindings?: ReadonlyMap<string, string>): SnippetInjection;