From 8f9dca2e90a8248c70bc78755a828f77b8ff4e62 Mon Sep 17 00:00:00 2001 From: Wren Date: Fri, 31 Oct 2025 22:34:44 -0400 Subject: [PATCH 01/10] display if rules are enabled when oxlint --rules is run --- apps/oxlint/src/lint.rs | 30 +++++-- apps/oxlint/src/output_formatter/mod.rs | 7 +- crates/oxc_linter/src/table.rs | 112 +++++++++++++++++++++--- 3 files changed, 125 insertions(+), 24 deletions(-) diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index d7583f3208e2b..568e5f26c33ea 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -17,6 +17,7 @@ use oxc_diagnostics::{DiagnosticSender, DiagnosticService, GraphicalReportHandle use oxc_linter::{ AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, ExternalLinter, ExternalPluginStore, InvalidFilterKind, LintFilter, LintOptions, LintRunner, LintServiceOptions, Linter, Oxlintrc, + table::RuleTable, }; use crate::{ @@ -46,12 +47,6 @@ impl CliRunner { let format_str = self.options.output_options.format; let output_formatter = OutputFormatter::new(format_str); - if self.options.list_rules { - if let Some(output) = output_formatter.all_rules() { - print_and_flush_stdout(stdout, &output); - } - return CliRunResult::None; - } let LintCommand { paths, @@ -305,6 +300,29 @@ impl CliRunner { let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store); + // If the user requested `--rules`, print a CLI-specific table that + // includes an "Enabled?" column based on the resolved configuration. + if self.options.list_rules { + // Build the set of enabled builtin rule names from the resolved config. + let enabled: FxHashSet<&str> = + config_store.rules().iter().map(|(rule, _)| rule.name()).collect(); + + let table = RuleTable::default(); + for section in &table.sections { + let md = section.render_markdown_table_cli(None, &enabled); + print_and_flush_stdout(stdout, &md); + print_and_flush_stdout(stdout, "\n"); + } + + print_and_flush_stdout( + stdout, + format!("Default: {}\n", table.turned_on_by_default_count).as_str(), + ); + print_and_flush_stdout(stdout, format!("Total: {}\n", table.total).as_str()); + + return CliRunResult::None; + } + let files_to_lint = paths .into_iter() .filter(|path| !ignore_matcher.should_ignore(Path::new(path))) diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs index 39a922fae62ea..5d7ece222f24e 100644 --- a/apps/oxlint/src/output_formatter/mod.rs +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -72,6 +72,7 @@ pub struct LintCommandInfo { /// The Formatter is then managed by [`OutputFormatter`]. trait InternalFormatter { /// Print all available rules by oxlint + #[allow(dead_code)] fn all_rules(&self) -> Option { None } @@ -108,12 +109,6 @@ impl OutputFormatter { } } - /// Print all available rules by oxlint - /// See [`InternalFormatter::all_rules`] for more details. - pub fn all_rules(&self) -> Option { - self.internal.all_rules() - } - /// At the end of the Lint command we may output extra information. pub fn lint_command_info(&self, lint_command_info: &LintCommandInfo) -> Option { self.internal.lint_command_info(lint_command_info) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index e1bca4dd476fc..caf9f08915da7 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -106,16 +106,25 @@ impl RuleTable { } impl RuleTableSection { - /// Renders all the rules in this section as a markdown table. + /// Internal helper that renders a markdown table for this section. /// - /// 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 { + /// 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 { const FIX_EMOJI_COL_WIDTH: usize = 10; const DEFAULT_EMOJI_COL_WIDTH: usize = 9; + const ENABLED_EMOJI_COL_WIDTH: usize = 9; /// text width, leave 2 spaces for padding const FIX: usize = FIX_EMOJI_COL_WIDTH - 2; const DEFAULT: usize = DEFAULT_EMOJI_COL_WIDTH - 2; + const ENABLED: usize = ENABLED_EMOJI_COL_WIDTH - 2; + + let include_enabled = enabled.is_some(); let mut s = String::new(); let category = &self.category; @@ -127,17 +136,41 @@ impl RuleTableSection { writeln!(s, "{}", category.description()).unwrap(); let x = ""; - writeln!( - s, - "| {: FIX { (emoji, 0) } else { (emoji, FIX - len) } }); - writeln!(s, "| {rendered_name:) -> String { + self.render_markdown_table_inner(link_prefix, 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)) + } } #[cfg(test)] @@ -202,4 +264,30 @@ mod test { assert!(html.contains(PREFIX_WITH_SLASH)); } } + + #[test] + fn test_table_cli_enabled_column() { + const PREFIX: &str = "/foo/bar"; + const PREFIX_WITH_SLASH: &str = "/foo/bar/"; + let options = Options::gfm(); + + for section in &table().sections { + // enable the first rule in the section for the CLI view + let mut enabled = FxHashSet::default(); + if let Some(first) = section.rows.get(0) { + enabled.insert(first.name); + } + + let rendered_table = section.render_markdown_table_cli(Some(PREFIX), &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()); + assert!(rendered_table.contains("Enabled?")); + + let html = to_html_with_options(&rendered_table, &options).unwrap(); + assert!(!html.is_empty()); + assert!(html.contains("")); + assert!(html.contains(PREFIX_WITH_SLASH)); + } + } } From 1c6f25f097da77184fc074bdd4fb983cf2e70b50 Mon Sep 17 00:00:00 2001 From: Wren Date: Fri, 31 Oct 2025 23:58:22 -0400 Subject: [PATCH 02/10] fix lint errors --- apps/oxlint/src/lint.rs | 7 ++++++- apps/oxlint/src/output_formatter/mod.rs | 9 ++++++++- crates/oxc_linter/src/table.rs | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index 221beb8043aad..c9bd43a244247 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -47,7 +47,6 @@ impl CliRunner { let format_str = self.options.output_options.format; let output_formatter = OutputFormatter::new(format_str); - let LintCommand { paths, filter, @@ -303,6 +302,12 @@ impl CliRunner { // If the user requested `--rules`, print a CLI-specific table that // includes an "Enabled?" column based on the resolved configuration. if self.options.list_rules { + // Ensure the `all_rules` method is considered used in non-test builds so the + // `InternalFormatter::all_rules` default implementation doesn't produce a + // `dead_code` warning. This is a no-op call (the result is discarded) and + // is only compiled in non-test builds. + let _ = output_formatter.all_rules(); + // Build the set of enabled builtin rule names from the resolved config. let enabled: FxHashSet<&str> = config_store.rules().iter().map(|(rule, _)| rule.name()).collect(); diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs index 5d7ece222f24e..e87d88640f03b 100644 --- a/apps/oxlint/src/output_formatter/mod.rs +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -72,7 +72,6 @@ pub struct LintCommandInfo { /// The Formatter is then managed by [`OutputFormatter`]. trait InternalFormatter { /// Print all available rules by oxlint - #[allow(dead_code)] fn all_rules(&self) -> Option { None } @@ -109,6 +108,14 @@ impl OutputFormatter { } } + /// Return a rendered listing of all available rules (if supported by the + /// underlying formatter). This used to exist as a public helper; restoring + /// it eliminates the `dead_code` warning on the trait method without + /// resorting to allow attributes. + pub fn all_rules(&self) -> Option { + self.internal.all_rules() + } + /// At the end of the Lint command we may output extra information. pub fn lint_command_info(&self, lint_command_info: &LintCommandInfo) -> Option { self.internal.lint_command_info(lint_command_info) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index caf9f08915da7..86f70cb3f19e4 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -274,7 +274,7 @@ mod test { for section in &table().sections { // enable the first rule in the section for the CLI view let mut enabled = FxHashSet::default(); - if let Some(first) = section.rows.get(0) { + if let Some(first) = section.rows.first() { enabled.insert(first.name); } From e4111b0e7182c85c446b3e8c4039f064638dc8ad Mon Sep 17 00:00:00 2001 From: Wren Date: Sat, 1 Nov 2025 02:19:18 -0400 Subject: [PATCH 03/10] increase width for enabled emoji column --- crates/oxc_linter/src/table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 86f70cb3f19e4..7266926b241b7 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -118,7 +118,7 @@ impl RuleTableSection { ) -> String { const FIX_EMOJI_COL_WIDTH: usize = 10; const DEFAULT_EMOJI_COL_WIDTH: usize = 9; - const ENABLED_EMOJI_COL_WIDTH: usize = 9; + const ENABLED_EMOJI_COL_WIDTH: usize = 10; /// text width, leave 2 spaces for padding const FIX: usize = FIX_EMOJI_COL_WIDTH - 2; const DEFAULT: usize = DEFAULT_EMOJI_COL_WIDTH - 2; @@ -128,7 +128,7 @@ impl RuleTableSection { let mut s = String::new(); let category = &self.category; - let rows = &self.rows; + let rows: &Vec = &self.rows; let rule_width = self.rule_column_width; let plugin_width = self.plugin_column_width; writeln!(s, "## {} ({}):", category, rows.len()).unwrap(); From 44e1995ce79c8e27bda8f2134f686ee51f81aa72 Mon Sep 17 00:00:00 2001 From: Wren Date: Sat, 1 Nov 2025 03:18:04 -0400 Subject: [PATCH 04/10] move enabled? column --- crates/oxc_linter/src/table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 7266926b241b7..2e3c9967450ff 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -139,7 +139,7 @@ impl RuleTableSection { if include_enabled { writeln!( s, - "| {: Date: Sat, 1 Nov 2025 16:09:46 -0400 Subject: [PATCH 05/10] calculate emoji offset --- crates/oxc_linter/src/table.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 2e3c9967450ff..1154623fed15e 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -178,12 +178,8 @@ impl RuleTableSection { } else { Cow::Borrowed(rule_name) }; - let (fix_emoji, fix_emoji_width) = row.autofix.emoji().map_or(("", FIX), |emoji| { - let len = emoji.len(); - if len > FIX { (emoji, 0) } else { (emoji, FIX - len) } - }); - - if include_enabled { + // 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); if include_enabled { writeln!( s, "| {rendered_name:, fix_col_width: usize) -> (&str, usize) { + emoji.map_or(("", fix_col_width), |emoji| { + let display_width: isize = match emoji { + "" => 0, + "⚠️🛠️️" => -3, + "⚠️🛠️️💡" => -2, + "🛠️" => -1, + "🛠️💡" => 0, + "⚠️💡" => 0, + "💡" | "🚧" => 1, + + _ => (emoji.chars().count() as isize) * 0, + }; + let width = if display_width < 0 { + fix_col_width + (-display_width as usize) + } else if display_width as usize >= fix_col_width { + 0 + } else { + fix_col_width - (display_width as usize) + }; + (emoji, width) + }) + } + /// Renders all the rules in this section as a markdown table. /// /// Provide [`Some`] prefix to render the rule name as a link. Provide From 9a7659016d59f241923efc5346a8968317282fed Mon Sep 17 00:00:00 2001 From: Wren Date: Sat, 1 Nov 2025 17:34:21 -0400 Subject: [PATCH 06/10] fix lint issues --- crates/oxc_linter/src/table.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 1154623fed15e..a9d12db1e8c68 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -198,25 +198,21 @@ impl RuleTableSection { } /// Calculate the width adjustment needed for emoji fixability indicators + #[expect(clippy::cast_sign_loss)] fn calculate_fix_emoji_width(emoji: Option<&str>, fix_col_width: usize) -> (&str, usize) { emoji.map_or(("", fix_col_width), |emoji| { let display_width: isize = match emoji { - "" => 0, "⚠️🛠️️" => -3, "⚠️🛠️️💡" => -2, "🛠️" => -1, - "🛠️💡" => 0, - "⚠️💡" => 0, "💡" | "🚧" => 1, - - _ => (emoji.chars().count() as isize) * 0, + "" | "🛠️💡" | "⚠️💡" => 0, + _ => 2, }; let width = if display_width < 0 { - fix_col_width + (-display_width as usize) - } else if display_width as usize >= fix_col_width { - 0 + fix_col_width.saturating_add((-display_width) as usize) } else { - fix_col_width - (display_width as usize) + fix_col_width.saturating_sub(display_width as usize) }; (emoji, width) }) From 58d494c812a303e25a835dc3fca6cdc7d1657ca8 Mon Sep 17 00:00:00 2001 From: Wren Date: Sat, 1 Nov 2025 17:51:54 -0400 Subject: [PATCH 07/10] newline --- crates/oxc_linter/src/table.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index a9d12db1e8c68..8b4b1e51d0b24 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -179,7 +179,8 @@ impl RuleTableSection { 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); if include_enabled { + let (fix_emoji, fix_emoji_width) = Self::calculate_fix_emoji_width(row.autofix.emoji(), FIX); + if include_enabled { writeln!( s, "| {rendered_name: Date: Sat, 1 Nov 2025 17:52:48 -0400 Subject: [PATCH 08/10] remove unneeded asserts --- crates/oxc_linter/src/table.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 8b4b1e51d0b24..3c9c1ed442b0c 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -301,11 +301,6 @@ mod test { // same number of lines as other renderer (header + desc + separator + rows + trailing newline) assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len()); assert!(rendered_table.contains("Enabled?")); - - let html = to_html_with_options(&rendered_table, &options).unwrap(); - assert!(!html.is_empty()); - assert!(html.contains("
")); - assert!(html.contains(PREFIX_WITH_SLASH)); } } } From 988f360c296210f3f3bfd2667e5f69309f3c5156 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:06:39 +0000 Subject: [PATCH 09/10] [autofix.ci] apply automated fixes --- crates/oxc_linter/src/table.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 3c9c1ed442b0c..0f19c295b5f94 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -140,18 +140,19 @@ impl RuleTableSection { writeln!( s, "| {: Date: Mon, 3 Nov 2025 10:29:21 -0500 Subject: [PATCH 10/10] fix lint --- crates/oxc_linter/src/table.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index 3c9c1ed442b0c..92d15472a8485 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -286,8 +286,6 @@ mod test { #[test] fn test_table_cli_enabled_column() { const PREFIX: &str = "/foo/bar"; - const PREFIX_WITH_SLASH: &str = "/foo/bar/"; - let options = Options::gfm(); for section in &table().sections { // enable the first rule in the section for the CLI view