Files
2026-01-19 19:13:27 +01:00

219 lines
7.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { name as isIdentifierName } from 'estree-util-is-identifier-name';
import { createVisitors } from 'estree-util-scope';
import { walk } from 'estree-walker';
/**
* @param program
* The ESTree program to scan.
* @param file
* The {@link VFile} to emit warnings to.
* @param variables
* The variables that should be injected.
* @param options
* {@link define}.options
* @returns
* The position in the body where the export may be injected.
*/
function scan(program, file, variables, options) {
const visitors = createVisitors();
const [scope] = visitors.scopes;
const identifiers = new Map();
let injectIndex = 0;
walk(program, {
enter(node, parent) {
visitors.enter(node);
switch (node.type) {
case 'Identifier':
if (scope.defined.includes(node.name) && !identifiers.has(node.name)) {
identifiers.set(node.name, node);
}
break;
case 'ArrowFunctionExpression':
case 'ClassDeclaration':
case 'ClassExpression':
case 'FunctionExpression':
case 'FunctionDeclaration':
this.skip();
break;
// Dont insert before directives.
case 'ExpressionStatement':
if (parent === program &&
node.expression.type === 'Literal' &&
typeof node.expression.value === 'string') {
injectIndex = program.body.indexOf(node) + 1;
}
break;
default:
}
},
leave: visitors.exit
});
for (const name of scope.defined) {
if (variables.has(name)) {
if (options?.conflict !== 'skip') {
const identifier = identifiers.get(name);
const message = file.message(`Variable name conflict: ${name}`, {
place: identifier?.loc,
ruleId: 'conflict',
source: 'unist-util-mdx-define'
});
message.url = 'https://github.com/remcohaszing/unist-util-mdx-define';
if (options?.conflict !== 'warn') {
message.fatal = true;
throw message;
}
}
variables.delete(name);
}
}
return injectIndex;
}
/**
* Generate an export named declaration.
*
* @param variables
* The variables for which to generate an declaration.
* @param options
* {@link define} options
* @param returnStatement
* The return statement of the program to inject into.
* @returns
* The export named declaration.
*/
function generate(variables, options, returnStatement) {
if (options?.export === 'namespace') {
const statements = [];
for (const [name, right] of variables) {
const isIdentifier = isIdentifierName(name);
statements.push({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
left: {
type: 'MemberExpression',
computed: !isIdentifier,
object: { type: 'Identifier', name: 'MDXContent' },
optional: false,
property: isIdentifier ? { type: 'Identifier', name } : { type: 'Literal', value: name }
},
operator: '=',
right
}
});
}
return statements;
}
const declarations = [];
for (const [name, init] of variables) {
declarations.push({
type: 'VariableDeclaration',
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
id: { type: 'Identifier', name },
init
}
]
});
}
if (options?.export === false) {
return declarations;
}
if (!returnStatement) {
return declarations.map((declaration) => ({
type: 'ExportNamedDeclaration',
declaration,
specifiers: []
}));
}
if (returnStatement.argument?.type === 'ObjectExpression') {
returnStatement.argument.properties.splice(-1, 0, ...Array.from(variables.keys(), (name) => ({
type: 'Property',
computed: false,
kind: 'init',
method: false,
shorthand: true,
key: { type: 'Identifier', name },
value: { type: 'Identifier', name }
})));
}
return declarations;
}
/**
* Define variables in an MDX related AST.
*
* @param ast
* The AST in which to define an export
* @param file
* The {@link VFile} to emit warnings to.
* @param variables
* A mapping of variables to define. They keys are the names. The values are the ESTree expression
* to represent them.
* @param options
* Additional options to configure behaviour.
*/
export function define(ast, file, variables, options) {
const map = new Map(Object.entries(variables));
if (options?.export !== 'namespace') {
for (const name of map.keys()) {
if (name === '_createMdxContent' ||
name === '_Fragment' ||
name === '_jsx' ||
name === '_jsxs' ||
name === '_missingMdxReference' ||
name === 'MDXContent') {
const message = file.message(`MDX internal name conflict: ${name}`, {
ruleId: 'internal',
source: 'unist-util-mdx-define'
});
message.url = 'https://github.com/remcohaszing/unist-util-mdx-define';
message.fatal = true;
throw message;
}
if (!isIdentifierName(name)) {
const message = file.message(`Invalid identifier name: ${name}`, {
ruleId: 'invalid-identifier',
source: 'unist-util-mdx-define'
});
message.url = 'https://github.com/remcohaszing/unist-util-mdx-define';
message.fatal = true;
throw message;
}
}
}
if (ast.type === 'root') {
for (const child of ast.children) {
if (child.type !== 'mdxjsEsm') {
continue;
}
const program = child.data?.estree;
/* c8 ignore start */
if (!program) {
continue;
}
/* c8 ignore stop */
scan(program, file, map, options);
}
if (map.size) {
ast.children.unshift({
type: 'mdxjsEsm',
value: '',
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: generate(map, options)
}
}
});
}
}
else {
const returnStatement = ast.body.find((node) => node.type === 'ReturnStatement');
const injectIndex = scan(ast, file, map, options);
if (map.size) {
ast.body.splice(injectIndex, 0, ...generate(map, options, returnStatement));
}
}
}
//# sourceMappingURL=unist-util-mdx-define.js.map