UNPKG

eslint-plugin-mdx

Version:
803 lines (781 loc) 24.8 kB
'use strict'; var eslintMdx = require('eslint-mdx'); var node_module = require('node:module'); var synckit = require('synckit'); var path = require('node:path'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var eslintMdx__namespace = /*#__PURE__*/_interopNamespaceDefault(eslintMdx); const base = { parser: "eslint-mdx", parserOptions: { sourceType: "module", ecmaVersion: "latest" }, plugins: ["mdx"], processor: "mdx/remark", rules: { "mdx/remark": "warn", "no-unused-expressions": "error" } }; const codeBlocks = { parserOptions: { ecmaFeatures: { // Adding a "use strict" directive at the top of // every code block is tedious and distracting, so // opt into strict mode parsing without the // directive. impliedStrict: true } }, rules: { // The Markdown parser automatically trims trailing // newlines from code blocks. "eol-last": "off", // In code snippets and examples, these rules are often // counterproductive to clarity and brevity. "no-undef": "off", "no-unused-expressions": "off", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off", "padded-blocks": "off", // Adding a "use strict" directive at the top of every // code block is tedious and distracting. The config // opts into strict mode parsing without the directive. strict: "off", // The processor will not receive a Unicode Byte Order // Mark from the Markdown parser. "unicode-bom": "off" } }; var __defProp$6 = Object.defineProperty; var __getOwnPropSymbols$6 = Object.getOwnPropertySymbols; var __hasOwnProp$6 = Object.prototype.hasOwnProperty; var __propIsEnum$6 = Object.prototype.propertyIsEnumerable; var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$6 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$6.call(b, prop)) __defNormalProp$6(a, prop, b[prop]); if (__getOwnPropSymbols$6) for (var prop of __getOwnPropSymbols$6(b)) { if (__propIsEnum$6.call(b, prop)) __defNormalProp$6(a, prop, b[prop]); } return a; }; var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp$6.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols$6) for (var prop of __getOwnPropSymbols$6(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum$6.call(source, prop)) target[prop] = source[prop]; } return target; }; const flat = { files: ["**/*.{md,mdx}"], languageOptions: { sourceType: "module", ecmaVersion: "latest", parser: eslintMdx__namespace, globals: { React: false } }, plugins: { mdx }, rules: { "mdx/remark": "warn", "no-unused-expressions": "error", "react/react-in-jsx-scope": "off" } }; const _a = codeBlocks, { parserOptions } = _a, restConfig = __objRest(_a, ["parserOptions"]); const flatCodeBlocks = __spreadValues$6({ files: ["**/*.{md,mdx}/**"], languageOptions: { parserOptions } }, restConfig); const import_meta = {}; const getGlobals = (sources, initialGlobals = {}) => (Array.isArray(sources) ? ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion sources ) : Object.keys(sources)).reduce( (globals, source) => Object.assign(globals, { [source]: false }), initialGlobals ); const cjsRequire = typeof require === "undefined" ? node_module.createRequire(import_meta.url) : require; var __defProp$5 = Object.defineProperty; var __defProps$3 = Object.defineProperties; var __getOwnPropDescs$3 = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols$5 = Object.getOwnPropertySymbols; var __hasOwnProp$5 = Object.prototype.hasOwnProperty; var __propIsEnum$5 = Object.prototype.propertyIsEnumerable; var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$5 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$5.call(b, prop)) __defNormalProp$5(a, prop, b[prop]); if (__getOwnPropSymbols$5) for (var prop of __getOwnPropSymbols$5(b)) { if (__propIsEnum$5.call(b, prop)) __defNormalProp$5(a, prop, b[prop]); } return a; }; var __spreadProps$3 = (a, b) => __defProps$3(a, __getOwnPropDescs$3(b)); let isReactPluginAvailable = false; try { cjsRequire.resolve("eslint-plugin-react"); isReactPluginAvailable = true; } catch (e) { } const overrides$1 = __spreadProps$3(__spreadValues$5({}, base), { globals: { React: false }, plugins: eslintMdx.arrayify( base.plugins, /* istanbul ignore next */ isReactPluginAvailable ? "react" : null ), rules: { "react/jsx-no-undef": ( /* istanbul ignore next */ isReactPluginAvailable ? [ 2, { allowGlobals: true } ] : 0 ), "react/react-in-jsx-scope": 0 } }); var __defProp$4 = Object.defineProperty; var __getOwnPropSymbols$4 = Object.getOwnPropertySymbols; var __hasOwnProp$4 = Object.prototype.hasOwnProperty; var __propIsEnum$4 = Object.prototype.propertyIsEnumerable; var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$4 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$4.call(b, prop)) __defNormalProp$4(a, prop, b[prop]); if (__getOwnPropSymbols$4) for (var prop of __getOwnPropSymbols$4(b)) { if (__propIsEnum$4.call(b, prop)) __defNormalProp$4(a, prop, b[prop]); } return a; }; const overrides = [ __spreadValues$4({ files: ["*.md", "*.mdx"], extends: "plugin:mdx/overrides" }, base), { files: "**/*.{md,mdx}/**", extends: "plugin:mdx/code-blocks" } ]; const recommended = { overrides }; const addPrettierRules = () => { try { cjsRequire.resolve("prettier"); const { meta } = cjsRequire("eslint-plugin-prettier"); const version = (meta == null ? void 0 : meta.version) || ""; const [major, minor, patch] = version.split("."); if ( /* istanbul ignore next */ +major > 5 || +major === 5 && (+minor > 1 || +minor === 1 && Number.parseInt(patch) >= 2) ) { return; } overrides.push( { files: "*.md", rules: { "prettier/prettier": [ "error", { parser: "markdown" } ] } }, { files: "*.mdx", rules: { "prettier/prettier": [ "error", { parser: "mdx" } ] } } ); } catch (e) { } }; addPrettierRules(); const configs = { base, "code-blocks": codeBlocks, codeBlocks, flat, flatCodeBlocks, overrides: overrides$1, recommended }; const pkg = cjsRequire("../package.json"); const meta = { name: pkg.name, version: pkg.version }; var __defProp$3 = Object.defineProperty; var __getOwnPropSymbols$3 = Object.getOwnPropertySymbols; var __hasOwnProp$3 = Object.prototype.hasOwnProperty; var __propIsEnum$3 = Object.prototype.propertyIsEnumerable; var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$3 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$3.call(b, prop)) __defNormalProp$3(a, prop, b[prop]); if (__getOwnPropSymbols$3) for (var prop of __getOwnPropSymbols$3(b)) { if (__propIsEnum$3.call(b, prop)) __defNormalProp$3(a, prop, b[prop]); } return a; }; const DEFAULT_LANGUAGE_MAPPER = { ecmascript: "js", javascript: "js", javascriptreact: "jsx", typescript: "ts", typescriptreact: "tsx", markdown: "md", markdownjsx: "mdx", markdownreact: "mdx", mdown: "md", mkdn: "md" }; function getShortLang(filename, languageMapper) { const language = filename.split(".").at(-1); if (languageMapper === false) { return language; } languageMapper = languageMapper ? __spreadValues$3(__spreadValues$3({}, DEFAULT_LANGUAGE_MAPPER), languageMapper) : DEFAULT_LANGUAGE_MAPPER; const mapped = languageMapper[language]; if (mapped) { return mapped; } const lang = language.toLowerCase(); return languageMapper[lang] || lang; } const fromMarkdown = synckit.createSyncFn(cjsRequire.resolve("./worker.js")); var __defProp$2 = Object.defineProperty; var __defProps$2 = Object.defineProperties; var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols; var __hasOwnProp$2 = Object.prototype.hasOwnProperty; var __propIsEnum$2 = Object.prototype.propertyIsEnumerable; var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$2 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$2.call(b, prop)) __defNormalProp$2(a, prop, b[prop]); if (__getOwnPropSymbols$2) for (var prop of __getOwnPropSymbols$2(b)) { if (__propIsEnum$2.call(b, prop)) __defNormalProp$2(a, prop, b[prop]); } return a; }; var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b)); const UNSATISFIABLE_RULES = /* @__PURE__ */ new Set([ "eol-last", // The Markdown parser strips trailing newlines in code fences "unicode-bom" // Code blocks will begin in the middle of Markdown files ]); const SUPPORTS_AUTOFIX = true; const BOM = "\uFEFF"; const blocksCache = /* @__PURE__ */ 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,}/, // eslint-disable-next-line sonarjs/slow-regex /-{2,}>$/ ], [ /^\/\*+/, // eslint-disable-next-line sonarjs/slow-regex /\*+\/$/ ] ]; 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, // Advance `trimLength` character from the beginning of the Markdown // line to the beginning of the equivalent JS line, then compute the // delta. md: mdOffset + trimLength - jsOffset }); mdOffset += line.length + 1; jsOffset += line.length - trimLength + 1; } return rangeMap; } const codeBlockFileNameRegex = new RegExp(`filename=(?<quote>["'])(?<filename>.*?)\\1`, "u"); function fileNameFromMeta(block) { var _a; return (_a = codeBlockFileNameRegex.exec(block.meta)) == null ? void 0 : _a.groups.filename.replaceAll(/\s+/gu, "_"); } function preprocess(sourceText, filename) { const text = sourceText.startsWith(BOM) ? sourceText.slice(1) : sourceText; const ast = fromMarkdown( text, // FIXME: how to read `extensions` and `markdownExtensions` parser options? 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 = []; }, /** * Visit a code node. * @param node The visited node. */ 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(__spreadProps$2(__spreadValues$2({}, node), { baseIndentText: getIndentText(text, node), comments, rangeMap: getBlockRangeMap(text, node, comments) })); }, /** * Visit an HTML node. * @param node The visited node. */ html(node) { const comment = getComment(node.value); if (comment) { allComments.push(comment); } else { allComments = []; } }, mdxFlowExpression: mdxExpression, mdxTextExpression: mdxExpression }); return blocks.map((block, index) => { var _a; const [language] = block.lang.trim().split(" "); return { filename: (_a = fileNameFromMeta(block)) != null ? _a : `${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", ` ${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 __spreadProps$2(__spreadValues$2({}, 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) => __spreadProps$2(__spreadValues$2({}, suggestion), { fix: adjustFix(block, suggestion.fix) })); } const adjustedFix = {}; if (message.fix) { adjustedFix.fix = adjustFix(block, message.fix); } return __spreadValues$2(__spreadValues$2(__spreadValues$2({}, 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); }); } const markdownProcessor = { meta: { name: "mdx/markdown", version: meta.version }, preprocess, postprocess, supportsAutofix: SUPPORTS_AUTOFIX }; const processorOptions = {}; const linterPath = Object.keys(cjsRequire.cache).find( (path) => /([/\\])eslint\1lib(?:\1linter){2}\.js$/.test(path) ); if (!linterPath) { throw new Error("Could not find ESLint Linter in require cache"); } const ESLinter = cjsRequire(linterPath).Linter; const { verify } = ESLinter.prototype; ESLinter.prototype.verify = function(code, config, options) { var _a, _b, _c; const settings = (_c = ((_b = (_a = config.extractConfig) == null ? void 0 : _a.call( config, // eslint-disable-next-line unicorn/no-typeof-undefined typeof options === "undefined" || typeof options === "string" ? options : options.filename )) != null ? _b : config).settings) != null ? _c : {}; processorOptions.lintCodeBlocks = settings["mdx/code-blocks"] === true; processorOptions.languageMapper = settings["mdx/language-mapper"]; return verify.call(this, code, config, options); }; var __defProp$1 = Object.defineProperty; var __defProps$1 = Object.defineProperties; var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; var __hasOwnProp$1 = Object.prototype.hasOwnProperty; var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$1 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]); if (__getOwnPropSymbols$1) for (var prop of __getOwnPropSymbols$1(b)) { if (__propIsEnum$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]); } return a; }; var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); const createRemarkProcessor = (processorOptions$1 = processorOptions) => ({ meta: { name: "mdx/remark", version: meta.version }, supportsAutofix: true, preprocess(text, filename) { if (!processorOptions$1.lintCodeBlocks) { return [text]; } return [ text, ...markdownProcessor.preprocess(text, filename).map(({ text: text2, filename: filename2 }) => ({ text: text2, filename: filename2.slice(0, filename2.lastIndexOf(".")) + "." + getShortLang(filename2, processorOptions$1.languageMapper) })) ]; }, postprocess([mdxMessages, ...markdownMessages], filename) { return [ ...mdxMessages, ...markdownProcessor.postprocess(markdownMessages, filename) ].sort((a, b) => a.line - b.line || a.column - b.column).map((lintMessage) => { const { message, ruleId: eslintRuleId, severity: eslintSeverity } = lintMessage; if (eslintRuleId !== "mdx/remark") { return lintMessage; } const { source, ruleId, reason, severity } = JSON.parse( message ); return __spreadProps$1(__spreadValues$1({}, lintMessage), { ruleId: `${source}-${ruleId}`, message: reason, severity: Math.max( eslintSeverity, severity ) }); }); } }); const remark$1 = createRemarkProcessor(); const processors = { remark: remark$1 }; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); const remark = { meta: { type: "layout", docs: { description: "Linter integration with remark plugins", category: "Stylistic Issues", recommended: true }, fixable: "code" }, create(context) { const filename = context.getFilename(); const extname = path.extname(filename); const sourceCode = context.getSourceCode(); const options = context.parserOptions; const isMdx = [ ...eslintMdx.DEFAULT_EXTENSIONS, ...options.extensions || [] ].includes(extname); const isMarkdown = [ ...eslintMdx.MARKDOWN_EXTENSIONS, ...options.markdownExtensions || [] ].includes(extname); return { Program(node) { if (!isMdx && !isMarkdown) { return; } const ignoreRemarkConfig = Boolean(options.ignoreRemarkConfig); const sourceText = sourceCode.getText(node); const { messages, content: fixedText } = eslintMdx.performSyncWork({ filePath: eslintMdx.getPhysicalFilename(filename), code: sourceText, // eslint-disable-next-line sonarjs/deprecation -- FIXME: ESLint 8.40+ required cwd: context.getCwd(), isMdx, process: true, ignoreRemarkConfig }); let fixed = 0; for (const { source, reason, ruleId, fatal, line, column, place } of messages) { const severity = fatal ? 2 : fatal == null ? 0 : 1; if (!severity) { continue; } const message = { reason, source, ruleId, severity }; const point = { line, // ! eslint ast column is 0-indexed, but unified is 1-indexed column: column - 1 }; context.report({ // related to https://github.com/eslint/eslint/issues/14198 message: JSON.stringify(message), loc: ( /* istanbul ignore next */ place && "start" in place ? __spreadProps(__spreadValues({}, point), { start: __spreadProps(__spreadValues({}, place.start), { column: place.start.column - 1 }), end: __spreadProps(__spreadValues({}, place.end), { column: place.end.column - 1 }) }) : point ), node, fix: fixedText == null || fixedText === sourceText ? null : () => fixed++ ? null : { range: [0, sourceText.length], text: fixedText } }); } } }; } }; const rules = { remark }; var mdx = /*#__PURE__*/Object.freeze({ __proto__: null, DEFAULT_LANGUAGE_MAPPER: DEFAULT_LANGUAGE_MAPPER, base: base, cjsRequire: cjsRequire, codeBlocks: codeBlocks, configs: configs, createRemarkProcessor: createRemarkProcessor, flat: flat, flatCodeBlocks: flatCodeBlocks, getGlobals: getGlobals, getShortLang: getShortLang, meta: meta, overrides: overrides$1, processorOptions: processorOptions, processors: processors, recommended: recommended, remark: remark, rules: rules }); exports.DEFAULT_LANGUAGE_MAPPER = DEFAULT_LANGUAGE_MAPPER; exports.base = base; exports.cjsRequire = cjsRequire; exports.codeBlocks = codeBlocks; exports.configs = configs; exports.createRemarkProcessor = createRemarkProcessor; exports.flat = flat; exports.flatCodeBlocks = flatCodeBlocks; exports.getGlobals = getGlobals; exports.getShortLang = getShortLang; exports.meta = meta; exports.overrides = overrides$1; exports.processorOptions = processorOptions; exports.processors = processors; exports.recommended = recommended; exports.remark = remark; exports.rules = rules;