diff --git a/apps/oxlint/src/lib.rs b/apps/oxlint/src/lib.rs index 2c4744b694f29..d2a3fe02ab4d6 100644 --- a/apps/oxlint/src/lib.rs +++ b/apps/oxlint/src/lib.rs @@ -44,3 +44,15 @@ mod js_plugins; ))] #[global_allocator] static GLOBAL: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc; + +/// Return a JSON blob containing metadata for all available oxlint rules. +/// +/// This uses the internal JSON output formatter to generate the full list. +/// +/// # Panics +/// Panics if the JSON generation fails, which should never happen under normal circumstances. +pub fn get_all_rules_json() -> String { + use crate::output_formatter::{OutputFormat, OutputFormatter}; + + OutputFormatter::new(OutputFormat::Json).all_rules().expect("Failed to generate rules JSON") +} diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index 13bc95dfa1481..69f5c326f3e90 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -323,7 +323,7 @@ impl CliRunner { let table = RuleTable::default(); for section in &table.sections { - let md = section.render_markdown_table_cli(None, &enabled); + let md = section.render_markdown_table_cli(&enabled); print_and_flush_stdout(stdout, &md); print_and_flush_stdout(stdout, "\n"); } diff --git a/apps/oxlint/src/output_formatter/default.rs b/apps/oxlint/src/output_formatter/default.rs index cde336314f388..490d7e3d260d7 100644 --- a/apps/oxlint/src/output_formatter/default.rs +++ b/apps/oxlint/src/output_formatter/default.rs @@ -15,7 +15,7 @@ impl InternalFormatter for DefaultOutputFormatter { let mut output = String::new(); let table = RuleTable::default(); for section in table.sections { - output.push_str(section.render_markdown_table(None).as_str()); + output.push_str(section.render_markdown_table().as_str()); output.push('\n'); } output.push_str(format!("Default: {}\n", table.turned_on_by_default_count).as_str()); diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index f856316fb18a0..4d9efa358940a 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -119,11 +119,7 @@ impl RuleTableSection { /// When `enabled` is [`Some`], an "Enabled?" column is added and the set /// is used to determine which rules are enabled. When `enabled` is /// [`None`], the column is omitted (used by docs rendering). - fn render_markdown_table_inner( - &self, - link_prefix: Option<&str>, - enabled: Option<&FxHashSet<&str>>, - ) -> String { + fn render_markdown_table_inner(&self, enabled: Option<&FxHashSet<&str>>) -> String { const FIX_EMOJI_COL_WIDTH: usize = 10; const DEFAULT_EMOJI_COL_WIDTH: usize = 9; const ENABLED_EMOJI_COL_WIDTH: usize = 10; @@ -182,11 +178,7 @@ impl RuleTableSection { let (default, default_width) = if row.turned_on_by_default { ("✅", DEFAULT - 1) } else { ("", DEFAULT) }; - let rendered_name = if let Some(prefix) = link_prefix { - Cow::Owned(format!("[{rule_name}]({prefix}/{plugin_name}/{rule_name}.html)")) - } else { - Cow::Borrowed(rule_name) - }; + let rendered_name = Cow::Borrowed(rule_name); // Improved mapping for emoji column alignment, allowing FIX to grow for negative display widths let (fix_emoji, fix_emoji_width) = Self::calculate_fix_emoji_width(row.autofix.emoji(), FIX); @@ -230,19 +222,12 @@ impl RuleTableSection { } /// Renders all the rules in this section as a markdown table. - /// - /// Provide [`Some`] prefix to render the rule name as a link. Provide - /// [`None`] to just display the rule name as text. - pub fn render_markdown_table(&self, link_prefix: Option<&str>) -> String { - self.render_markdown_table_inner(link_prefix, None) + pub fn render_markdown_table(&self) -> String { + self.render_markdown_table_inner(None) } - pub fn render_markdown_table_cli( - &self, - link_prefix: Option<&str>, - enabled: &FxHashSet<&str>, - ) -> String { - self.render_markdown_table_inner(link_prefix, Some(enabled)) + pub fn render_markdown_table_cli(&self, enabled: &FxHashSet<&str>) -> String { + self.render_markdown_table_inner(Some(enabled)) } } @@ -264,39 +249,18 @@ mod test { fn test_table_no_links() { let options = Options::gfm(); for section in &table().sections { - let rendered_table = section.render_markdown_table(None); - assert!(!rendered_table.is_empty()); - assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len()); - - let html = to_html_with_options(&rendered_table, &options).unwrap(); - assert!(!html.is_empty()); - assert!(html.contains("")); - } - } - - #[test] - fn test_table_with_links() { - const PREFIX: &str = "/foo/bar"; - const PREFIX_WITH_SLASH: &str = "/foo/bar/"; - - let options = Options::gfm(); - - for section in &table().sections { - let rendered_table = section.render_markdown_table(Some(PREFIX)); + let rendered_table = section.render_markdown_table(); assert!(!rendered_table.is_empty()); assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len()); let html = to_html_with_options(&rendered_table, &options).unwrap(); assert!(!html.is_empty()); assert!(html.contains("
")); - assert!(html.contains(PREFIX_WITH_SLASH)); } } #[test] fn test_table_cli_enabled_column() { - const PREFIX: &str = "/foo/bar"; - for section in &table().sections { // enable the first rule in the section for the CLI view let mut enabled = FxHashSet::default(); @@ -304,7 +268,7 @@ mod test { enabled.insert(first.name); } - let rendered_table = section.render_markdown_table_cli(Some(PREFIX), &enabled); + let rendered_table = section.render_markdown_table_cli(&enabled); assert!(!rendered_table.is_empty()); // same number of lines as other renderer (header + desc + separator + rows + trailing newline) assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len()); diff --git a/justfile b/justfile index 86729a811f157..c0127a43aa05e 100755 --- a/justfile +++ b/justfile @@ -253,7 +253,7 @@ watch-playground: # When testing changes to the website documentation, you may also want to run `pnpm run fmt` # in the website directory. website path: - cargo run -p website_linter rules --table {{path}}/src/docs/guide/usage/linter/generated-rules.md --rule-docs {{path}}/src/docs/guide/usage/linter/rules --git-ref $(git rev-parse HEAD) --rule-count {{path}}/src/docs/guide/usage + cargo run -p website_linter rules --rules-json {{path}}/.vitepress/data/rules.json --rule-docs {{path}}/src/docs/guide/usage/linter/rules --git-ref $(git rev-parse HEAD) --rule-count {{path}}/src/docs/guide/usage cargo run -p website_linter cli > {{path}}/src/docs/guide/usage/linter/generated-cli.md cargo run -p website_linter schema-markdown > {{path}}/src/docs/guide/usage/linter/generated-config.md cargo run -p website_formatter cli > {{path}}/src/docs/guide/usage/formatter/generated-cli.md diff --git a/tasks/website_linter/src/rules/mod.rs b/tasks/website_linter/src/rules/mod.rs index f95b74d2879a6..f2b261e30719a 100644 --- a/tasks/website_linter/src/rules/mod.rs +++ b/tasks/website_linter/src/rules/mod.rs @@ -1,10 +1,8 @@ #![expect(clippy::print_stderr)] mod doc_page; mod html; -mod table; use std::{ - borrow::Cow, env, fs, path::{Path, PathBuf}, }; @@ -14,13 +12,12 @@ use html::HtmlWriter; use oxc_linter::{Oxlintrc, table::RuleTable}; use pico_args::Arguments; use schemars::{SchemaGenerator, r#gen::SchemaSettings}; -use table::render_rules_table; const HELP: &str = " usage: linter-rules [args] Arguments: - -t,--table Path to file where rule markdown table will be saved. + -j,--rules-json Path to file where rules json blob will be saved. -r,--rule-docs Path to directory where rule doc pages will be saved. A directory will be created if one doesn't exist. -c,--rule-count Path to directory where rule count data file will be saved. @@ -32,11 +29,11 @@ Arguments: /// `print_rules` /// -/// `cargo run -p website linter-rules --table -/// /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/generated-rules.md -/// --rule-docs /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/rules -/// --rule-count /path/to/oxc/oxc-project.github.io/src/docs/guide/usage -/// --git-ref dc9dc03872101c15b0d02f05ce45705565665829 +/// `cargo run -p website linter-rules +/// --rules-json /path/to/oxc/oxc-project.github.io/.vitepress/data/rules.json +/// --rule-docs /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/rules +/// --rule-count /path/to/oxc/oxc-project.github.io/src/docs/guide/usage +/// --git-ref dc9dc03872101c15b0d02f05ce45705565665829 /// ` /// #[expect(clippy::print_stdout)] @@ -48,30 +45,19 @@ pub fn print_rules(mut args: Arguments) { } let git_ref: Option = args.opt_value_from_str("--git-ref").unwrap(); - let table_path = args.opt_value_from_str::<_, PathBuf>(["-t", "--table"]).unwrap(); + let rules_json_path = args.opt_value_from_str::<_, PathBuf>(["-j", "--rules-json"]).unwrap(); let rules_dir = args.opt_value_from_str::<_, PathBuf>(["-r", "--rule-docs"]).unwrap(); let rule_count_dir = args.opt_value_from_str::<_, PathBuf>(["-c", "--rule-count"]).unwrap(); - let prefix = - rules_dir.as_ref().and_then(|p| p.as_os_str().to_str()).map_or(Cow::Borrowed(""), |p| { - if p.contains("src/docs") { - let split = p.split("src/docs").collect::>(); - assert!(split.len() > 1); - Cow::Owned("/docs".to_string() + split.last().unwrap()) - } else { - Cow::Borrowed(p) - } - }); - let mut generator = SchemaGenerator::new(SchemaSettings::default()); let table = RuleTable::new(Some(&mut generator)); - if let Some(table_path) = table_path { - let table_path = pwd.join(table_path).canonicalize().unwrap(); + if let Some(rules_json_path) = rules_json_path { + let rules_json_path = pwd.join(rules_json_path).canonicalize().unwrap(); - eprintln!("Rendering rules table..."); - let rules_table = render_rules_table(&table, prefix.as_ref()); - fs::write(table_path, rules_table).unwrap(); + eprintln!("Rendering rules JSON blob..."); + let rules_json = oxlint::get_all_rules_json(); + fs::write(rules_json_path, rules_json).unwrap(); } if let Some(rules_dir) = &rules_dir { diff --git a/tasks/website_linter/src/rules/table.rs b/tasks/website_linter/src/rules/table.rs deleted file mode 100644 index 788d99b56945b..0000000000000 --- a/tasks/website_linter/src/rules/table.rs +++ /dev/null @@ -1,42 +0,0 @@ -use oxc_linter::table::RuleTable; - -/// Renders the website's Rules page. Each [`category`] gets its own table with -/// links to documentation pages for each rule. -/// -/// `docs_prefix` is a path prefix to the base URL all rule documentation pages -/// share in common. -/// -/// [`category`]: oxc_linter::RuleCategory -pub fn render_rules_table(table: &RuleTable, docs_prefix: &str) -> String { - let total = table.total; - let turned_on_by_default_count = table.turned_on_by_default_count; - let rules_with_fixes = table.rules_with_fixes; - let body = table - .sections - .iter() - .map(|s| s.render_markdown_table(Some(docs_prefix))) - .collect::>() - .join("\n"); - - format!(" ---- -search: false ---- -# Rules - -The progress of all rule implementations is tracked [here](https://github.com/oxc-project/oxc/issues/481). - -- Total number of rules: {total} -- Rules turned on by default: {turned_on_by_default_count} -- Rules with fixes available: {rules_with_fixes} - -**Legend for 'Fixable?' column:** -- 🛠️: an auto-fix is available for this rule -- 💡: a suggestion is available for this rule -- ⚠️🛠️: a dangerous auto-fix is available for this rule -- ⚠️💡: a dangerous suggestion is available for this rule -- 🚧: an auto-fix or suggestion is possible, but currently not implemented - -{body} -").trim().to_string() -}