From 04b4bae5c53f39f8db080777d357d3950302bca7 Mon Sep 17 00:00:00 2001 From: Cam McHenry Date: Thu, 24 Oct 2024 03:47:07 -0400 Subject: [PATCH] feat(rulegen): add module definitions for new rules (#6845) Makes new rule creation a little bit easier by automatically adding the rule definition to `rules.rs` with the proper alphabetical order. Example: https://github.com/user-attachments/assets/c9584986-7d08-4f91-bce2-501a8441e446 --- tasks/rulegen/src/main.rs | 89 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tasks/rulegen/src/main.rs b/tasks/rulegen/src/main.rs index d4ff23a0d941d..d19ce9c934e91 100644 --- a/tasks/rulegen/src/main.rs +++ b/tasks/rulegen/src/main.rs @@ -780,4 +780,93 @@ fn main() { if let Err(err) = template.render(rule_kind) { eprintln!("failed to render {rule_name} rule template: {err}"); } + + if let Err(err) = add_rules_entry(&context, rule_kind) { + eprintln!("failed to add {rule_name} to rules file: {err}"); + } +} + +/// Adds a module definition for the given rule to the `rules.rs` file, and adds the rule to the +/// `declare_all_lint_rules!` macro block. +fn add_rules_entry(ctx: &Context, rule_kind: RuleKind) -> Result<(), Box> { + let rules_path = "crates/oxc_linter/src/rules.rs"; + let mut rules = std::fs::read_to_string(rules_path)?; + + let mod_name = match rule_kind { + RuleKind::ESLint => "eslint", + RuleKind::Import => "import", + RuleKind::Typescript => "typescript", + RuleKind::Jest => "jest", + RuleKind::React => "react", + RuleKind::ReactPerf => "react_perf", + RuleKind::Unicorn => "unicorn", + RuleKind::JSDoc => "jsdoc", + RuleKind::JSXA11y => "jsx_a11y", + RuleKind::Oxc => "oxc", + RuleKind::NextJS => "nextjs", + RuleKind::TreeShaking => "tree_shaking", + RuleKind::Promise => "promise", + RuleKind::Vitest => "vitest", + RuleKind::Node => "node", + RuleKind::Security => "security", + }; + let mod_def = format!("mod {mod_name}"); + let Some(mod_start) = rules.find(&mod_def) else { + return Err(format!("failed to find '{mod_def}' in {rules_path}").into()); + }; + let mod_end = &rules[mod_start..] + .find("}\n") + .ok_or(format!("failed to find end of '{mod_def}' module in {rules_path}"))?; + let mod_rules = &rules[mod_start..(*mod_end + mod_start)]; + + // find the rule name (`pub mod xyz;`) that comes alphabetically before the new rule mod def, + // otherwise just append it to the mod. + let rule_mod_def = format!("pub mod {};", ctx.kebab_rule_name); + let rule_mod_def_start = mod_rules + .lines() + .filter_map(|line| line.split_once("pub mod ").map(|(_, rest)| rest)) + .position(|rule_mod| rule_mod < &rule_mod_def) + .map(|i| i + 1) + .and_then(|i| rules[mod_start + i..].find("pub mod ").map(|j| i + j)) + .ok_or(format!( + "failed to find where to insert the new rule mod def ({rule_mod_def}) in {rules_path}" + ))?; + + rules.insert_str( + mod_start + rule_mod_def_start, + &format!(" pub mod {};\n", ctx.snake_rule_name), + ); + + // then, insert `{mod_name}::{rule_name};` in the `declare_all_lint_rules!` macro block + // in the correct position, alphabetically. + let declare_all_lint_rules_start = rules + .find("declare_all_lint_rules!") + .ok_or(format!("failed to find 'declare_all_lint_rules!' in {rules_path}"))?; + let rule_def = format!("{mod_name}::{};", ctx.snake_rule_name); + let rule_def_start = rules[declare_all_lint_rules_start..] + .lines() + .filter_map(|line| line.trim().split_once("::")) + .find_map(|(plugin, rule)| { + if plugin == mod_name && rule > &ctx.kebab_rule_name { + let def = format!("{plugin}::{rule}"); + rules.find(&def) + } else { + None + } + }) + .ok_or(format!( + "failed to find where to insert the new rule def ({rule_def}) in {rules_path}" + ))?; + rules.insert_str( + rule_def_start, + &format!( + "{mod_name}::{rule_name},\n ", + mod_name = mod_name, + rule_name = ctx.snake_rule_name + ), + ); + + std::fs::write(rules_path, rules)?; + + Ok(()) }