From 1e9d89b4adec210401ee65f67cdeb9fe1897d0f0 Mon Sep 17 00:00:00 2001 From: azu Date: Mon, 30 Dec 2024 23:21:29 +0900 Subject: [PATCH] Update src/textlint-rule-eslint.ts+2 --- src/textlint-rule-eslint.ts | 282 +++++++++++++++------------- test/fixtures/style.eslintconfig.js | 46 +++-- test/textlint-rule-eslint-test.ts | 246 +++++++++++------------- 3 files changed, 282 insertions(+), 292 deletions(-) diff --git a/src/textlint-rule-eslint.ts b/src/textlint-rule-eslint.ts index 4775545..3605c3b 100644 --- a/src/textlint-rule-eslint.ts +++ b/src/textlint-rule-eslint.ts @@ -1,72 +1,69 @@ // LICENSE : MIT "use strict"; import path from "path"; -import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types" +import type { TextlintRuleContext, TextlintRuleModule } from "@textlint/types"; import { StructuredSource } from "structured-source"; import { ESLint } from "eslint"; const defaultOptions = { - // path to eslint config file - "configFile": null, - // recognize lang of CodeBlock as JavaScript - "langs": ["js", "javascript", "node", "jsx"] + // path to eslint config file + configFile: null, + // recognize lang of CodeBlock as JavaScript + langs: ["js", "javascript", "node", "jsx"] }; const getConfigBaseDir = (context: TextlintRuleContext & { config?: any }) => { - if (typeof context.getConfigBaseDir === "function") { - return context.getConfigBaseDir(); - } - // Fallback that use deprecated `config` value - // https://github.com/textlint/textlint/issues/294 - const textlintRcFilePath = context.config ? context.config.configFile : null; - // .textlinrc directory - return textlintRcFilePath ? path.dirname(textlintRcFilePath) : process.cwd(); + if (typeof context.getConfigBaseDir === "function") { + return context.getConfigBaseDir(); + } + // Fallback that use deprecated `config` value + // https://github.com/textlint/textlint/issues/294 + const textlintRcFilePath = context.config ? context.config.configFile : null; + // .textlinrc directory + return textlintRcFilePath ? path.dirname(textlintRcFilePath) : process.cwd(); }; export type Options = { - configFile: string; - langs: string[] -} -const createReporter = ({ - fix - }: { - fix: boolean -}): TextlintRuleModule => { - return (context, options) => { - const { Syntax, RuleError, report, fixer, getSource, locator } = context; - if (!options) { - throw new Error(`Require options: { "configFile": "path/to/.eslintrc" }`); - } - if (!options.configFile) { - throw new Error(`Require options: { "configFile": "path/to/.eslintrc" }`); - } - const availableLang = options.langs || defaultOptions.langs; - const textlintRCDir = getConfigBaseDir(context); - const esLintConfigFilePath = textlintRCDir ? path.resolve(textlintRCDir, options.configFile) : options.configFile; - const engine = new ESLint({ - overrideConfigFile: esLintConfigFilePath, - ignore: false, - - }); - return { - async [Syntax.CodeBlock](node) { - if (!node.lang) { - return; + configFile: string; + langs: string[]; +}; +const createReporter = ({ fix }: { fix: boolean }): TextlintRuleModule => { + return (context, options) => { + const { Syntax, RuleError, report, fixer, getSource, locator } = context; + if (!options) { + throw new Error(`Require options: { "configFile": "path/to/.eslintrc" }`); } - if (availableLang.indexOf(node.lang) === -1) { - return; + if (!options.configFile) { + throw new Error(`Require options: { "configFile": "path/to/.eslintrc" }`); } - const raw = getSource(node); - const code = getUntrimmedCode(node, raw); - const source = new StructuredSource(code); - const resultLinting = await engine.lintText(code, { - filePath: `test.js` + const availableLang = options.langs || defaultOptions.langs; + const textlintRCDir = getConfigBaseDir(context); + const esLintConfigFilePath = textlintRCDir + ? path.resolve(textlintRCDir, options.configFile) + : options.configFile; + const engine = new ESLint({ + overrideConfigFile: esLintConfigFilePath, + ignore: false }); - if (resultLinting.length === 0) { - return; - } - resultLinting.forEach(result => { - result.messages.forEach(message => { - /* + return { + async [Syntax.CodeBlock](node) { + if (!node.lang) { + return; + } + if (availableLang.indexOf(node.lang) === -1) { + return; + } + const raw = getSource(node); + const code = getUntrimmedCode(node, raw); + const source = new StructuredSource(code); + const resultLinting = await engine.lintText(code, { + filePath: `test.js` + }); + if (resultLinting.length === 0) { + return; + } + resultLinting.forEach((result) => { + result.messages.forEach((message) => { + /* 1. ```js 2. CODE @@ -74,63 +71,78 @@ const createReporter = ({ ESLint message line and column start with 1 */ - if (options.ignoreParsingErrors && message.message.includes("Parsing error")) { - return; - } + if (options.ignoreParsingErrors && message.message.includes("Parsing error")) { + return; + } - const prefix = message.ruleId ? `${message.ruleId}: ` : ""; - if (message.fix) { - // relative range from node - const fixedRange = message.fix.range; - const fixedText = message.fix.text; - const sourceBlockDiffIndex = (raw !== node.value) ? raw.indexOf(code) : 0; - const fixedWithPadding = [fixedRange[0] + sourceBlockDiffIndex, fixedRange[1] + sourceBlockDiffIndex] as const; - const location = source.rangeToLocation(fixedWithPadding); - const isSamePosition = location.start.line === location.end.line && location.start.column === location.end.column; - report(node, new RuleError(`${prefix}${message.message}`, { - padding: isSamePosition - ? locator.at(fixedWithPadding[0]) - : locator.range(fixedWithPadding), - fix: - isSamePosition - ? fixer.insertTextAfterRange(fixedWithPadding, fixedText) - : fixer.replaceTextRange(fixedWithPadding, fixedText) - })); - } else { - const sourceBlockDiffIndex = (raw !== node.value) ? raw.indexOf(code) : 0; - if (message.endLine !== undefined && message.endColumn !== undefined) { - const range = source.locationToRange({ - start: { - line: message.line, - column: message.column - }, - end: { - line: message.endLine, - column: message.endColumn - } + const prefix = message.ruleId ? `${message.ruleId}: ` : ""; + if (message.fix) { + // relative range from node + const fixedRange = message.fix.range; + const fixedText = message.fix.text; + const sourceBlockDiffIndex = raw !== node.value ? raw.indexOf(code) : 0; + const fixedWithPadding = [ + fixedRange[0] + sourceBlockDiffIndex, + fixedRange[1] + sourceBlockDiffIndex + ] as const; + const location = source.rangeToLocation(fixedWithPadding); + const isSamePosition = + location.start.line === location.end.line && + location.start.column === location.end.column; + report( + node, + new RuleError(`${prefix}${message.message}`, { + padding: isSamePosition + ? locator.at(fixedWithPadding[0]) + : locator.range(fixedWithPadding), + fix: isSamePosition + ? fixer.insertTextAfterRange(fixedWithPadding, fixedText) + : fixer.replaceTextRange(fixedWithPadding, fixedText) + }) + ); + } else { + const sourceBlockDiffIndex = raw !== node.value ? raw.indexOf(code) : 0; + if (message.endLine !== undefined && message.endColumn !== undefined) { + const range = source.locationToRange({ + start: { + line: message.line, + column: message.column + }, + end: { + line: message.endLine, + column: message.endColumn + } + }); + const adjustedRange = [ + range[0] + sourceBlockDiffIndex, + range[1] + sourceBlockDiffIndex + ] as const; + report( + node, + new RuleError(`${prefix}${message.message}`, { + padding: locator.range(adjustedRange) + }) + ); + } else { + const index = source.positionToIndex({ + line: message.line, + column: message.column + }); + const adjustedIndex = index + sourceBlockDiffIndex - 1; + report( + node, + new RuleError(`${prefix}${message.message}`, { + padding: locator.at(adjustedIndex) + }) + ); + } + } + }); }); - const adjustedRange = [range[0] + sourceBlockDiffIndex, range[1] + sourceBlockDiffIndex] as const; - report(node, new RuleError(`${prefix}${message.message}`, { - padding: locator.range(adjustedRange) - })); - } else { - const index = source.positionToIndex({ - line: message.line, - column: message.column - }); - const adjustedIndex = index + sourceBlockDiffIndex - 1; - report(node, new RuleError(`${prefix}${message.message}`, { - padding: locator.at(adjustedIndex) - })); - } } - - }); - }); - } - } - }; -} + }; + }; +}; /** * [Markdown] get actual code value from CodeBlock node @@ -139,36 +151,36 @@ const createReporter = ({ * @returns {string} */ function getUntrimmedCode(node: any, raw: string) { - if (node.type !== "CodeBlock") { - return node.value - } - // Space indented CodeBlock that has not lang - if (!node.lang) { - return node.value; - } + if (node.type !== "CodeBlock") { + return node.value; + } + // Space indented CodeBlock that has not lang + if (!node.lang) { + return node.value; + } - // If it is not markdown codeBlock, just use node.value - if (!(raw.startsWith("```") && raw.endsWith("```"))) { - if (node.value.endsWith("\n")) { - return node.value + // If it is not markdown codeBlock, just use node.value + if (!(raw.startsWith("```") && raw.endsWith("```"))) { + if (node.value.endsWith("\n")) { + return node.value; + } + return node.value + "\n"; } - return node.value + "\n"; - } - // Markdown(remark) specific hack - // https://github.com/wooorm/remark/issues/207#issuecomment-244620590 - const lines = raw.split("\n"); - // code lines without the first line and the last line - const codeLines = lines.slice(1, lines.length - 1); - // add last new line - // \n``` - return codeLines.join("\n") + "\n"; + // Markdown(remark) specific hack + // https://github.com/wooorm/remark/issues/207#issuecomment-244620590 + const lines = raw.split("\n"); + // code lines without the first line and the last line + const codeLines = lines.slice(1, lines.length - 1); + // add last new line + // \n``` + return codeLines.join("\n") + "\n"; } export default { - linter: createReporter({ - fix: false - }), - fixer: createReporter({ - fix: true - }) + linter: createReporter({ + fix: false + }), + fixer: createReporter({ + fix: true + }) }; diff --git a/test/fixtures/style.eslintconfig.js b/test/fixtures/style.eslintconfig.js index 94cc616..f0df83b 100644 --- a/test/fixtures/style.eslintconfig.js +++ b/test/fixtures/style.eslintconfig.js @@ -3,35 +3,43 @@ module.exports = { rules: { "eol-last": ["error", "always"], - "indent": ["error", 4, { - "SwitchCase": 1 - }], - "quotes": ["error", "double"], + indent: [ + "error", + 4, + { + SwitchCase: 1 + } + ], + quotes: ["error", "double"], "key-spacing": "error", "no-dupe-keys": "error", "keyword-spacing": "error", "linebreak-style": ["error", "unix"], "rest-spread-spacing": "error", - "semi": ["error", "always"], + semi: ["error", "always"], "semi-spacing": "error", "space-before-blocks": "error", "space-before-function-paren": ["error", "never"], "space-in-parens": "error", "space-infix-ops": "error", "space-unary-ops": "error", - "spaced-comment": ["error", "always", { - "exceptions": ["-", "="], - "markers": [ - "eslint", - "eslint-env", - "eslint-disable", - "eslint-enable", - "eslint-disable-line", - "eslint-disable-next-line", - "exported", - "globals", - "istanbul" - ] - }] + "spaced-comment": [ + "error", + "always", + { + exceptions: ["-", "="], + markers: [ + "eslint", + "eslint-env", + "eslint-disable", + "eslint-enable", + "eslint-disable-line", + "eslint-disable-next-line", + "exported", + "globals", + "istanbul" + ] + } + ] } }; diff --git a/test/textlint-rule-eslint-test.ts b/test/textlint-rule-eslint-test.ts index 4369dbd..f314f71 100644 --- a/test/textlint-rule-eslint-test.ts +++ b/test/textlint-rule-eslint-test.ts @@ -10,110 +10,90 @@ const WrongCode1 = "var a = 1"; const WrongCode2 = "var a = 1; var b = 2"; // @ts-expect-error tester.run("textlint-rule-eslint", rule, { - valid: [ - { - text: "`var a = 1;`", - options: { - configFile: configFilePath - } - }, - { - text: "```js\n" + - "var a = 1;\n" + - "```", - options: { - configFile: configFilePath - } - }, - { - text: "```\n\n" + - "++++++" + - "```", - options: { - configFile: configFilePath - } - }, - { - text: "```js\n\n" + - "var a = 1;\n\n" + - "```", - options: { - configFile: configFilePath - } - }, - { - text: "```js\n\n" + - "+++1+++\n" + - "```", - options: { - configFile: configFilePath, - ignoreParsingErrors: true - } - }, - ], - invalid: [ - { - text: "```js\n" + - "+++1+++\n" + - "```", - errors: [ + valid: [ { - message: "Parsing error: Assigning to rvalue", - line: 2, - column: 4 - } - ], - options: { - configFile: configFilePath - } - }, - { - text: "```js\n" + - WrongCode1 + "\n" + - "```", - output: "```js\n" + - WrongCode1 + ";\n" + - "```", - errors: [ + text: "`var a = 1;`", + options: { + configFile: configFilePath + } + }, { - message: "semi: Missing semicolon.", - line: 2, - column: 10 - } - ], - options: { - configFile: configFilePath - } - }, - { - text: "```javascript\n" + - "var a = 1\n" + - "var b = 2\n" + - "```", - output: "```javascript\n" + - "var a = 1;\n" + - "var b = 2;\n" + - "```", - errors: [ + text: "```js\n" + "var a = 1;\n" + "```", + options: { + configFile: configFilePath + } + }, { - message: "semi: Missing semicolon.", - line: 2, - column: 10 + text: "```\n\n" + "++++++" + "```", + options: { + configFile: configFilePath + } }, { - message: "semi: Missing semicolon.", - line: 3, - column: 10 + text: "```js\n\n" + "var a = 1;\n\n" + "```", + options: { + configFile: configFilePath + } + }, + { + text: "```js\n\n" + "+++1+++\n" + "```", + options: { + configFile: configFilePath, + ignoreParsingErrors: true + } } - ], - options: { - configFile: configFilePath - } - }, - // no-dupe-keys error - // It test that report range as error - { - text: ` + ], + invalid: [ + { + text: "```js\n" + "+++1+++\n" + "```", + errors: [ + { + message: "Parsing error: Assigning to rvalue", + line: 2, + column: 4 + } + ], + options: { + configFile: configFilePath + } + }, + { + text: "```js\n" + WrongCode1 + "\n" + "```", + output: "```js\n" + WrongCode1 + ";\n" + "```", + errors: [ + { + message: "semi: Missing semicolon.", + line: 2, + column: 10 + } + ], + options: { + configFile: configFilePath + } + }, + { + text: "```javascript\n" + "var a = 1\n" + "var b = 2\n" + "```", + output: "```javascript\n" + "var a = 1;\n" + "var b = 2;\n" + "```", + errors: [ + { + message: "semi: Missing semicolon.", + line: 2, + column: 10 + }, + { + message: "semi: Missing semicolon.", + line: 3, + column: 10 + } + ], + options: { + configFile: configFilePath + } + }, + // no-dupe-keys error + // It test that report range as error + { + text: ` \`\`\`js var foo = { bar: "baz", @@ -121,46 +101,36 @@ var foo = { }; \`\`\` `, - errors: [ - { - message: "no-dupe-keys: Duplicate key 'bar'.", - range: [40, 43] - } - ], - options: { - configFile: configFilePath - } - }, - // multiple - { - text: "```js\n" + - WrongCode1 + "\n" + - "```\n" + - "This is text.\n" + - "```js\n" + - WrongCode2 + "\n" + - "```", - output: "```js\n" + - WrongCode1 + ";\n" + - "```\n" + - "This is text.\n" + - "```js\n" + - WrongCode2 + ";\n" + - "```", - errors: [ + errors: [ + { + message: "no-dupe-keys: Duplicate key 'bar'.", + range: [40, 43] + } + ], + options: { + configFile: configFilePath + } + }, + // multiple { - message: "semi: Missing semicolon.", - line: 2, - column: 10 - }, { - message: "semi: Missing semicolon.", - line: 6, - column: 21 + text: "```js\n" + WrongCode1 + "\n" + "```\n" + "This is text.\n" + "```js\n" + WrongCode2 + "\n" + "```", + output: + "```js\n" + WrongCode1 + ";\n" + "```\n" + "This is text.\n" + "```js\n" + WrongCode2 + ";\n" + "```", + errors: [ + { + message: "semi: Missing semicolon.", + line: 2, + column: 10 + }, + { + message: "semi: Missing semicolon.", + line: 6, + column: 21 + } + ], + options: { + configFile: configFilePath + } } - ], - options: { - configFile: configFilePath - } - } - ] + ] });