UNPKG

eslint-plugin-mdx

Version:
218 lines 6.97 kB
import { fromMarkdown } from '../from-markdown.js'; import { meta } from '../meta.js'; const UNSATISFIABLE_RULES = new Set([ 'eol-last', 'unicode-bom', ]); const SUPPORTS_AUTOFIX = true; const BOM = '\uFEFF'; const blocksCache = new Map(); function traverse(node, callbacks) { if (callbacks[node.type]) { callbacks[node.type](node); } else { callbacks['*'](); } const parent = node; if ('children' in parent) { for (const child of parent.children) { traverse(child, callbacks); } } } const COMMENTS = [ [ /^<!-{2,}/, /-{2,}>$/, ], [ /^\/\*+/, /\*+\/$/, ], ]; const eslintCommentRegex = /^(?:eslint\b|global\s)/u; function getComment(value, isMdx = false) { const [commentStart, commentEnd] = COMMENTS[+isMdx]; const commentStartMatched = commentStart.exec(value); const commentEndMatched = commentEnd.exec(value); if (commentStartMatched == null || commentEndMatched == null) { return ''; } const comment = value .slice(commentStartMatched[0].length, -commentEndMatched[0].length) .trim(); if (!eslintCommentRegex.test(comment)) { return ''; } return comment; } const leadingWhitespaceRegex = /^[>\s]*/u; function getBeginningOfLineOffset(node) { return node.position.start.offset - node.position.start.column + 1; } function getIndentText(text, node) { return leadingWhitespaceRegex.exec(text.slice(getBeginningOfLineOffset(node)))[0]; } function getBlockRangeMap(text, node, comments) { const startOffset = getBeginningOfLineOffset(node); const code = text.slice(startOffset, node.position.end.offset); const lines = code.split('\n'); const baseIndent = getIndentText(text, node).length; const commentLength = comments.reduce((len, comment) => len + comment.length + 1, 0); const rangeMap = [ { indent: baseIndent, js: 0, md: 0, }, ]; let jsOffset = commentLength; let mdOffset = startOffset + lines[0].length + 1; for (let i = 0; i + 1 < lines.length; i++) { const line = lines[i + 1]; const leadingWhitespaceLength = leadingWhitespaceRegex.exec(line)[0].length; const trimLength = Math.min(baseIndent, leadingWhitespaceLength); rangeMap.push({ indent: trimLength, js: jsOffset, md: mdOffset + trimLength - jsOffset, }); mdOffset += line.length + 1; jsOffset += line.length - trimLength + 1; } return rangeMap; } const codeBlockFileNameRegex = /filename=(?<quote>["'])(?<filename>.*?)\1/u; function fileNameFromMeta(block) { return codeBlockFileNameRegex .exec(block.meta) ?.groups.filename.replaceAll(/\s+/gu, '_'); } function preprocess(sourceText, filename) { const text = sourceText.startsWith(BOM) ? sourceText.slice(1) : sourceText; const ast = fromMarkdown(text, filename.endsWith('.mdx')); const blocks = []; blocksCache.set(filename, blocks); let allComments = []; function mdxExpression(node) { const comment = getComment(node.value, true); if (comment) { allComments.push(comment); } else { allComments = []; } } traverse(ast, { '*'() { allComments = []; }, code(node) { if (!node.lang) { return; } const comments = []; for (const comment of allComments) { if (comment === 'eslint-skip') { allComments = []; return; } comments.push(`/* ${comment} */`); } allComments = []; blocks.push({ ...node, baseIndentText: getIndentText(text, node), comments, rangeMap: getBlockRangeMap(text, node, comments), }); }, html(node) { const comment = getComment(node.value); if (comment) { allComments.push(comment); } else { allComments = []; } }, mdxFlowExpression: mdxExpression, mdxTextExpression: mdxExpression, }); return blocks.map((block, index) => { const [language] = block.lang.trim().split(' '); return { filename: fileNameFromMeta(block) ?? `${index}.${language}`, text: [...block.comments, block.value, ''].join('\n'), }; }); } function adjustFix(block, fix) { return { range: fix.range.map(range => { let i = 1; while (i < block.rangeMap.length && block.rangeMap[i].js <= range) { i++; } return range + block.rangeMap[i - 1].md; }), text: fix.text.replaceAll('\n', `\n${block.baseIndentText}`), }; } function adjustBlock(block) { const leadingCommentLines = block.comments.reduce((count, comment) => count + comment.split('\n').length, 0); const blockStart = block.position.start.line; return function adjustMessage(message) { if (!Number.isInteger(message.line)) { return { ...message, line: blockStart, column: block.position.start.column, }; } const lineInCode = message.line - leadingCommentLines; if (lineInCode < 1 || lineInCode >= block.rangeMap.length) { return null; } const out = { line: lineInCode + blockStart, column: message.column + block.rangeMap[lineInCode].indent, }; if (Number.isInteger(message.endLine)) { out.endLine = message.endLine - leadingCommentLines + blockStart; } if (Array.isArray(message.suggestions)) { out.suggestions = message.suggestions.map(suggestion => ({ ...suggestion, fix: adjustFix(block, suggestion.fix), })); } const adjustedFix = {}; if (message.fix) { adjustedFix.fix = adjustFix(block, message.fix); } return { ...message, ...out, ...adjustedFix }; }; } function excludeUnsatisfiableRules(message) { return message && !UNSATISFIABLE_RULES.has(message.ruleId); } function postprocess(messages, filename) { const blocks = blocksCache.get(filename); blocksCache.delete(filename); return messages.flatMap((group, i) => { const adjust = adjustBlock(blocks[i]); return group.map(adjust).filter(excludeUnsatisfiableRules); }); } export const markdownProcessor = { meta: { name: 'mdx/markdown', version: meta.version, }, preprocess, postprocess, supportsAutofix: SUPPORTS_AUTOFIX, }; //# sourceMappingURL=markdown.js.map