eslint-mdx
Version:
ESLint Parser for MDX
387 lines • 20.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRemarkProcessor = exports.processorCache = void 0;
const tslib_1 = require("tslib");
const node_path_1 = tslib_1.__importDefault(require("node:path"));
const node_url_1 = require("node:url");
const synckit_1 = require("synckit");
const assert_1 = require("uvu/assert");
const helpers_1 = require("./helpers");
const tokens_1 = require("./tokens");
let config;
let acorn;
let acornJsx;
let acornParser;
let tokTypes;
let jsxTokTypes;
let tt;
let TokenTranslator;
exports.processorCache = new Map();
const getRemarkConfig = (searchFrom) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
if (!config) {
const { Configuration } = yield (0, helpers_1.loadEsmModule)('unified-engine');
config = new Configuration({
cwd: process.cwd(),
packageField: 'remarkConfig',
pluginPrefix: 'remark',
rcName: '.remarkrc',
detectConfig: true,
});
}
return new Promise((resolve, reject) => config.load(searchFrom, (error, result) => {
error ? reject(error) : resolve(result);
}));
});
const getRemarkMdxOptions = (tokens) => ({
acorn: acornParser,
acornOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
ranges: true,
onToken: tokens,
},
});
const sharedTokens = [];
const getRemarkProcessor = (searchFrom, isMdx, ignoreRemarkConfig) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
const initCacheKey = `${String(isMdx)}-${searchFrom}`;
let cachedProcessor = exports.processorCache.get(initCacheKey);
if (cachedProcessor) {
return cachedProcessor;
}
const result = ignoreRemarkConfig ? null : yield getRemarkConfig(searchFrom);
const cacheKey = (result === null || result === void 0 ? void 0 : result.filePath)
? `${String(isMdx)}-${result.filePath}`
: String(isMdx);
cachedProcessor = exports.processorCache.get(cacheKey);
if (cachedProcessor) {
return cachedProcessor;
}
const { unified } = yield (0, helpers_1.loadEsmModule)('unified');
const remarkParse = (yield (0, helpers_1.loadEsmModule)('remark-parse')).default;
const remarkStringify = (yield (0, helpers_1.loadEsmModule)('remark-stringify')).default;
const remarkMdx = (yield (0, helpers_1.loadEsmModule)('remark-mdx')).default;
const remarkProcessor = unified().use(remarkParse).freeze();
if (result === null || result === void 0 ? void 0 : result.filePath) {
const { plugins, settings } = result;
if (plugins.length > 0) {
try {
plugins.push([
(yield (0, helpers_1.loadEsmModule)('remark-lint-file-extension')).default,
false,
]);
}
catch (_a) {
}
}
const initProcessor = remarkProcessor()
.use({ settings })
.use(remarkStringify);
if (isMdx) {
initProcessor.use(remarkMdx, getRemarkMdxOptions(sharedTokens));
}
cachedProcessor = plugins
.reduce((processor, plugin) => processor.use(...plugin), initProcessor)
.freeze();
}
else {
const initProcessor = remarkProcessor().use(remarkStringify);
if (isMdx) {
initProcessor.use(remarkMdx, getRemarkMdxOptions(sharedTokens));
}
cachedProcessor = initProcessor.freeze();
}
exports.processorCache
.set(initCacheKey, cachedProcessor)
.set(cacheKey, cachedProcessor);
return cachedProcessor;
});
exports.getRemarkProcessor = getRemarkProcessor;
function isExpressionStatement(statement) {
(0, assert_1.ok)(!statement || statement.type === 'ExpressionStatement');
}
(0, synckit_1.runAsWorker)(({ fileOptions, physicalFilename, isMdx, process, ignoreRemarkConfig, }) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
sharedTokens.length = 0;
if (!acorn) {
acorn = yield (0, helpers_1.loadEsmModule)('acorn');
acornJsx = yield (0, helpers_1.loadEsmModule)('acorn-jsx');
acornParser = acorn.Parser.extend(acornJsx.default());
}
const processor = yield (0, exports.getRemarkProcessor)(physicalFilename, isMdx, ignoreRemarkConfig);
if (process) {
const { VFile } = yield (0, helpers_1.loadEsmModule)('vfile');
const file = new VFile(fileOptions);
try {
yield processor.process(file);
}
catch (err) {
const error = err;
if (!file.messages.includes(error)) {
file.message(error).fatal = true;
}
}
return {
messages: file.messages.map(message => (0, synckit_1.extractProperties)(message)),
content: file.toString(),
};
}
if (!tokTypes) {
tokTypes = acorn.tokTypes;
}
if (!jsxTokTypes) {
jsxTokTypes = acornJsx.default({
allowNamespacedObjects: true,
})(acorn.Parser).acornJsx.tokTypes;
}
if (!TokenTranslator) {
TokenTranslator = (yield (0, helpers_1.loadEsmModule)((0, node_url_1.pathToFileURL)(node_path_1.default.resolve(require.resolve('espree/package.json'), '../lib/token-translator.js')))).default;
}
if (!tt) {
tt = Object.assign(Object.assign({}, tokTypes), jsxTokTypes);
}
const text = fileOptions.value;
const tokenTranslator = new TokenTranslator(tt, text);
const root = processor.parse(fileOptions);
const body = [];
const comments = [];
const tokens = [];
const { visit } = yield (0, helpers_1.loadEsmModule)('unist-util-visit');
const processed = new WeakSet();
if (isMdx) {
const prevCharOffset = (0, helpers_1.prevCharOffsetFactory)(text);
const nextCharOffset = (0, helpers_1.nextCharOffsetFactory)(text);
const normalizeNode = (start, end) => (Object.assign(Object.assign({}, (0, helpers_1.normalizePosition)({
start: { offset: start },
end: { offset: end },
text,
})), { raw: text.slice(start, end) }));
const handleJsxName = (nodeName, start) => {
const name = nodeName.trim();
const nameIndex = nodeName.indexOf(name);
const colonIndex = nodeName.indexOf(':');
if (colonIndex !== -1) {
const [fullNamespace, fullName] = nodeName.split(':');
return Object.assign(Object.assign({}, normalizeNode(start + nameIndex, start + nameIndex + name.length)), { type: 'JSXNamespacedName', namespace: handleJsxName(fullNamespace, start), name: handleJsxName(fullName, start + colonIndex + 1) });
}
const lastPointIndex = nodeName.lastIndexOf('.');
if (lastPointIndex === -1) {
return Object.assign(Object.assign({}, normalizeNode(start + nameIndex, start + nameIndex + name.length)), { type: 'JSXIdentifier', name });
}
const objectName = nodeName.slice(0, lastPointIndex);
const propertyName = nodeName.slice(lastPointIndex + 1);
return Object.assign(Object.assign({}, normalizeNode(start + nameIndex, start + nameIndex + name.length)), { type: 'JSXMemberExpression', object: handleJsxName(objectName, start), property: handleJsxName(propertyName, start + lastPointIndex + 1) });
};
visit(root, node => {
if (processed.has(node) ||
(node.type !== 'mdxFlowExpression' &&
node.type !== 'mdxJsxFlowElement' &&
node.type !== 'mdxJsxTextElement' &&
node.type !== 'mdxTextExpression' &&
node.type !== 'mdxjsEsm')) {
return;
}
processed.add(node);
function handleChildren(node) {
return 'children' in node
? node.children.reduce((acc, child) => {
processed.add(child);
if (child.data && 'estree' in child.data && child.data.estree) {
const { estree } = child.data;
(0, assert_1.ok)(estree.body.length <= 1);
const statement = estree.body[0];
isExpressionStatement(statement);
const expression = statement === null || statement === void 0 ? void 0 : statement.expression;
if (child.type === 'mdxTextExpression') {
const { start: { offset: start }, end: { offset: end }, } = node.position;
const expressionContainer = Object.assign(Object.assign({}, normalizeNode(start, end)), { type: 'JSXExpressionContainer', expression: expression || Object.assign(Object.assign({}, normalizeNode(start + 1, end - 1)), { type: 'JSXEmptyExpression' }) });
acc.push(expressionContainer);
}
else if (expression) {
acc.push(expression);
}
comments.push(...estree.comments);
}
else {
const expression = handleNode(child);
if (Array.isArray(expression)) {
acc.push(...expression);
}
else if (expression) {
acc.push(expression);
}
}
return acc;
}, [])
: [];
}
function handleNode(node) {
if (node.type === 'paragraph') {
return handleChildren(node);
}
const { start: { offset: start }, end: { offset: end }, } = node.position;
if (node.type === 'code') {
const { lang, meta, value } = node;
const mdxJsxCode = Object.assign(Object.assign({}, normalizeNode(start, end)), { type: 'MDXCode', lang,
meta,
value });
return mdxJsxCode;
}
if (node.type === 'heading') {
const { depth } = node;
const mdxJsxHeading = Object.assign(Object.assign({}, normalizeNode(start, end)), { type: 'MDXHeading', depth, children: handleChildren(node) });
return mdxJsxHeading;
}
if (node.type === 'text') {
const jsxText = Object.assign(Object.assign({}, normalizeNode(start, end)), { type: 'JSXText', value: node.value });
return jsxText;
}
if (node.type !== 'mdxJsxTextElement' &&
node.type !== 'mdxJsxFlowElement') {
return;
}
const children = handleChildren(node);
const nodePos = node.position;
const nodeStart = nodePos.start.offset;
const nodeEnd = nodePos.end.offset;
const lastCharOffset = prevCharOffset(nodeEnd - 2);
let expression;
if ('name' in node && node.name) {
const nodeNameLength = node.name.length;
const nodeNameStart = nextCharOffset(nodeStart + 1);
const selfClosing = text[lastCharOffset] === '/';
let lastAttrOffset = nodeNameStart + nodeNameLength - 1;
let closingElement = null;
if (!selfClosing) {
const prevOffset = prevCharOffset(lastCharOffset);
const slashOffset = prevCharOffset(prevOffset - nodeNameLength);
(0, assert_1.ok)(text[slashOffset] === '/', `expect \`${text[slashOffset]}\` to be \`/\`, the node is ${node.name}`);
const tagStartOffset = prevCharOffset(slashOffset - 1);
(0, assert_1.ok)(text[tagStartOffset] === '<');
closingElement = Object.assign(Object.assign({}, normalizeNode(tagStartOffset, nodeEnd)), { type: 'JSXClosingElement', name: handleJsxName(node.name, prevOffset + 1 - nodeNameLength) });
}
const jsxEl = Object.assign(Object.assign({}, normalizeNode(nodeStart, nodeEnd)), { type: 'JSXElement', openingElement: {
type: 'JSXOpeningElement',
name: handleJsxName(node.name, nodeNameStart),
attributes: node.attributes.map(attr => {
if (attr.type === 'mdxJsxExpressionAttribute') {
(0, assert_1.ok)(attr.data);
(0, assert_1.ok)(attr.data.estree);
(0, assert_1.ok)(attr.data.estree.range);
let [attrValStart, attrValEnd] = attr.data.estree.range;
attrValStart = prevCharOffset(attrValStart - 1);
attrValEnd = nextCharOffset(attrValEnd);
(0, assert_1.ok)(text[attrValStart] === '{');
(0, assert_1.ok)(text[attrValEnd] === '}');
lastAttrOffset = attrValEnd;
return Object.assign(Object.assign({}, normalizeNode(attrValStart, attrValEnd + 1)), { type: 'JSXSpreadAttribute', argument: attr.data.estree.body[0]
.expression.properties[0].argument });
}
const attrStart = nextCharOffset(lastAttrOffset + 1);
(0, assert_1.ok)(attrStart != null);
const attrName = attr.name;
const attrNameLength = attrName.length;
const attrValue = attr.value;
lastAttrOffset = attrStart + attrNameLength;
const attrNamePos = normalizeNode(attrStart, lastAttrOffset);
if (attrValue == null) {
return Object.assign(Object.assign({}, normalizeNode(attrStart, lastAttrOffset)), { type: 'JSXAttribute', name: Object.assign(Object.assign({}, attrNamePos), { type: 'JSXIdentifier', name: attrName }), value: null });
}
const attrEqualOffset = nextCharOffset(attrStart + attrNameLength);
(0, assert_1.ok)(text[attrEqualOffset] === '=');
let attrValuePos;
if (typeof attrValue === 'string') {
const attrQuoteOffset = nextCharOffset(attrEqualOffset + 1);
const attrQuote = text[attrQuoteOffset];
(0, assert_1.ok)(attrQuote === '"' || attrQuote === "'");
lastAttrOffset = nextCharOffset(attrQuoteOffset + attrValue.length + 1);
(0, assert_1.ok)(text[lastAttrOffset] === attrQuote);
attrValuePos = normalizeNode(attrQuoteOffset, lastAttrOffset + 1);
}
else {
const data = attrValue.data;
let [attrValStart, attrValEnd] = data.estree.range;
attrValStart = prevCharOffset(attrValStart - 1);
attrValEnd = nextCharOffset(attrValEnd);
(0, assert_1.ok)(text[attrValStart] === '{');
(0, assert_1.ok)(text[attrValEnd] === '}');
lastAttrOffset = attrValEnd;
attrValuePos = normalizeNode(attrValStart, attrValEnd + 1);
}
return Object.assign(Object.assign({}, normalizeNode(attrStart, lastAttrOffset + 1)), { type: 'JSXAttribute', name: Object.assign(Object.assign({}, attrNamePos), { type: 'JSXIdentifier', name: attrName }), value: typeof attr.value === 'string'
? Object.assign(Object.assign({}, attrValuePos), { type: 'Literal', value: attr.value }) : Object.assign(Object.assign({}, attrValuePos), { type: 'JSXExpressionContainer', expression: attr.value.data.estree
.body[0].expression }) });
}),
selfClosing,
}, closingElement,
children });
let nextOffset = nextCharOffset(lastAttrOffset + 1);
let nextChar = text[nextOffset];
const expectedNextChar = selfClosing ? '/' : '>';
if (nextChar !== expectedNextChar) {
nextOffset = nextCharOffset(lastAttrOffset);
nextChar = text[nextOffset];
}
(0, assert_1.ok)(nextChar === expectedNextChar, `\`nextChar\` must be '${expectedNextChar}' but actually is '${nextChar}'`);
Object.assign(jsxEl.openingElement, normalizeNode(nodeStart, selfClosing ? nodeEnd : nextOffset + 1));
expression = jsxEl;
}
else {
const openEndOffset = nextCharOffset(nodeStart + 1);
const openPos = normalizeNode(nodeStart, openEndOffset);
const closeStartOffset = prevCharOffset(lastCharOffset - 1);
const jsxFrg = Object.assign(Object.assign({}, openPos), { type: 'JSXFragment', openingFragment: Object.assign(Object.assign({}, openPos), { type: 'JSXOpeningFragment' }), closingFragment: Object.assign(Object.assign({}, normalizeNode(closeStartOffset, nodeEnd)), { type: 'JSXClosingFragment' }), children });
expression = jsxFrg;
}
return expression;
}
const expression = handleNode(node);
if (expression) {
body.push(Object.assign(Object.assign({}, (0, helpers_1.normalizePosition)(node.position)), { type: 'ExpressionStatement', expression: expression }));
}
const estree = ((node.data &&
'estree' in node.data &&
node.data.estree) || {
body: [],
comments: [],
});
body.push(...estree.body);
comments.push(...estree.comments);
});
}
const { visit: visitEstree } = yield (0, helpers_1.loadEsmModule)('estree-util-visit');
visitEstree({
type: 'Program',
sourceType: 'module',
body,
}, node => {
if (node.type !== 'TemplateElement') {
return;
}
const templateElement = node;
const startOffset = -1;
const endOffset = templateElement.tail ? 1 : 2;
templateElement.start += startOffset;
templateElement.end += endOffset;
if (templateElement.range) {
templateElement.range[0] += startOffset;
templateElement.range[1] += endOffset;
}
if (templateElement.loc) {
templateElement.loc.start.column += startOffset;
templateElement.loc.end.column += endOffset;
}
});
for (const token of (0, tokens_1.restoreTokens)(text, root, sharedTokens, tt, visit)) {
tokenTranslator.onToken(token, {
ecmaVersion: 'latest',
tokens: tokens,
});
}
return {
root,
body,
comments,
tokens,
};
}));
//# sourceMappingURL=worker.js.map