forked from oxc-project/oxc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tasks): Re-introduce tasks/lint_rules (oxc-project#2166)
Part of oxc-project#2020
- Loading branch information
Showing
9 changed files
with
441 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# tasks/lint_rules2 | ||
|
||
Task to update implementation progress for each linter plugin. | ||
|
||
```sh | ||
Usage: | ||
$ cmd [--target=<pluginName>]... [--update] [--help] | ||
|
||
Options: | ||
--target, -t: Which plugin to target, multiple allowed | ||
--update: Update the issue instead of printing to stdout | ||
--help, -h: Print this help message | ||
``` | ||
|
||
Environment variables `GITHUB_TOKEN` is required when `--update` is specified. | ||
|
||
## Design | ||
|
||
- Always install `eslint-plugin-XXX@latest` from npm | ||
- Load them through ESLint Node.js API | ||
- https://eslint.org/docs/latest/integrate/nodejs-api#linter | ||
- List all their plugin rules(name, deprecated, recommended, docs, etc...) | ||
- List all our implemented rules(name) | ||
- Combine these lists and render as markdown | ||
- Update GitHub issue body | ||
|
||
## FAQ | ||
|
||
- Why is this task written in Node.js? Why not Rust? | ||
- Some plugins do not provide static rules list | ||
- https://github.com/jest-community/eslint-plugin-jest/ | ||
- Easiest way to collect the list is just evaluating config file in JavaScript | ||
- Why `.cjs`? | ||
- To keep dependencies as simple as possible | ||
- Some plugins only provide their module as CommonJS | ||
- So, CommonJS is the only way to go without bundling |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"compilerOptions": { | ||
"checkJs": true, | ||
"module": "node", | ||
"moduleResolution": "node", | ||
"lib": ["esnext"], | ||
"target": "esnext", | ||
"strict": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"private": true, | ||
"name": "lint_rules2", | ||
"main": "./src/main.cjs", | ||
"version": "0.0.0", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"dependencies": { | ||
"@typescript-eslint/eslint-plugin": "latest", | ||
"eslint": "latest", | ||
"eslint-plugin-import": "latest", | ||
"eslint-plugin-jest": "latest", | ||
"eslint-plugin-jsdoc": "latest", | ||
"eslint-plugin-n": "latest", | ||
"eslint-plugin-unicorn": "latest" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
const { Linter } = require("eslint"); | ||
|
||
// NOTICE! | ||
// Plugins do not provide their type definitions, and also `@types/*` do not exist! | ||
// Even worse, every plugin has slightly different types, different way of configuration in detail... | ||
// | ||
// So here, we need to list all rules while normalizing recommended and deprecated flags. | ||
// - rule.meta.docs.recommended | ||
// - rule.meta.deprecated | ||
|
||
// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/index.ts | ||
const { | ||
rules: pluginTypeScriptAllRules, | ||
configs: pluginTypeScriptConfigs, | ||
} = require("@typescript-eslint/eslint-plugin"); | ||
// https://github.com/eslint-community/eslint-plugin-n/blob/master/lib/index.js | ||
const { rules: pluginNAllRules } = require("eslint-plugin-n"); | ||
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/index.js | ||
const { | ||
rules: pluginUnicornAllRules, | ||
configs: pluginUnicornConfigs, | ||
} = require("eslint-plugin-unicorn"); | ||
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/src/index.js | ||
const { | ||
// @ts-expect-error: Module has no exported member | ||
rules: pluginJSDocAllRules, | ||
// @ts-expect-error: Module has no exported member | ||
configs: pluginJSDocConfigs, | ||
} = require("eslint-plugin-jsdoc"); | ||
// https://github.com/import-js/eslint-plugin-import/blob/main/src/index.js | ||
const { | ||
rules: pluginImportAllRules, | ||
configs: pluginImportConfigs, | ||
} = require("eslint-plugin-import"); | ||
// https://github.com/jest-community/eslint-plugin-jest/blob/main/src/index.ts | ||
const { rules: pluginJestAllRules } = require("eslint-plugin-jest"); | ||
|
||
// All rules(including deprecated, recommended) are loaded initially. | ||
exports.createESLintLinter = () => new Linter(); | ||
|
||
/** @param {import("eslint").Linter} linter */ | ||
exports.loadPluginTypeScriptRules = (linter) => { | ||
// We want to list all rules but not support type-checked rules | ||
const pluginTypeScriptDisableTypeCheckedRules = new Map( | ||
Object.entries(pluginTypeScriptConfigs["disable-type-checked"].rules), | ||
); | ||
for (const [name, rule] of Object.entries(pluginTypeScriptAllRules)) { | ||
if ( | ||
pluginTypeScriptDisableTypeCheckedRules.has(`@typescript-eslint/${name}`) | ||
) | ||
continue; | ||
|
||
const prefixedName = `typescript/${name}`; | ||
|
||
linter.defineRule(prefixedName, rule); | ||
} | ||
}; | ||
|
||
/** @param {import("eslint").Linter} linter */ | ||
exports.loadPluginNRules = (linter) => { | ||
for (const [name, rule] of Object.entries(pluginNAllRules)) { | ||
const prefixedName = `n/${name}`; | ||
|
||
// @ts-expect-error: The types of 'meta.fixable', 'null' is not assignable to type '"code" | "whitespace" | undefined'. | ||
linter.defineRule(prefixedName, rule); | ||
} | ||
}; | ||
|
||
/** @param {import("eslint").Linter} linter */ | ||
exports.loadPluginUnicornRules = (linter) => { | ||
const pluginUnicornRecommendedRules = new Map( | ||
Object.entries(pluginUnicornConfigs.recommended.rules), | ||
); | ||
for (const [name, rule] of Object.entries(pluginUnicornAllRules)) { | ||
const prefixedName = `unicorn/${name}`; | ||
|
||
// If name is presented and value is not "off", it is recommended | ||
const recommendedValue = pluginUnicornRecommendedRules.get(prefixedName); | ||
// @ts-expect-error: `rule.meta.docs` is possibly `undefined` | ||
rule.meta.docs.recommended = recommendedValue && recommendedValue !== "off"; | ||
|
||
linter.defineRule(prefixedName, rule); | ||
} | ||
}; | ||
|
||
/** @param {import("eslint").Linter} linter */ | ||
exports.loadPluginJSDocRules = (linter) => { | ||
const pluginJSDocRecommendedRules = new Map( | ||
Object.entries(pluginJSDocConfigs.recommended.rules), | ||
); | ||
for (const [name, rule] of Object.entries(pluginJSDocAllRules)) { | ||
const prefixedName = `jsdoc/${name}`; | ||
|
||
// If name is presented and value is not "off", it is recommended | ||
const recommendedValue = pluginJSDocRecommendedRules.get(prefixedName); | ||
rule.meta.docs.recommended = recommendedValue && recommendedValue !== "off"; | ||
|
||
linter.defineRule(prefixedName, rule); | ||
} | ||
}; | ||
|
||
/** @param {import("eslint").Linter} linter */ | ||
exports.loadPluginImportRules = (linter) => { | ||
const pluginImportRecommendedRules = new Map( | ||
// @ts-expect-error: Property 'rules' does not exist on type 'Object'. | ||
Object.entries(pluginImportConfigs.recommended.rules), | ||
); | ||
for (const [name, rule] of Object.entries(pluginImportAllRules)) { | ||
const prefixedName = `import/${name}`; | ||
|
||
// @ts-expect-error: Property 'recommended' does not exist on type | ||
rule.meta.docs.recommended = pluginImportRecommendedRules.has(prefixedName); | ||
|
||
// @ts-expect-error: The types of 'meta.type', 'string' is not assignable to type '"problem" | "suggestion" | "layout" | undefined'. | ||
linter.defineRule(prefixedName, rule); | ||
} | ||
}; | ||
|
||
/** @param {import("eslint").Linter} linter */ | ||
exports.loadPluginJestRules = (linter) => { | ||
for (const [name, rule] of Object.entries(pluginJestAllRules)) { | ||
const prefixedName = `jest/${name}`; | ||
|
||
// Presented but type is `string | false` | ||
rule.meta.docs.recommended = typeof rule.meta.docs.recommended === "string"; | ||
|
||
linter.defineRule(prefixedName, rule); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
const { parseArgs } = require("node:util"); | ||
const { | ||
createESLintLinter, | ||
loadPluginTypeScriptRules, | ||
loadPluginNRules, | ||
loadPluginUnicornRules, | ||
loadPluginJSDocRules, | ||
loadPluginImportRules, | ||
loadPluginJestRules, | ||
} = require("./eslint-rules.cjs"); | ||
const { | ||
createRuleEntries, | ||
readAllImplementedRuleNames, | ||
updateNotSupportedStatus, | ||
updateImplementedStatus, | ||
} = require("./oxlint-rules.cjs"); | ||
const { renderRulesList, renderLayout } = require("./output-markdown.cjs"); | ||
|
||
const ALL_TARGET_PLUGIN_NAMES = new Set([ | ||
"eslint", | ||
"typescript", | ||
"n", | ||
"unicorn", | ||
"jsdoc", | ||
"import", | ||
"jest", | ||
]); | ||
|
||
const HELP = ` | ||
Usage: | ||
$ cmd [--target=<pluginName>]... [--update] [--help] | ||
Options: | ||
--target, -t: Which plugin to target, multiple allowed | ||
--update: Update the issue instead of printing to stdout | ||
--help, -h: Print this help message | ||
Plugins: ${[...ALL_TARGET_PLUGIN_NAMES].join(", ")} | ||
`; | ||
|
||
(async () => { | ||
// | ||
// Parse arguments | ||
// | ||
const { values } = parseArgs({ | ||
options: { | ||
target: { type: "string", short: "t", multiple: true }, | ||
update: { type: "boolean" }, | ||
help: { type: "boolean", short: "h" }, | ||
}, | ||
}); | ||
|
||
if (values.help) return console.log(HELP); | ||
|
||
const targetPluginNames = new Set(values.target ?? ALL_TARGET_PLUGIN_NAMES); | ||
for (const pluginName of targetPluginNames) { | ||
if (!ALL_TARGET_PLUGIN_NAMES.has(pluginName)) | ||
throw new Error(`Unknown plugin name: ${pluginName}`); | ||
} | ||
|
||
// | ||
// Load linter and all plugins | ||
// | ||
const linter = createESLintLinter(); | ||
loadPluginTypeScriptRules(linter); | ||
loadPluginNRules(linter); | ||
loadPluginUnicornRules(linter); | ||
loadPluginJSDocRules(linter); | ||
loadPluginImportRules(linter); | ||
loadPluginJestRules(linter); | ||
// TODO: more plugins | ||
|
||
// | ||
// Generate entry and update status | ||
// | ||
const ruleEntries = createRuleEntries(linter.getRules()); | ||
const implementedRuleNames = await readAllImplementedRuleNames(); | ||
updateImplementedStatus(ruleEntries, implementedRuleNames); | ||
updateNotSupportedStatus(ruleEntries); | ||
|
||
// | ||
// Render list and update if necessary | ||
// | ||
await Promise.allSettled( | ||
Array.from(targetPluginNames).map(async (pluginName) => { | ||
const listPart = renderRulesList(ruleEntries, pluginName); | ||
const content = renderLayout(listPart, pluginName); | ||
|
||
if (!values.update) return console.log(content); | ||
// TODO: Update issue | ||
}), | ||
); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/** | ||
* @param {import("./oxlint-rules.cjs").RuleEntries} ruleEntries | ||
* @param {string} pluginName | ||
*/ | ||
exports.renderRulesList = (ruleEntries, pluginName) => { | ||
/* prettier-ignore */ | ||
const list = [ | ||
"| Name | Kind | Status | Docs |", | ||
"| :--- | :--: | :----: | :--- |", | ||
]; | ||
|
||
for (const [name, entry] of ruleEntries) { | ||
if (!name.startsWith(`${pluginName}/`)) continue; | ||
|
||
// These should be exclusive, but show it for sure... | ||
let kind = ""; | ||
if (entry.isRecommended) kind += "🍀"; | ||
if (entry.isDeprecated) kind += "⚠️"; | ||
|
||
let status = ""; | ||
if (entry.isImplemented) status += "✨"; | ||
if (entry.isNotSupported) status += "🚫"; | ||
|
||
list.push(`| ${name} | ${kind} | ${status} | ${entry.docsUrl} |`); | ||
} | ||
|
||
return ` | ||
- Kind: 🍀 = recommended | ⚠️ = deprecated | ||
- Status: ✨ = implemented | 🚫 = not supported | ||
${list.join("\n")} | ||
`; | ||
}; | ||
|
||
/** | ||
* @param {string} listPart | ||
* @param {string} pluginName | ||
*/ | ||
exports.renderLayout = (listPart, pluginName) => ` | ||
> [!WARNING] | ||
> This comment is maintained by CI. Do not edit this comment directly. | ||
> To update comment template, see https://github.com/oxc-project/oxc/tree/main/tasks/lint_rules | ||
## Rules | ||
${listPart} | ||
## Getting started | ||
\`\`\`sh | ||
just new-${pluginName}-rule <RULE_NAME> | ||
\`\`\` | ||
Then register the rule in \`crates/oxc_linter/src/rules.rs\` and also \`declare_all_lint_rules\` at the bottom. | ||
`; |
Oops, something went wrong.