Skip to content

Commit

Permalink
feat(tasks): tasks/lint_rules2 re-implement --update (#2173)
Browse files Browse the repository at this point in the history
- Add `pluginMeta` field to make issue more informative
- Implement `--update` flag to update GitHub issue

With lots of refactoring.
  • Loading branch information
leaysgur authored Jan 26, 2024
1 parent ee5b968 commit b268cb2
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 136 deletions.
3 changes: 0 additions & 3 deletions tasks/lint_rules2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
"name": "lint_rules2",
"main": "./src/main.cjs",
"version": "0.0.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@next/eslint-plugin-next": "latest",
"@typescript-eslint/eslint-plugin": "latest",
Expand Down
65 changes: 51 additions & 14 deletions tasks/lint_rules2/src/eslint-rules.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,8 @@ const {
// https://github.com/vercel/next.js/blob/canary/packages/eslint-plugin-next/src/index.ts
const { rules: pluginNextAllRules } = require("@next/eslint-plugin-next");

// All rules(including deprecated, recommended) are loaded initially.
exports.createESLintLinter = () => new Linter();

/** @param {import("eslint").Linter} linter */
exports.loadPluginTypeScriptRules = (linter) => {
const 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),
Expand All @@ -76,7 +73,7 @@ exports.loadPluginTypeScriptRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginNRules = (linter) => {
const loadPluginNRules = (linter) => {
for (const [name, rule] of Object.entries(pluginNAllRules)) {
const prefixedName = `n/${name}`;

Expand All @@ -86,7 +83,7 @@ exports.loadPluginNRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginUnicornRules = (linter) => {
const loadPluginUnicornRules = (linter) => {
const pluginUnicornRecommendedRules = new Map(
Object.entries(pluginUnicornConfigs.recommended.rules),
);
Expand All @@ -103,7 +100,7 @@ exports.loadPluginUnicornRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginJSDocRules = (linter) => {
const loadPluginJSDocRules = (linter) => {
const pluginJSDocRecommendedRules = new Map(
Object.entries(pluginJSDocConfigs.recommended.rules),
);
Expand All @@ -119,7 +116,7 @@ exports.loadPluginJSDocRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginImportRules = (linter) => {
const loadPluginImportRules = (linter) => {
const pluginImportRecommendedRules = new Map(
// @ts-expect-error: Property 'rules' does not exist on type 'Object'.
Object.entries(pluginImportConfigs.recommended.rules),
Expand All @@ -136,7 +133,7 @@ exports.loadPluginImportRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginJSXA11yRules = (linter) => {
const loadPluginJSXA11yRules = (linter) => {
const pluginJSXA11yRecommendedRules = new Map(
Object.entries(pluginJSXA11yConfigs.recommended.rules),
);
Expand All @@ -155,7 +152,7 @@ exports.loadPluginJSXA11yRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginJestRules = (linter) => {
const loadPluginJestRules = (linter) => {
for (const [name, rule] of Object.entries(pluginJestAllRules)) {
const prefixedName = `jest/${name}`;

Expand All @@ -167,7 +164,7 @@ exports.loadPluginJestRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginReactRules = (linter) => {
const loadPluginReactRules = (linter) => {
for (const [name, rule] of Object.entries(pluginReactAllRules)) {
const prefixedName = `react/${name}`;

Expand All @@ -176,7 +173,7 @@ exports.loadPluginReactRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginReactHooksRules = (linter) => {
const loadPluginReactHooksRules = (linter) => {
for (const [name, rule] of Object.entries(pluginReactHooksAllRules)) {
const prefixedName = `react-hooks/${name}`;

Expand All @@ -186,7 +183,7 @@ exports.loadPluginReactHooksRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginReactPerfRules = (linter) => {
const loadPluginReactPerfRules = (linter) => {
const pluginReactPerfRecommendedRules = new Map(
Object.entries(pluginReactPerfConfigs.recommended.rules),
);
Expand All @@ -201,10 +198,50 @@ exports.loadPluginReactPerfRules = (linter) => {
};

/** @param {import("eslint").Linter} linter */
exports.loadPluginNextRules = (linter) => {
const loadPluginNextRules = (linter) => {
for (const [name, rule] of Object.entries(pluginNextAllRules)) {
const prefixedName = `nextjs/${name}`;

linter.defineRule(prefixedName, rule);
}
};

/**
* @typedef {{
* npm: string;
* issueNo: number;
* }} TargetPluginMeta
* @type {Map<string, TargetPluginMeta>}
*/
exports.ALL_TARGET_PLUGINS = new Map([
["eslint", { npm: "eslint", issueNo: 479 }],
["typescript", { npm: "@typescript-eslint/eslint-plugin", issueNo: 503 }],
["n", { npm: "eslint-plugin-n", issueNo: 493 }],
["unicorn", { npm: "eslint-plugin-unicorn", issueNo: 684 }],
["jsdoc", { npm: "eslint-plugin-jsdoc", issueNo: 1170 }],
["import", { npm: "eslint-plugin-import", issueNo: 1117 }],
["jsx-a11y", { npm: "eslint-plugin-jsx-a11y", issueNo: 1141 }],
["jest", { npm: "eslint-plugin-jest", issueNo: 492 }],
["react", { npm: "eslint-plugin-react", issueNo: 1022 }],
["react-hooks", { npm: "eslint-plugin-react-hooks", issueNo: -1 }], // TODO: Fill issueNo
["react-perf", { npm: "eslint-plugin-react-perf", issueNo: 2041 }],
["nextjs", { npm: "@next/eslint-plugin-next", issueNo: 1929 }],
]);

// All rules(including deprecated, recommended) are loaded initially.
exports.createESLintLinter = () => new Linter();

/** @param {import("eslint").Linter} linter */
exports.loadTargetPluginRules = (linter) => {
loadPluginTypeScriptRules(linter);
loadPluginNRules(linter);
loadPluginUnicornRules(linter);
loadPluginJSDocRules(linter);
loadPluginImportRules(linter);
loadPluginJSXA11yRules(linter);
loadPluginJestRules(linter);
loadPluginReactRules(linter);
loadPluginReactHooksRules(linter);
loadPluginReactPerfRules(linter);
loadPluginNextRules(linter);
};
78 changes: 27 additions & 51 deletions tasks/lint_rules2/src/main.cjs
Original file line number Diff line number Diff line change
@@ -1,40 +1,16 @@
const { parseArgs } = require("node:util");
const {
ALL_TARGET_PLUGINS,
createESLintLinter,
loadPluginTypeScriptRules,
loadPluginNRules,
loadPluginUnicornRules,
loadPluginJSDocRules,
loadPluginImportRules,
loadPluginJSXA11yRules,
loadPluginJestRules,
loadPluginReactRules,
loadPluginReactHooksRules,
loadPluginReactPerfRules,
loadPluginNextRules,
loadTargetPluginRules,
} = 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",
"jsx-a11y",
"jest",
"react",
"react-hooks",
"react-perf",
"nextjs",
]);
const { renderMarkdown } = require("./markdown-renderer.cjs");
const { updateGitHubIssue } = require("./result-reporter.cjs");

const HELP = `
Usage:
Expand All @@ -45,7 +21,7 @@ Options:
--update: Update the issue instead of printing to stdout
--help, -h: Print this help message
Plugins: ${[...ALL_TARGET_PLUGIN_NAMES].join(", ")}
Plugins: ${Array.from(ALL_TARGET_PLUGINS.keys()).join(", ")}
`;

(async () => {
Expand All @@ -54,6 +30,7 @@ Plugins: ${[...ALL_TARGET_PLUGIN_NAMES].join(", ")}
//
const { values } = parseArgs({
options: {
// Mainly for debugging
target: { type: "string", short: "t", multiple: true },
update: { type: "boolean" },
help: { type: "boolean", short: "h" },
Expand All @@ -62,46 +39,45 @@ Plugins: ${[...ALL_TARGET_PLUGIN_NAMES].join(", ")}

if (values.help) return console.log(HELP);

const targetPluginNames = new Set(values.target ?? ALL_TARGET_PLUGIN_NAMES);
const targetPluginNames = new Set(values.target ?? ALL_TARGET_PLUGINS.keys());
for (const pluginName of targetPluginNames) {
if (!ALL_TARGET_PLUGIN_NAMES.has(pluginName))
throw new Error(`Unknown plugin name: ${pluginName}`);
if (!ALL_TARGET_PLUGINS.has(pluginName)) {
console.error(`Unknown plugin name: ${pluginName}`);
return;
}
}

//
// Load linter and all plugins
//
const linter = createESLintLinter();
loadPluginTypeScriptRules(linter);
loadPluginNRules(linter);
loadPluginUnicornRules(linter);
loadPluginJSDocRules(linter);
loadPluginImportRules(linter);
loadPluginJSXA11yRules(linter);
loadPluginJestRules(linter);
loadPluginReactRules(linter);
loadPluginReactHooksRules(linter);
loadPluginReactPerfRules(linter);
loadPluginNextRules(linter);
loadTargetPluginRules(linter);

//
// Generate entry and update status
//
const ruleEntries = createRuleEntries(linter.getRules());
const implementedRuleNames = await readAllImplementedRuleNames();
updateImplementedStatus(ruleEntries, implementedRuleNames);
await updateImplementedStatus(ruleEntries);
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);
const results = await Promise.allSettled(
Array.from(targetPluginNames).map((pluginName) => {
const pluginMeta =
/** @type {import("./eslint-rules.cjs").TargetPluginMeta} */ (
ALL_TARGET_PLUGINS.get(pluginName)
);
const content = renderMarkdown(pluginName, pluginMeta, ruleEntries);

if (!values.update) return console.log(content);
// TODO: Update issue
if (!values.update) return Promise.resolve(content);
// Requires `env.GITHUB_TOKEN`
return updateGitHubIssue(pluginMeta, content);
}),
);
for (const result of results) {
if (result.status === "fulfilled") console.log(result.value);
if (result.status === "rejected") console.error(result.reason);
}
})();
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
/**
* @param {import("./oxlint-rules.cjs").RuleEntries} ruleEntries
* @param {string} pluginName
* @param {import("./eslint-rules.cjs").TargetPluginMeta} pluginMeta
* @param {string} listPart
*/
exports.renderRulesList = (ruleEntries, pluginName) => {
const renderLayout = (pluginName, pluginMeta, listPart) => `
> [!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
This is tracking issue for \`${pluginMeta.npm}\`.
## 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.
`;

/** @param {[string, import("./oxlint-rules.cjs").RuleEntry][]} ruleEntries */
const renderRulesList = (ruleEntries) => {
/* 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 += "🍀";
Expand All @@ -33,22 +52,13 @@ ${list.join("\n")}
};

/**
* @param {string} listPart
* @param {string} pluginName
* @param {import("./eslint-rules.cjs").TargetPluginMeta} pluginMeta
* @param {import("./oxlint-rules.cjs").RuleEntries} ruleEntries
*/
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.
`;
exports.renderMarkdown = (pluginName, pluginMeta, ruleEntries) => {
const pluginRules = Array.from(ruleEntries).filter(([name]) =>
name.startsWith(`${pluginName}/`),
);
return renderLayout(pluginName, pluginMeta, renderRulesList(pluginRules));
};
Loading

0 comments on commit b268cb2

Please sign in to comment.