@mintlify/common 1.0.1228 → 1.0.1230
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/plugins/remark/index.d.ts +1 -0
- package/dist/mdx/plugins/remark/index.js +1 -0
- package/dist/mdx/plugins/remark/pureTemplateComponents.d.ts +7 -0
- package/dist/mdx/plugins/remark/pureTemplateComponents.js +406 -0
- package/dist/mdx/plugins/remark/remarkMdxRemoveJs.js +34 -28
- package/dist/navigation/filterDivisions.test.js +23 -0
- package/dist/navigation/pathsDifferByOneSegment.test.js +17 -0
- package/dist/navigation/scopeNavToPath.d.ts +1 -1
- package/dist/navigation/scopeNavToPath.js +37 -5
- package/dist/navigation/scopeNavToPath.test.js +37 -0
- package/dist/navigation/updateFirstHrefInDivision.d.ts +1 -1
- package/dist/navigation/updateFirstHrefInDivision.js +26 -23
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Program } from 'estree-jsx';
|
|
2
|
+
import type { Root } from 'mdast';
|
|
3
|
+
export declare const TEMPLATE_BUILT_IN_COMPONENTS: ReadonlySet<string>;
|
|
4
|
+
type Statement = Program['body'][number];
|
|
5
|
+
export declare function getKeptEsmStatements(tree: Root): Set<Statement>;
|
|
6
|
+
export declare function extractIdentifierRef(estree: Program | undefined): string | null;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
|
+
import { isMdxJsEsm } from '../../utils.js';
|
|
3
|
+
export const TEMPLATE_BUILT_IN_COMPONENTS = new Set([
|
|
4
|
+
'Accordion',
|
|
5
|
+
'AccordionGroup',
|
|
6
|
+
'Badge',
|
|
7
|
+
'Callout',
|
|
8
|
+
'Card',
|
|
9
|
+
'CardGroup',
|
|
10
|
+
'Check',
|
|
11
|
+
'CodeBlock',
|
|
12
|
+
'CodeGroup',
|
|
13
|
+
'Color',
|
|
14
|
+
'Column',
|
|
15
|
+
'Columns',
|
|
16
|
+
'Danger',
|
|
17
|
+
'Expandable',
|
|
18
|
+
'FileTree',
|
|
19
|
+
'Frame',
|
|
20
|
+
'GitHub',
|
|
21
|
+
'Github',
|
|
22
|
+
'Heading',
|
|
23
|
+
'Icon',
|
|
24
|
+
'Info',
|
|
25
|
+
'Latex',
|
|
26
|
+
'Link',
|
|
27
|
+
'MDX',
|
|
28
|
+
'Mermaid',
|
|
29
|
+
'Note',
|
|
30
|
+
'OptimizedVideo',
|
|
31
|
+
'Panel',
|
|
32
|
+
'ParamField',
|
|
33
|
+
'Prompt',
|
|
34
|
+
'RequestExample',
|
|
35
|
+
'ResponseExample',
|
|
36
|
+
'ResponseField',
|
|
37
|
+
'SnippetGroup',
|
|
38
|
+
'Step',
|
|
39
|
+
'Steps',
|
|
40
|
+
'Tab',
|
|
41
|
+
'Tabs',
|
|
42
|
+
'Tile',
|
|
43
|
+
'Tip',
|
|
44
|
+
'Tooltip',
|
|
45
|
+
'Tree',
|
|
46
|
+
'Update',
|
|
47
|
+
'Variation',
|
|
48
|
+
'View',
|
|
49
|
+
'Visibility',
|
|
50
|
+
'Warning',
|
|
51
|
+
'ZoomImage',
|
|
52
|
+
]);
|
|
53
|
+
const UNSAFE_TAGS = new Set([
|
|
54
|
+
'base',
|
|
55
|
+
'embed',
|
|
56
|
+
'form',
|
|
57
|
+
'frame',
|
|
58
|
+
'frameset',
|
|
59
|
+
'iframe',
|
|
60
|
+
'link',
|
|
61
|
+
'math',
|
|
62
|
+
'meta',
|
|
63
|
+
'noscript',
|
|
64
|
+
'object',
|
|
65
|
+
'portal',
|
|
66
|
+
'script',
|
|
67
|
+
'style',
|
|
68
|
+
'svg',
|
|
69
|
+
'template',
|
|
70
|
+
]);
|
|
71
|
+
const UNSAFE_ATTRIBUTES = new Set([
|
|
72
|
+
'action',
|
|
73
|
+
'dangerouslysetinnerhtml',
|
|
74
|
+
'formaction',
|
|
75
|
+
'ref',
|
|
76
|
+
'srcdoc',
|
|
77
|
+
'style',
|
|
78
|
+
]);
|
|
79
|
+
const MDX_CONTENT_LOCALS = new Set(['props']);
|
|
80
|
+
const VALUE_NAME = /^[a-z][\w$]*$/;
|
|
81
|
+
const COMPONENT_NAME = /^[A-Z]\w*$/;
|
|
82
|
+
const TAG_NAME = /^[a-z][a-z0-9]*$/;
|
|
83
|
+
const ATTRIBUTE_NAME = /^[a-zA-Z][a-zA-Z0-9-]*$/;
|
|
84
|
+
export function getKeptEsmStatements(tree) {
|
|
85
|
+
const components = new Map();
|
|
86
|
+
const kept = new Set();
|
|
87
|
+
const constantNames = new Set();
|
|
88
|
+
const esmBindings = new Set();
|
|
89
|
+
visit(tree, (node) => {
|
|
90
|
+
var _a;
|
|
91
|
+
if (!isMdxJsEsm(node) || !((_a = node.data) === null || _a === void 0 ? void 0 : _a.estree))
|
|
92
|
+
return;
|
|
93
|
+
const program = node.data.estree;
|
|
94
|
+
collectTopLevelBindings(program, esmBindings);
|
|
95
|
+
for (const statement of program.body) {
|
|
96
|
+
const constantName = getLiteralConstantName(statement);
|
|
97
|
+
if (constantName) {
|
|
98
|
+
kept.add(statement);
|
|
99
|
+
constantNames.add(constantName);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const componentName = getPureTemplateComponentName(statement);
|
|
103
|
+
if (componentName)
|
|
104
|
+
components.set(statement, componentName);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
if (components.size === 0)
|
|
108
|
+
return kept;
|
|
109
|
+
const { rejected, attributeStubNames } = scanContent(tree, new Set(components.values()), constantNames);
|
|
110
|
+
const shadowing = new Set([...esmBindings, ...attributeStubNames]);
|
|
111
|
+
for (const [statement, name] of components) {
|
|
112
|
+
if (rejected.has(name) || !usesOnlyUnshadowedBuiltIns(statement, shadowing))
|
|
113
|
+
continue;
|
|
114
|
+
kept.add(statement);
|
|
115
|
+
}
|
|
116
|
+
return kept;
|
|
117
|
+
}
|
|
118
|
+
function getLiteralConstantName(statement) {
|
|
119
|
+
var _a;
|
|
120
|
+
if (statement.type !== 'ExportNamedDeclaration')
|
|
121
|
+
return null;
|
|
122
|
+
const declaration = statement.declaration;
|
|
123
|
+
if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) !== 'VariableDeclaration' || declaration.kind !== 'const')
|
|
124
|
+
return null;
|
|
125
|
+
const declarator = declaration.declarations[0];
|
|
126
|
+
if (declaration.declarations.length !== 1 || (declarator === null || declarator === void 0 ? void 0 : declarator.id.type) !== 'Identifier')
|
|
127
|
+
return null;
|
|
128
|
+
const name = declarator.id.name;
|
|
129
|
+
if (!VALUE_NAME.test(name) || MDX_CONTENT_LOCALS.has(name))
|
|
130
|
+
return null;
|
|
131
|
+
if (((_a = declarator.init) === null || _a === void 0 ? void 0 : _a.type) !== 'Literal')
|
|
132
|
+
return null;
|
|
133
|
+
const value = declarator.init.value;
|
|
134
|
+
const isPrimitive = typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
135
|
+
return isPrimitive ? name : null;
|
|
136
|
+
}
|
|
137
|
+
function getPureTemplateComponentName(statement) {
|
|
138
|
+
var _a, _b;
|
|
139
|
+
if (statement.type !== 'ExportNamedDeclaration' || !statement.declaration)
|
|
140
|
+
return null;
|
|
141
|
+
const declaration = statement.declaration;
|
|
142
|
+
let name;
|
|
143
|
+
let fn;
|
|
144
|
+
if (declaration.type === 'FunctionDeclaration') {
|
|
145
|
+
name = (_a = declaration.id) === null || _a === void 0 ? void 0 : _a.name;
|
|
146
|
+
fn = declaration;
|
|
147
|
+
}
|
|
148
|
+
else if (declaration.type === 'VariableDeclaration' &&
|
|
149
|
+
declaration.kind === 'const' &&
|
|
150
|
+
declaration.declarations.length === 1) {
|
|
151
|
+
const declarator = declaration.declarations[0];
|
|
152
|
+
if ((declarator === null || declarator === void 0 ? void 0 : declarator.id.type) !== 'Identifier')
|
|
153
|
+
return null;
|
|
154
|
+
if (((_b = declarator.init) === null || _b === void 0 ? void 0 : _b.type) !== 'ArrowFunctionExpression')
|
|
155
|
+
return null;
|
|
156
|
+
name = declarator.id.name;
|
|
157
|
+
fn = declarator.init;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
if (!name || !COMPONENT_NAME.test(name) || TEMPLATE_BUILT_IN_COMPONENTS.has(name))
|
|
163
|
+
return null;
|
|
164
|
+
if (fn.async || fn.generator)
|
|
165
|
+
return null;
|
|
166
|
+
const params = getParamNames(fn.params);
|
|
167
|
+
if (!params)
|
|
168
|
+
return null;
|
|
169
|
+
const root = getReturnedExpression(fn);
|
|
170
|
+
if ((root === null || root === void 0 ? void 0 : root.type) !== 'JSXElement' && (root === null || root === void 0 ? void 0 : root.type) !== 'JSXFragment')
|
|
171
|
+
return null;
|
|
172
|
+
return isPureElement(root, params) ? name : null;
|
|
173
|
+
}
|
|
174
|
+
function getParamNames(params) {
|
|
175
|
+
const names = new Set();
|
|
176
|
+
if (params.length === 0)
|
|
177
|
+
return names;
|
|
178
|
+
const param = params[0];
|
|
179
|
+
if (params.length > 1 || (param === null || param === void 0 ? void 0 : param.type) !== 'ObjectPattern')
|
|
180
|
+
return null;
|
|
181
|
+
for (const property of param.properties) {
|
|
182
|
+
if (property.type !== 'Property' || property.computed || !property.shorthand)
|
|
183
|
+
return null;
|
|
184
|
+
if (property.key.type !== 'Identifier' || property.value.type !== 'Identifier')
|
|
185
|
+
return null;
|
|
186
|
+
const name = property.key.name;
|
|
187
|
+
if (property.value.name !== name || !VALUE_NAME.test(name) || name in Object.prototype) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
names.add(name);
|
|
191
|
+
}
|
|
192
|
+
return names;
|
|
193
|
+
}
|
|
194
|
+
function getReturnedExpression(fn) {
|
|
195
|
+
var _a;
|
|
196
|
+
if (fn.body.type !== 'BlockStatement')
|
|
197
|
+
return fn.body;
|
|
198
|
+
const statement = fn.body.body[0];
|
|
199
|
+
if (fn.body.body.length !== 1 || (statement === null || statement === void 0 ? void 0 : statement.type) !== 'ReturnStatement')
|
|
200
|
+
return null;
|
|
201
|
+
return (_a = statement.argument) !== null && _a !== void 0 ? _a : null;
|
|
202
|
+
}
|
|
203
|
+
function isPureElement(node, params) {
|
|
204
|
+
if (node.type === 'JSXElement') {
|
|
205
|
+
const { name, attributes } = node.openingElement;
|
|
206
|
+
if (name.type !== 'JSXIdentifier' || !isSafeElementName(name.name))
|
|
207
|
+
return false;
|
|
208
|
+
if (!attributes.every((attribute) => isPureAttribute(attribute, params)))
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return node.children.every((child) => isPureChild(child, params));
|
|
212
|
+
}
|
|
213
|
+
function isSafeElementName(name) {
|
|
214
|
+
if (TEMPLATE_BUILT_IN_COMPONENTS.has(name))
|
|
215
|
+
return true;
|
|
216
|
+
return TAG_NAME.test(name) && !UNSAFE_TAGS.has(name);
|
|
217
|
+
}
|
|
218
|
+
function isPureAttribute(attribute, params) {
|
|
219
|
+
if (attribute.type !== 'JSXAttribute' || attribute.name.type !== 'JSXIdentifier')
|
|
220
|
+
return false;
|
|
221
|
+
const name = attribute.name.name;
|
|
222
|
+
if (!ATTRIBUTE_NAME.test(name) ||
|
|
223
|
+
/^on/i.test(name) ||
|
|
224
|
+
UNSAFE_ATTRIBUTES.has(name.toLowerCase())) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
const value = attribute.value;
|
|
228
|
+
if (value === null)
|
|
229
|
+
return true;
|
|
230
|
+
if (value.type === 'Literal')
|
|
231
|
+
return typeof value.value === 'string';
|
|
232
|
+
if (value.type !== 'JSXExpressionContainer')
|
|
233
|
+
return false;
|
|
234
|
+
if (value.expression.type === 'JSXEmptyExpression')
|
|
235
|
+
return false;
|
|
236
|
+
return isPureValue(value.expression, params);
|
|
237
|
+
}
|
|
238
|
+
function isPureChild(child, params) {
|
|
239
|
+
switch (child.type) {
|
|
240
|
+
case 'JSXText':
|
|
241
|
+
return true;
|
|
242
|
+
case 'JSXElement':
|
|
243
|
+
case 'JSXFragment':
|
|
244
|
+
return isPureElement(child, params);
|
|
245
|
+
case 'JSXExpressionContainer':
|
|
246
|
+
return (child.expression.type === 'JSXEmptyExpression' || isPureValue(child.expression, params));
|
|
247
|
+
default:
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function isPureValue(node, params) {
|
|
252
|
+
switch (node.type) {
|
|
253
|
+
case 'Literal':
|
|
254
|
+
return typeof node.value === 'string' || typeof node.value === 'number';
|
|
255
|
+
case 'Identifier':
|
|
256
|
+
return params.has(node.name);
|
|
257
|
+
case 'TemplateLiteral':
|
|
258
|
+
return node.expressions.every((expression) => expression.type === 'Identifier' && params.has(expression.name));
|
|
259
|
+
default:
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function scanContent(tree, candidates, constantNames) {
|
|
264
|
+
const rejected = new Set();
|
|
265
|
+
const attributeStubNames = new Set();
|
|
266
|
+
visit(tree, (node) => {
|
|
267
|
+
var _a, _b, _c;
|
|
268
|
+
if (isMdxJsEsm(node))
|
|
269
|
+
return;
|
|
270
|
+
if ((node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') &&
|
|
271
|
+
'name' in node &&
|
|
272
|
+
typeof node.name === 'string') {
|
|
273
|
+
const memberRoot = node.name.split('.')[0];
|
|
274
|
+
if (memberRoot && memberRoot !== node.name && candidates.has(memberRoot)) {
|
|
275
|
+
rejected.add(memberRoot);
|
|
276
|
+
}
|
|
277
|
+
if (candidates.has(node.name) && 'attributes' in node) {
|
|
278
|
+
const hasOnlyLiteralAttributes = node.attributes.every((attribute) => {
|
|
279
|
+
var _a;
|
|
280
|
+
return attribute.type === 'mdxJsxAttribute' &&
|
|
281
|
+
(attribute.value == null ||
|
|
282
|
+
typeof attribute.value === 'string' ||
|
|
283
|
+
isConstantReference((_a = attribute.value.data) === null || _a === void 0 ? void 0 : _a.estree, constantNames));
|
|
284
|
+
});
|
|
285
|
+
if (!hasOnlyLiteralAttributes)
|
|
286
|
+
rejected.add(node.name);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const referenced = new Set();
|
|
290
|
+
if ('data' in node && isRecord(node.data) && 'estree' in node.data) {
|
|
291
|
+
collectIdentifierNames(node.data.estree, referenced);
|
|
292
|
+
}
|
|
293
|
+
if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
|
|
294
|
+
for (const attribute of node.attributes) {
|
|
295
|
+
collectIdentifierNames(attribute, referenced);
|
|
296
|
+
if (attribute.type !== 'mdxJsxAttribute' || typeof attribute.value !== 'object')
|
|
297
|
+
continue;
|
|
298
|
+
const stubName = extractIdentifierRef((_c = (_b = (_a = attribute.value) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.estree) !== null && _c !== void 0 ? _c : undefined);
|
|
299
|
+
if (stubName)
|
|
300
|
+
attributeStubNames.add(stubName);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const name of referenced) {
|
|
304
|
+
if (candidates.has(name))
|
|
305
|
+
rejected.add(name);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
return { rejected, attributeStubNames };
|
|
309
|
+
}
|
|
310
|
+
function isConstantReference(program, constantNames) {
|
|
311
|
+
const statement = program === null || program === void 0 ? void 0 : program.body[0];
|
|
312
|
+
if ((program === null || program === void 0 ? void 0 : program.body.length) !== 1 || (statement === null || statement === void 0 ? void 0 : statement.type) !== 'ExpressionStatement')
|
|
313
|
+
return false;
|
|
314
|
+
return statement.expression.type === 'Identifier' && constantNames.has(statement.expression.name);
|
|
315
|
+
}
|
|
316
|
+
// Returns the identifier name if the estree expression is a bare reference that will be kept
|
|
317
|
+
// by isStringSafe but needs a stub to avoid a ReferenceError at render time:
|
|
318
|
+
// {foo} → Identifier
|
|
319
|
+
// {!!foo} → UnaryExpression(! UnaryExpression(! Identifier))
|
|
320
|
+
// Parenthesised forms are transparent in ESTree so (foo) produces the same AST as foo.
|
|
321
|
+
export function extractIdentifierRef(estree) {
|
|
322
|
+
const stmt = estree === null || estree === void 0 ? void 0 : estree.body[0];
|
|
323
|
+
if ((stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
|
|
324
|
+
return null;
|
|
325
|
+
const expr = stmt.expression;
|
|
326
|
+
if (expr.type === 'Identifier')
|
|
327
|
+
return expr.name;
|
|
328
|
+
if (expr.type === 'UnaryExpression' &&
|
|
329
|
+
expr.operator === '!' &&
|
|
330
|
+
expr.argument.type === 'UnaryExpression' &&
|
|
331
|
+
expr.argument.operator === '!' &&
|
|
332
|
+
expr.argument.argument.type === 'Identifier')
|
|
333
|
+
return expr.argument.argument.name;
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function usesOnlyUnshadowedBuiltIns(statement, shadowing) {
|
|
337
|
+
const elementNames = new Set();
|
|
338
|
+
collectJsxElementNames(statement, elementNames);
|
|
339
|
+
for (const name of elementNames) {
|
|
340
|
+
if (/^[A-Z]/.test(name) && shadowing.has(name))
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
function collectTopLevelBindings(program, out) {
|
|
346
|
+
for (const statement of program.body) {
|
|
347
|
+
if (statement.type === 'ImportDeclaration') {
|
|
348
|
+
for (const specifier of statement.specifiers)
|
|
349
|
+
out.add(specifier.local.name);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if ((statement.type !== 'ExportNamedDeclaration' &&
|
|
353
|
+
statement.type !== 'ExportDefaultDeclaration') ||
|
|
354
|
+
!statement.declaration) {
|
|
355
|
+
collectIdentifierNames(statement, out, ['Identifier']);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const declaration = statement.declaration;
|
|
359
|
+
if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'VariableDeclaration') {
|
|
360
|
+
for (const declarator of declaration.declarations) {
|
|
361
|
+
collectIdentifierNames(declarator.id, out, ['Identifier']);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else if (((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'FunctionDeclaration' || (declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'ClassDeclaration') &&
|
|
365
|
+
declaration.id) {
|
|
366
|
+
out.add(declaration.id.name);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function collectIdentifierNames(value, out, types = ['Identifier', 'JSXIdentifier']) {
|
|
371
|
+
walkEstree(value, (node) => {
|
|
372
|
+
if (typeof node.type === 'string' &&
|
|
373
|
+
types.includes(node.type) &&
|
|
374
|
+
typeof node.name === 'string') {
|
|
375
|
+
out.add(node.name);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function collectJsxElementNames(value, out) {
|
|
380
|
+
walkEstree(value, (node) => {
|
|
381
|
+
if (node.type !== 'JSXOpeningElement' || !('name' in node) || !isRecord(node.name))
|
|
382
|
+
return;
|
|
383
|
+
if (node.name.type === 'JSXIdentifier' && typeof node.name.name === 'string') {
|
|
384
|
+
out.add(node.name.name);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
function walkEstree(value, onNode) {
|
|
389
|
+
if (Array.isArray(value)) {
|
|
390
|
+
for (const item of value)
|
|
391
|
+
walkEstree(item, onNode);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (!isRecord(value))
|
|
395
|
+
return;
|
|
396
|
+
if (typeof value.type === 'string')
|
|
397
|
+
onNode(value);
|
|
398
|
+
for (const [key, child] of Object.entries(value)) {
|
|
399
|
+
if (key === 'loc' || key === 'range' || key === 'position')
|
|
400
|
+
continue;
|
|
401
|
+
walkEstree(child, onNode);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function isRecord(value) {
|
|
405
|
+
return typeof value === 'object' && value !== null;
|
|
406
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { jsx as jsxHandlers, toJs } from 'estree-util-to-js';
|
|
1
2
|
import { remove } from 'unist-util-remove';
|
|
2
3
|
import { CONTINUE, visit } from 'unist-util-visit';
|
|
3
4
|
import { UNSAFE_OBJECT_KEYS } from '../../static-object-keys.js';
|
|
4
5
|
import { isMdxJsEsm } from '../../utils.js';
|
|
6
|
+
import { extractIdentifierRef, getKeptEsmStatements } from './pureTemplateComponents.js';
|
|
5
7
|
const objectEstree = {
|
|
6
8
|
type: 'Program',
|
|
7
9
|
body: [
|
|
@@ -402,26 +404,6 @@ function collectIdentifiersFromPattern(pattern, out) {
|
|
|
402
404
|
collectIdentifiersFromPattern(pattern.argument, out);
|
|
403
405
|
}
|
|
404
406
|
}
|
|
405
|
-
// Returns the identifier name if the estree expression is a bare reference that will be kept
|
|
406
|
-
// by isStringSafe but needs a stub to avoid a ReferenceError at render time:
|
|
407
|
-
// {foo} → Identifier
|
|
408
|
-
// {!!foo} → UnaryExpression(! UnaryExpression(! Identifier))
|
|
409
|
-
// Parenthesised forms are transparent in ESTree so (foo) produces the same AST as foo.
|
|
410
|
-
function extractIdentifierRef(estree) {
|
|
411
|
-
const stmt = estree === null || estree === void 0 ? void 0 : estree.body[0];
|
|
412
|
-
if ((stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
|
|
413
|
-
return null;
|
|
414
|
-
const expr = stmt.expression;
|
|
415
|
-
if (expr.type === 'Identifier')
|
|
416
|
-
return expr.name;
|
|
417
|
-
if (expr.type === 'UnaryExpression' &&
|
|
418
|
-
expr.operator === '!' &&
|
|
419
|
-
expr.argument.type === 'UnaryExpression' &&
|
|
420
|
-
expr.argument.operator === '!' &&
|
|
421
|
-
expr.argument.argument.type === 'Identifier')
|
|
422
|
-
return expr.argument.argument.name;
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
407
|
function collectExportedNames(estree) {
|
|
426
408
|
var _a, _b;
|
|
427
409
|
const names = [];
|
|
@@ -690,19 +672,43 @@ function sanitizer(edit) {
|
|
|
690
672
|
});
|
|
691
673
|
}
|
|
692
674
|
edit.remove(tree, (node) => isMdxExpression(node) && !isInertExpression(node));
|
|
675
|
+
const keptStatements = getKeptEsmStatements(tree);
|
|
676
|
+
const keptNames = new Set(collectExportedNames({
|
|
677
|
+
type: 'Program',
|
|
678
|
+
body: [...keptStatements],
|
|
679
|
+
sourceType: 'module',
|
|
680
|
+
}).map(({ name }) => name));
|
|
693
681
|
// Collect exported names before stripping ESM so we can inject stubs.
|
|
694
682
|
// Map keyed by name deduplicates naturally.
|
|
695
683
|
const stubbedNamesMap = new Map();
|
|
696
|
-
|
|
684
|
+
const partiallyKept = [];
|
|
685
|
+
visit(tree, (node, index, parent) => {
|
|
697
686
|
var _a;
|
|
698
|
-
if (isMdxJsEsm(node)
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
687
|
+
if (!isMdxJsEsm(node) || !((_a = node.data) === null || _a === void 0 ? void 0 : _a.estree))
|
|
688
|
+
return;
|
|
689
|
+
const program = node.data.estree;
|
|
690
|
+
const stripped = program.body.filter((statement) => !keptStatements.has(statement));
|
|
691
|
+
for (const entry of collectExportedNames(Object.assign(Object.assign({}, program), { body: stripped }))) {
|
|
692
|
+
if (!stubbedNamesMap.has(entry.name))
|
|
693
|
+
stubbedNamesMap.set(entry.name, entry);
|
|
694
|
+
}
|
|
695
|
+
if (stripped.length > 0 && stripped.length < program.body.length && parent && index != null) {
|
|
696
|
+
const body = program.body.filter((statement) => keptStatements.has(statement));
|
|
697
|
+
partiallyKept.push({ parent, index, program: Object.assign(Object.assign({}, program), { body, comments: [] }) });
|
|
703
698
|
}
|
|
704
699
|
});
|
|
705
|
-
|
|
700
|
+
for (const { parent, index, program } of partiallyKept) {
|
|
701
|
+
edit.replaceChild(parent, index, {
|
|
702
|
+
type: 'mdxjsEsm',
|
|
703
|
+
value: toJs(program, { handlers: jsxHandlers }).value,
|
|
704
|
+
data: { estree: program },
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
edit.remove(tree, (node) => {
|
|
708
|
+
var _a, _b;
|
|
709
|
+
return isMdxJsEsm(node) &&
|
|
710
|
+
!((_b = (_a = node.data) === null || _a === void 0 ? void 0 : _a.estree) === null || _b === void 0 ? void 0 : _b.body.some((statement) => keptStatements.has(statement)));
|
|
711
|
+
});
|
|
706
712
|
// Sanitize JSX attributes. Any bare identifier that survives is added to stubbedNamesMap
|
|
707
713
|
// so it gets a stub even if its import was already stripped by a prior plugin.
|
|
708
714
|
visit(tree, (node) => {
|
|
@@ -717,7 +723,7 @@ function sanitizer(edit) {
|
|
|
717
723
|
if (attr.type === 'mdxJsxAttribute' && attr.value instanceof Object) {
|
|
718
724
|
if (isStringSafe(attr.value.value)) {
|
|
719
725
|
const ident = extractIdentifierRef((_a = attr.value.data) === null || _a === void 0 ? void 0 : _a.estree);
|
|
720
|
-
if (ident && !stubbedNamesMap.has(ident))
|
|
726
|
+
if (ident && !stubbedNamesMap.has(ident) && !keptNames.has(ident))
|
|
721
727
|
stubbedNamesMap.set(ident, { name: ident, isFunction: false });
|
|
722
728
|
return attr;
|
|
723
729
|
}
|
|
@@ -270,6 +270,29 @@ describe(filterDivisions, () => {
|
|
|
270
270
|
});
|
|
271
271
|
});
|
|
272
272
|
});
|
|
273
|
+
describe('filterDivisions with regional languages', () => {
|
|
274
|
+
it('matches the same page in a regional language whose prefix is lower case', () => {
|
|
275
|
+
const page = (href) => ({ title: href, href });
|
|
276
|
+
const result = filterDivisions({
|
|
277
|
+
currentPath: '/quickstart',
|
|
278
|
+
userGroups: new Set(),
|
|
279
|
+
currentVersion: undefined,
|
|
280
|
+
currentLanguage: 'en',
|
|
281
|
+
shouldUseDivisionMatch: false,
|
|
282
|
+
cache: { dropdownCache: new Map() },
|
|
283
|
+
decoratedNav: {
|
|
284
|
+
languages: [
|
|
285
|
+
{ language: 'en', default: true, pages: [page('/index'), page('/quickstart')] },
|
|
286
|
+
{ language: 'en-GB', pages: [page('/en-gb/index'), page('/en-gb/quickstart')] },
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
expect(result.firstHrefInLanguage.get('en-GB')).toEqual({
|
|
291
|
+
href: '/en-gb/quickstart',
|
|
292
|
+
matchLevel: DivisionMatchLevel.PATH,
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
273
296
|
describe('filterDivisions with partial auth', () => {
|
|
274
297
|
const defaultArgs = {
|
|
275
298
|
decoratedNav: { groups: [] },
|
|
@@ -9,6 +9,23 @@ describe('pathsDifferByOneSegment', () => {
|
|
|
9
9
|
expect(pathsDifferByOneSegment('/ja/getting-started', '/es/getting-started', 'language')).toBe(true);
|
|
10
10
|
expect(pathsDifferByOneSegment('/fr/docs/intro', '/de/docs/intro', 'language')).toBe(true);
|
|
11
11
|
});
|
|
12
|
+
it('treats configured regional language prefixes as locales, ignoring case', () => {
|
|
13
|
+
const languages = ['en', 'en-GB', 'es', 'es-MX'];
|
|
14
|
+
expect(pathsDifferByOneSegment('/en-gb/quickstart', '/quickstart', 'language', languages)).toBe(true);
|
|
15
|
+
expect(pathsDifferByOneSegment('/es-MX/quickstart', '/es/quickstart', 'language', languages)).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it('treats a configured regional language after a version segment as a locale', () => {
|
|
18
|
+
expect(pathsDifferByOneSegment('/v2/en-gb/quickstart', '/v2/quickstart', 'language', [
|
|
19
|
+
'en',
|
|
20
|
+
'en-GB',
|
|
21
|
+
])).toBe(true);
|
|
22
|
+
expect(pathsDifferByOneSegment('/v2/hi-fi/quickstart', '/v2/quickstart', 'language')).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
it('does not treat unconfigured code-like segments as locales', () => {
|
|
25
|
+
expect(pathsDifferByOneSegment('/en-gb/quickstart', '/quickstart', 'language')).toBe(false);
|
|
26
|
+
expect(pathsDifferByOneSegment('/hi-fi/setup', '/de/hi-fi/setup', 'language', ['en', 'de'])).toBe(true);
|
|
27
|
+
expect(pathsDifferByOneSegment('/hi-fi/setup', '/de/setup', 'language', ['en', 'de'])).toBe(false);
|
|
28
|
+
});
|
|
12
29
|
it('returns false for paths with different content', () => {
|
|
13
30
|
expect(pathsDifferByOneSegment('/ja/getting-started', '/ja/quickstart', 'language')).toBe(false);
|
|
14
31
|
expect(pathsDifferByOneSegment('/getting-started', '/quickstart', 'language')).toBe(false);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LocaleType } from '@mintlify/models';
|
|
2
|
-
import type
|
|
2
|
+
import { type DecoratedNavigationConfig, type DocsConfig } from '@mintlify/validation';
|
|
3
3
|
import { DivisionMatch } from './updateFirstHrefInDivision.js';
|
|
4
4
|
export declare function scopeDecoratedNavToPath(nav: DecoratedNavigationConfig, currentPath: string, userGroups?: Set<string>): DecoratedNavigationConfig;
|
|
5
5
|
export declare function scopeDocsConfigNavToPath(docsConfig: DocsConfig, currentPath: string): DocsConfig;
|
|
@@ -9,7 +9,8 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
12
|
+
import { isLocale } from '@mintlify/models';
|
|
13
|
+
import { divisions, } from '@mintlify/validation';
|
|
13
14
|
import { generatePathToLanguageDict, generatePathToVersionDict } from '../divisions/index.js';
|
|
14
15
|
import { optionallyRemoveLeadingSlash } from '../fs/optionallySlash.js';
|
|
15
16
|
import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
@@ -49,6 +50,31 @@ const STUB_STRIPPED_KEYS = new Set([
|
|
|
49
50
|
function isNavNode(value) {
|
|
50
51
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
52
|
}
|
|
53
|
+
const LANGUAGE_CONTAINER_KEYS = [...divisions, 'global'];
|
|
54
|
+
function addConfiguredLanguages(node, languages) {
|
|
55
|
+
if (Array.isArray(node)) {
|
|
56
|
+
for (const entry of node)
|
|
57
|
+
addConfiguredLanguages(entry, languages);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!isNavNode(node))
|
|
61
|
+
return;
|
|
62
|
+
if (typeof node.language === 'string' && isLocale(node.language))
|
|
63
|
+
languages.add(node.language);
|
|
64
|
+
for (const key of LANGUAGE_CONTAINER_KEYS)
|
|
65
|
+
addConfiguredLanguages(node[key], languages);
|
|
66
|
+
}
|
|
67
|
+
const configuredLanguagesCache = new WeakMap();
|
|
68
|
+
function getConfiguredLanguages(nav) {
|
|
69
|
+
const cached = configuredLanguagesCache.get(nav);
|
|
70
|
+
if (cached)
|
|
71
|
+
return cached;
|
|
72
|
+
const languages = new Set();
|
|
73
|
+
addConfiguredLanguages(nav, languages);
|
|
74
|
+
const result = [...languages];
|
|
75
|
+
configuredLanguagesCache.set(nav, result);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
52
78
|
function isScopedDivisionKey(key) {
|
|
53
79
|
return key === 'versions' || key === 'languages';
|
|
54
80
|
}
|
|
@@ -143,7 +169,7 @@ function pickStubTarget(division, ctx, type) {
|
|
|
143
169
|
const exact = pool.find((candidate) => isEqualIgnoringLeadingSlash(candidate.href, ctx.currentPath));
|
|
144
170
|
if (exact)
|
|
145
171
|
return exact;
|
|
146
|
-
const pathMatch = pool.find((candidate) => pathsDifferByOneSegment(candidate.href, ctx.currentPath, type));
|
|
172
|
+
const pathMatch = pool.find((candidate) => pathsDifferByOneSegment(candidate.href, ctx.currentPath, type, ctx.languages));
|
|
147
173
|
return pathMatch !== null && pathMatch !== void 0 ? pathMatch : pool[0];
|
|
148
174
|
}
|
|
149
175
|
// carrying the target's hidden/groups keeps a division with no anonymously
|
|
@@ -517,13 +543,18 @@ function scopeNode(node, ctx) {
|
|
|
517
543
|
return scoped;
|
|
518
544
|
}
|
|
519
545
|
export function scopeDecoratedNavToPath(nav, currentPath, userGroups) {
|
|
546
|
+
const ctx = { currentPath, userGroups, languages: getConfiguredLanguages(nav) };
|
|
520
547
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- structural nav traversal over the zod union types
|
|
521
|
-
return scopeNode(nav,
|
|
548
|
+
return scopeNode(nav, ctx);
|
|
522
549
|
}
|
|
523
550
|
export function scopeDocsConfigNavToPath(docsConfig, currentPath) {
|
|
551
|
+
const ctx = {
|
|
552
|
+
currentPath,
|
|
553
|
+
languages: getConfiguredLanguages(docsConfig.navigation),
|
|
554
|
+
};
|
|
524
555
|
return Object.assign(Object.assign({}, docsConfig), {
|
|
525
556
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- structural nav traversal over the zod union types
|
|
526
|
-
navigation: scopeNode(docsConfig.navigation,
|
|
557
|
+
navigation: scopeNode(docsConfig.navigation, ctx) });
|
|
527
558
|
}
|
|
528
559
|
const pathToVersionDictCache = new WeakMap();
|
|
529
560
|
const pathToLanguageDictCache = new WeakMap();
|
|
@@ -571,7 +602,7 @@ export function getScopedNavForPath({ decoratedNav, currentPath, userGroups = ne
|
|
|
571
602
|
const strippedPath = optionallyRemoveLeadingSlash(currentPath);
|
|
572
603
|
const dictVersion = cachedPathToVersionDict(decoratedNav).get(strippedPath);
|
|
573
604
|
const dictLanguage = cachedPathToLanguageDict(decoratedNav).get(strippedPath);
|
|
574
|
-
const localeFromDict =
|
|
605
|
+
const localeFromDict = dictLanguage && isLocale(dictLanguage) ? dictLanguage : undefined;
|
|
575
606
|
currentVersion =
|
|
576
607
|
dictVersion ||
|
|
577
608
|
getVersionOrLanguageFromPath('version', decoratedNav, currentPath, defaultVersion, (_d = (_c = initial.versions[0]) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : '') ||
|
|
@@ -596,6 +627,7 @@ export function getScopedNavForPath({ decoratedNav, currentPath, userGroups = ne
|
|
|
596
627
|
currentPath,
|
|
597
628
|
currentVersion,
|
|
598
629
|
currentLanguage,
|
|
630
|
+
languages: getConfiguredLanguages(decoratedNav),
|
|
599
631
|
versionTargets: filtered.firstHrefInVersion,
|
|
600
632
|
languageTargets: filtered.firstHrefInLanguage,
|
|
601
633
|
userGroups,
|