diff --git a/Cargo.lock b/Cargo.lock index 7f936f0d4c668..3e5b7ae034a71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2217,6 +2217,8 @@ dependencies = [ "oxc_linter", "oxc_span", "rayon", + "serde", + "serde_json", "tempfile", "tracing-subscriber", ] diff --git a/apps/oxlint/Cargo.toml b/apps/oxlint/Cargo.toml index e49afd6666293..af967c99eb5d3 100644 --- a/apps/oxlint/Cargo.toml +++ b/apps/oxlint/Cargo.toml @@ -39,6 +39,8 @@ bpaf = { workspace = true, features = ["autocomplete", "bright-color", "derive"] ignore = { workspace = true, features = ["simd-accel"] } miette = { workspace = true } rayon = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } tempfile = { workspace = true } tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature diff --git a/apps/oxlint/src/command/lint.rs b/apps/oxlint/src/command/lint.rs index a02ca8f609db0..c2435c1a1fc57 100644 --- a/apps/oxlint/src/command/lint.rs +++ b/apps/oxlint/src/command/lint.rs @@ -1,8 +1,10 @@ -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; use bpaf::Bpaf; use oxc_linter::{AllowWarnDeny, FixKind, LintPlugins}; +use crate::output_formatter::OutputFormat; + use super::{ ignore::{ignore_options, IgnoreOptions}, misc_options, validate_paths, MiscOptions, PATHS_ERROR_MESSAGE, VERSION, @@ -184,32 +186,6 @@ pub struct OutputOptions { pub format: OutputFormat, } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum OutputFormat { - Default, - /// GitHub Check Annotation - /// - Github, - Json, - Unix, - Checkstyle, -} - -impl FromStr for OutputFormat { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "json" => Ok(Self::Json), - "default" => Ok(Self::Default), - "unix" => Ok(Self::Unix), - "checkstyle" => Ok(Self::Checkstyle), - "github" => Ok(Self::Github), - _ => Err(format!("'{s}' is not a known format")), - } - } -} - /// Enable Plugins #[allow(clippy::struct_field_names)] #[derive(Debug, Default, Clone, Bpaf)] diff --git a/apps/oxlint/src/command/mod.rs b/apps/oxlint/src/command/mod.rs index a2e1733f8211f..2ffe29d79e747 100644 --- a/apps/oxlint/src/command/mod.rs +++ b/apps/oxlint/src/command/mod.rs @@ -7,7 +7,7 @@ use bpaf::Bpaf; pub use self::{ ignore::IgnoreOptions, - lint::{lint_command, LintCommand, OutputFormat, OutputOptions, WarningOptions}, + lint::{lint_command, LintCommand, OutputOptions, WarningOptions}, }; const VERSION: &str = match option_env!("OXC_VERSION") { diff --git a/apps/oxlint/src/lib.rs b/apps/oxlint/src/lib.rs index 75a5315df3067..d8d5293f2060f 100644 --- a/apps/oxlint/src/lib.rs +++ b/apps/oxlint/src/lib.rs @@ -1,5 +1,6 @@ mod command; mod lint; +mod output_formatter; mod result; mod runner; mod walk; diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index d2b03b79d2494..5ca498dfcc0d6 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -16,9 +16,9 @@ use oxc_span::VALID_EXTENSIONS; use crate::{ cli::{ - CliRunResult, LintCommand, LintResult, MiscOptions, OutputFormat, OutputOptions, Runner, - WarningOptions, + CliRunResult, LintCommand, LintResult, MiscOptions, OutputOptions, Runner, WarningOptions, }, + output_formatter::{OutputFormat, OutputFormatter}, walk::{Extensions, Walk}, }; @@ -36,13 +36,12 @@ impl Runner for LintRunner { } fn run(self) -> CliRunResult { + let format_str = self.options.output_options.format; + let output_formatter = OutputFormatter::new(format_str); + if self.options.list_rules { let mut stdout = BufWriter::new(std::io::stdout()); - if self.options.output_options.format == OutputFormat::Json { - Linter::print_rules_json(&mut stdout); - } else { - Linter::print_rules(&mut stdout); - } + output_formatter.all_rules(&mut stdout); return CliRunResult::None; } diff --git a/apps/oxlint/src/output_formatter/default.rs b/apps/oxlint/src/output_formatter/default.rs new file mode 100644 index 0000000000000..37598e40cf1d3 --- /dev/null +++ b/apps/oxlint/src/output_formatter/default.rs @@ -0,0 +1,29 @@ +use std::io::Write; + +use oxc_linter::table::RuleTable; + +pub struct DefaultOutputFormatter; + +impl DefaultOutputFormatter { + pub fn all_rules(writer: &mut T) { + let table = RuleTable::new(); + for section in table.sections { + writeln!(writer, "{}", section.render_markdown_table(None)).unwrap(); + } + writeln!(writer, "Default: {}", table.turned_on_by_default_count).unwrap(); + writeln!(writer, "Total: {}", table.total).unwrap(); + } +} + +#[cfg(test)] +mod test { + use crate::output_formatter::default::DefaultOutputFormatter; + + #[test] + fn all_rules() { + let mut writer = Vec::new(); + + DefaultOutputFormatter::all_rules(&mut writer); + assert!(!writer.is_empty()); + } +} diff --git a/apps/oxlint/src/output_formatter/json.rs b/apps/oxlint/src/output_formatter/json.rs new file mode 100644 index 0000000000000..1b9b1d50828a8 --- /dev/null +++ b/apps/oxlint/src/output_formatter/json.rs @@ -0,0 +1,31 @@ +use oxc_linter::rules::RULES; +use oxc_linter::RuleCategory; +use std::io::Write; + +#[derive(Debug)] +pub struct JsonOutputFormatter; + +impl JsonOutputFormatter { + pub fn all_rules(writer: &mut T) { + #[derive(Debug, serde::Serialize)] + struct RuleInfoJson<'a> { + scope: &'a str, + value: &'a str, + category: RuleCategory, + } + + let rules_info = RULES.iter().map(|rule| RuleInfoJson { + scope: rule.plugin_name(), + value: rule.name(), + category: rule.category(), + }); + + writer + .write_all( + serde_json::to_string_pretty(&rules_info.collect::>()) + .expect("Failed to serialize") + .as_bytes(), + ) + .unwrap(); + } +} diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs new file mode 100644 index 0000000000000..adb958a4d1bbe --- /dev/null +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -0,0 +1,50 @@ +mod default; +mod json; + +use std::io::Write; +use std::str::FromStr; + +use crate::output_formatter::{default::DefaultOutputFormatter, json::JsonOutputFormatter}; + +pub struct OutputFormatter { + format: OutputFormat, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum OutputFormat { + Default, + /// GitHub Check Annotation + /// + Github, + Json, + Unix, + Checkstyle, +} + +impl FromStr for OutputFormat { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "json" => Ok(Self::Json), + "default" => Ok(Self::Default), + "unix" => Ok(Self::Unix), + "checkstyle" => Ok(Self::Checkstyle), + "github" => Ok(Self::Github), + _ => Err(format!("'{s}' is not a known format")), + } + } +} + +impl OutputFormatter { + pub fn new(format: OutputFormat) -> Self { + Self { format } + } + // print all rules which are currently supported by oxlint + pub fn all_rules(&self, writer: &mut T) { + match self.format { + OutputFormat::Json => JsonOutputFormatter::all_rules(writer), + _ => DefaultOutputFormatter::all_rules(writer), + } + } +} diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 27ef351b768e5..3e1e2d8144315 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -15,17 +15,16 @@ mod module_graph_visitor; mod module_record; mod options; mod rule; -mod rules; mod service; mod utils; pub mod loader; +pub mod rules; pub mod table; -use std::{io::Write, path::Path, rc::Rc, sync::Arc}; +use std::{path::Path, rc::Rc, sync::Arc}; use oxc_semantic::{AstNode, Semantic}; -use rules::RULES; pub use crate::{ config::{ @@ -45,7 +44,6 @@ use crate::{ context::ContextHost, fixer::{Fixer, Message}, rules::RuleEnum, - table::RuleTable, utils::iter_possible_jest_call_node, }; @@ -183,52 +181,11 @@ impl Linter { ctx_host.take_diagnostics() } - - /// # Panics - pub fn print_rules(writer: &mut W) { - let table = RuleTable::new(); - for section in table.sections { - writeln!(writer, "{}", section.render_markdown_table(None)).unwrap(); - } - writeln!(writer, "Default: {}", table.turned_on_by_default_count).unwrap(); - writeln!(writer, "Total: {}", table.total).unwrap(); - } - - /// # Panics - pub fn print_rules_json(writer: &mut W) { - #[derive(Debug, serde::Serialize)] - struct RuleInfoJson<'a> { - scope: &'a str, - value: &'a str, - category: RuleCategory, - } - - let rules_info = RULES.iter().map(|rule| RuleInfoJson { - scope: rule.plugin_name(), - value: rule.name(), - category: rule.category(), - }); - - writer - .write_all( - serde_json::to_string_pretty(&rules_info.collect::>()) - .expect("Failed to serialize") - .as_bytes(), - ) - .unwrap(); - } } #[cfg(test)] mod test { - use super::{Linter, Oxlintrc}; - - #[test] - fn print_rules() { - let mut writer = Vec::new(); - Linter::print_rules(&mut writer); - assert!(!writer.is_empty()); - } + use super::Oxlintrc; #[test] fn test_schema_json() {