init
This commit is contained in:
18
node_modules/unist-util-mdx-define/LICENSE.md
generated
vendored
Normal file
18
node_modules/unist-util-mdx-define/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# MIT License
|
||||
|
||||
Copyright © 2025 Remco Haszing
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the “Software”), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
129
node_modules/unist-util-mdx-define/README.md
generated
vendored
Normal file
129
node_modules/unist-util-mdx-define/README.md
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
# unist-util-mdx-define
|
||||
|
||||
[](https://github.com/remcohaszing/unist-util-mdx-define/actions/workflows/ci.yaml)
|
||||
[](https://codecov.io/gh/remcohaszing/unist-util-mdx-define)
|
||||
[](https://www.npmjs.com/package/unist-util-mdx-define)
|
||||
[](https://www.npmjs.com/package/unist-util-mdx-define)
|
||||
|
||||
A [unist](https://github.com/syntax-tree/unist) utility to define [MDX](https://mdxjs.com) exports.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [`define`](#define)
|
||||
- [Compatibility](#compatibility)
|
||||
- [Related projects](#related-projects)
|
||||
- [License](#license)
|
||||
|
||||
## Introduction
|
||||
|
||||
This package provides a utility to define exports in an [MDX](https://mdxjs.com) AST, so you don’t
|
||||
have to worry about the details. It supports [mdast](https://github.com/syntax-tree/mdast)
|
||||
([remark](https://github.com/remarkjs)), [hast](https://github.com/syntax-tree/hast)
|
||||
([rehype](https://github.com/rehypejs)), and [estree](https://github.com/estree/estree) /
|
||||
[esast](https://github.com/syntax-tree/esast) ([recma](https://github.com/mdx-js/recma)). This
|
||||
package supports various options to export the variables you define and to handle name conflicts.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install unist-util-mdx-define
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Typically this is used with an MDX plugin.
|
||||
|
||||
```ts
|
||||
import { compile } from '@mdx-js/mdx'
|
||||
import type * as estree from 'estree'
|
||||
import type * as hast from 'hast'
|
||||
import type * as mdast from 'mdast'
|
||||
import { type Plugin } from 'unified'
|
||||
import { define } from 'unist-util-mdx-define'
|
||||
|
||||
const yourRemarkMdxPlugin: Plugin<[], mdast.Root> = () => (ast, file) => {
|
||||
define(ast, file, { remarkVariable: { type: 'Literal', value: 'Hello remark plugin!' } })
|
||||
}
|
||||
|
||||
const yourRehypeMdxPlugin: Plugin<[], hast.Root> = () => (ast, file) => {
|
||||
define(ast, file, { rehypeVariable: { type: 'Literal', value: 'Hello rehype plugin!' } })
|
||||
}
|
||||
|
||||
const yourRecmaMdxPlugin: Plugin<[], estree.Program> = () => (ast, file) => {
|
||||
define(ast, file, { recmaVariable: { type: 'Literal', value: 'Hello recma plugin!' } })
|
||||
}
|
||||
|
||||
const { value } = await compile('{remarkVariable} {rehypeVariable} {recmaVariable}', {
|
||||
remarkPlugins: [yourRemarkMdxPlugin],
|
||||
rehypePlugins: [yourRehypeMdxPlugin],
|
||||
recmaPlugins: [yourRecmaMdxPlugin]
|
||||
})
|
||||
|
||||
console.log(value)
|
||||
```
|
||||
|
||||
MDX remark, rehype, and recma plugins are similar, but not the same. The type of plugin you should
|
||||
create depends on your goal.
|
||||
|
||||
If your goal is to handle something specific to the markdown content, you should write a remark
|
||||
plugin. A practical example is
|
||||
[`remark-mdx-frontmatter`](https://github.com/remcohaszing/remark-mdx-frontmatter). This plugin
|
||||
handles frontmatter data, which no longer exists after the mdast is compiled to hast.
|
||||
|
||||
If your goal is to transform or access content, you typically want a rehype plugin. A good example
|
||||
is [`rehype-mdx-title`](https://github.com/remcohaszing/rehype-mdx-title), which accesses the title
|
||||
of the document.
|
||||
|
||||
For most other purposes, use a recma plugin. For example,
|
||||
[`recma-export-filepath`](https://github.com/remcohaszing/recma-export-filepath) exposes file
|
||||
information. This doesn’t need access to the content.
|
||||
|
||||
`unist-util-mdx-define` can define variables in any of these AST types. For mdast and hast, it
|
||||
prepends the variable declarations to the root. This way they’ll end up at the start of the module,
|
||||
and their value can be used by **user defined** expressions. This does mean the **generated**
|
||||
expressions are not able to use other variables. For ESTree, `unist-util-mdx-define` attempts to do
|
||||
the same.
|
||||
|
||||
## API
|
||||
|
||||
### `define`
|
||||
|
||||
Define variables in an MDX related AST.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `ast` ([mdast.Root](https://github.com/syntax-tree/mdast#root) |
|
||||
[hast.Root](https://github.com/syntax-tree/hast#root) |
|
||||
[estree.Program](https://github.com/estree/estree)) — The AST in which to define an export.
|
||||
- `file` ([VFile](https://github.com/vfile/vfile)) — The file to emit warnings to.
|
||||
- `variables` (`Record<string, estree.Expression>`) — A mapping of variables to define. They keys
|
||||
are the names. The values are the ESTree expression to represent them.
|
||||
- `options` ([Options](#options)) — Additional options to configure behaviour.
|
||||
|
||||
#### Options
|
||||
|
||||
- `export` — If and how to export the variable. (Default: `'module'`)
|
||||
- `'module'`: Export the value using an ESM const export declaration.
|
||||
- `'namespace'`: Attach the value as a property on `MDXContent`.
|
||||
- `false`: Define the variable locally, but don’t export it.
|
||||
- `conflict` — What to do if there’s a name conflict. (Default: `'throw'`)
|
||||
- `'skip'`: Don’t insert the variable if there’s a name conflict.
|
||||
- `'throw'`: Throw if there’s a name conflict.
|
||||
- `'warn'`: Emit a vfile warning, but don’t throw.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This project is compatible with Node.js 16 or greater.
|
||||
|
||||
## Related projects
|
||||
|
||||
- [`estree-util-value-to-estree`](https://github.com/remcohaszing/estree-util-value-to-estree) —
|
||||
Convert a JavaScript value to an estree expression
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE.md) © [Remco Haszing](https://github.com/remcohaszing)
|
||||
51
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.d.ts
generated
vendored
Normal file
51
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.d.ts
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import type * as estree from 'estree';
|
||||
import type * as hast from 'hast';
|
||||
import type * as mdast from 'mdast';
|
||||
import { type VFile } from 'vfile';
|
||||
export declare namespace define {
|
||||
/**
|
||||
* A mapping of variables to define. They keys are the names. The values are the ESTree expression
|
||||
* to represent them.
|
||||
*/
|
||||
type Variables = Record<string, estree.Expression>;
|
||||
/**
|
||||
* Options for {@link define}
|
||||
*/
|
||||
interface Options {
|
||||
/**
|
||||
* If and how to export the variable.
|
||||
*
|
||||
* - `'module'`: Export the value using an ESM const export declaration.
|
||||
* - `'namespace'`: Attach the value as a property on `MDXContent`.
|
||||
* - `false`: Define the variable locally, but don’t export it.
|
||||
*
|
||||
* @default 'module'
|
||||
*/
|
||||
export?: 'module' | 'namespace' | false | undefined;
|
||||
/**
|
||||
* What to do if there’s a name conflict.
|
||||
*
|
||||
* - `'skip'`: Don’t insert the variable if there’s a name conflict.
|
||||
* - `'throw'`: Throw if there’s a name conflict.
|
||||
* - `'warn'`: Emit a vfile warning, but don’t throw.
|
||||
*
|
||||
* @default 'throw'
|
||||
*/
|
||||
conflict?: 'skip' | 'throw' | 'warn' | undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 declare function define(ast: estree.Program | hast.Root | mdast.Root, file: VFile, variables: define.Variables, options?: define.Options | undefined): undefined;
|
||||
//# sourceMappingURL=unist-util-mdx-define.d.ts.map
|
||||
1
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.d.ts.map
generated
vendored
Normal file
1
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"unist-util-mdx-define.d.ts","sourceRoot":"","sources":["../src/unist-util-mdx-define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAA;AAIrC,OAAO,KAAK,KAAK,IAAI,MAAM,MAAM,CAAA;AACjC,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAA;AAElC,yBAAiB,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAY,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAEzD;;OAEG;IACH,UAAiB,OAAO;QACtB;;;;;;;;WAQG;QACH,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,KAAK,GAAG,SAAS,CAAA;QAEnD;;;;;;;;WAQG;QACH,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;KACjD;CACF;AAgLD;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,EAC5C,IAAI,EAAE,KAAK,EACX,SAAS,EAAE,MAAM,CAAC,SAAS,EAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,GACnC,SAAS,CAwEX"}
|
||||
219
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.js
generated
vendored
Normal file
219
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.js
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
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;
|
||||
// Don’t 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
|
||||
1
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.js.map
generated
vendored
Normal file
1
node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
51
node_modules/unist-util-mdx-define/package.json
generated
vendored
Normal file
51
node_modules/unist-util-mdx-define/package.json
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "unist-util-mdx-define",
|
||||
"version": "1.1.2",
|
||||
"description": "A unist utility to define MDX exports.",
|
||||
"keywords": [],
|
||||
"homepage": "https://github.com/remcohaszing/unist-util-mdx-define#readme",
|
||||
"bugs": "https://github.com/remcohaszing/unist-util-mdx-define/issues",
|
||||
"repository": "remcohaszing/unist-util-mdx-define",
|
||||
"funding": "https://github.com/sponsors/remcohaszing",
|
||||
"license": "MIT",
|
||||
"author": "Remco Haszing <remcohaszing@gmail.com>",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": "./dist/unist-util-mdx-define.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"!*.test.*"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "tsc --build",
|
||||
"pretest": "tsc --build",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/mdast": "^4.0.0",
|
||||
"estree-util-is-identifier-name": "^3.0.0",
|
||||
"estree-util-scope": "^1.0.0",
|
||||
"estree-walker": "^3.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/mdx": "^3.0.0",
|
||||
"acorn": "^8.0.0",
|
||||
"astring": "^1.0.0",
|
||||
"c8": "^9.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-config-remcohaszing": "^10.0.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"remark-cli": "^12.0.0",
|
||||
"remark-preset-remcohaszing": "^3.0.0",
|
||||
"snapshot-fixtures": "^1.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"unified": "^11.0.0",
|
||||
"vfile-message": "^4.0.0"
|
||||
}
|
||||
}
|
||||
308
node_modules/unist-util-mdx-define/src/unist-util-mdx-define.ts
generated
vendored
Normal file
308
node_modules/unist-util-mdx-define/src/unist-util-mdx-define.ts
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
import type * as estree from 'estree'
|
||||
import { name as isIdentifierName } from 'estree-util-is-identifier-name'
|
||||
import { createVisitors } from 'estree-util-scope'
|
||||
import { walk } from 'estree-walker'
|
||||
import type * as hast from 'hast'
|
||||
import type * as mdast from 'mdast'
|
||||
import { type VFile } from 'vfile'
|
||||
|
||||
export namespace define {
|
||||
/**
|
||||
* A mapping of variables to define. They keys are the names. The values are the ESTree expression
|
||||
* to represent them.
|
||||
*/
|
||||
export type Variables = Record<string, estree.Expression>
|
||||
|
||||
/**
|
||||
* Options for {@link define}
|
||||
*/
|
||||
export interface Options {
|
||||
/**
|
||||
* If and how to export the variable.
|
||||
*
|
||||
* - `'module'`: Export the value using an ESM const export declaration.
|
||||
* - `'namespace'`: Attach the value as a property on `MDXContent`.
|
||||
* - `false`: Define the variable locally, but don’t export it.
|
||||
*
|
||||
* @default 'module'
|
||||
*/
|
||||
export?: 'module' | 'namespace' | false | undefined
|
||||
|
||||
/**
|
||||
* What to do if there’s a name conflict.
|
||||
*
|
||||
* - `'skip'`: Don’t insert the variable if there’s a name conflict.
|
||||
* - `'throw'`: Throw if there’s a name conflict.
|
||||
* - `'warn'`: Emit a vfile warning, but don’t throw.
|
||||
*
|
||||
* @default 'throw'
|
||||
*/
|
||||
conflict?: 'skip' | 'throw' | 'warn' | undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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: estree.Program,
|
||||
file: VFile,
|
||||
variables: Map<string, estree.Expression>,
|
||||
options: define.Options | undefined
|
||||
): number {
|
||||
const visitors = createVisitors()
|
||||
const [scope] = visitors.scopes
|
||||
const identifiers = new Map<string, estree.Identifier>()
|
||||
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
|
||||
|
||||
// Don’t 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: Map<string, estree.Expression>,
|
||||
options: define.Options | undefined,
|
||||
returnStatement?: estree.ReturnStatement | undefined
|
||||
): (estree.ModuleDeclaration | estree.Statement)[] {
|
||||
if (options?.export === 'namespace') {
|
||||
const statements: estree.Statement[] = []
|
||||
|
||||
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: estree.Declaration[] = []
|
||||
|
||||
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<estree.ExportNamedDeclaration>((declaration) => ({
|
||||
type: 'ExportNamedDeclaration',
|
||||
declaration,
|
||||
specifiers: []
|
||||
}))
|
||||
}
|
||||
|
||||
if (returnStatement.argument?.type === 'ObjectExpression') {
|
||||
returnStatement.argument.properties.splice(
|
||||
-1,
|
||||
0,
|
||||
...Array.from(
|
||||
variables.keys(),
|
||||
(name): estree.Property => ({
|
||||
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: estree.Program | hast.Root | mdast.Root,
|
||||
file: VFile,
|
||||
variables: define.Variables,
|
||||
options?: define.Options | undefined
|
||||
): undefined {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user