Repository: oxc-project/oxc (2026-04-11T11:57:01Z)

Issue:
linter: `--format=github` output should show number of files, rules and time taken
https://oxc.rs/docs/guide/usage/linter/output-formats.html#format-github

## Current Output

```txt
Found 0 warnings and 0 errors.
```

## Expected Output

```txt
Found 0 warnings and 0 errors.
Finished in 1.9s on 850 files with 312 rules using 28 threads.
```

(similarly as the `default` output)


Discussion:
(expand #20404 work)



Patch:
diff --git a/apps/oxlint/src/output_formatter/default.rs b/apps/oxlint/src/output_formatter/default.rs
index 35c78e7df3115..a56decfafa933 100644
--- a/apps/oxlint/src/output_formatter/default.rs
+++ b/apps/oxlint/src/output_formatter/default.rs
@@ -1,5 +1,3 @@
-use std::time::Duration;
-
 use crate::output_formatter::InternalFormatter;
 use oxc_diagnostics::{
     Error, GraphicalReportHandler,
@@ -25,20 +23,7 @@ impl InternalFormatter for DefaultOutputFormatter {
     }
 
     fn lint_command_info(&self, lint_command_info: &super::LintCommandInfo) -> Option<String> {
-        let time = Self::get_execution_time(&lint_command_info.start_time);
-        let s = if lint_command_info.number_of_files == 1 { "" } else { "s" };
-
-        if let Some(number_of_rules) = lint_command_info.number_of_rules {
-            Some(format!(
-                "Finished in {time} on {} file{s} with {} rules using {} threads.\n",
-                lint_command_info.number_of_files, number_of_rules, lint_command_info.threads_count
-            ))
-        } else {
-            Some(format!(
-                "Finished in {time} on {} file{s} using {} threads.\n",
-                lint_command_info.number_of_files, lint_command_info.threads_count
-            ))
-        }
+        Some(lint_command_info.format_execution_summary())
     }
 
     #[cfg(not(any(test, feature = "testing")))]
@@ -54,13 +39,6 @@ impl InternalFormatter for DefaultOutputFormatter {
     }
 }
 
-impl DefaultOutputFormatter {
-    fn get_execution_time(duration: &Duration) -> String {
-        let ms = duration.as_millis();
-        if ms < 1000 { format!("{ms}ms") } else { format!("{:.1}s", duration.as_secs_f64()) }
-    }
-}
-
 /// Pretty-prints diagnostics. Primarily meant for human-readable output in a terminal.
 ///
 /// See [`GraphicalReportHandler`] for how to configure colors, context lines, etc.
diff --git a/apps/oxlint/src/output_formatter/github.rs b/apps/oxlint/src/output_formatter/github.rs
index 16fcbcbc287a3..547c6cba41f3e 100644
--- a/apps/oxlint/src/output_formatter/github.rs
+++ b/apps/oxlint/src/output_formatter/github.rs
@@ -12,6 +12,10 @@ use crate::output_formatter::InternalFormatter;
 pub struct GithubOutputFormatter;
 
 impl InternalFormatter for GithubOutputFormatter {
+    fn lint_command_info(&self, lint_command_info: &super::LintCommandInfo) -> Option<String> {
+        Some(lint_command_info.format_execution_summary())
+    }
+
     fn get_diagnostic_reporter(&self) -> Box<dyn DiagnosticReporter> {
         Box::new(GithubReporter)
     }
@@ -86,13 +90,45 @@ fn escape_property(value: &str) -> String {
 
 #[cfg(test)]
 mod test {
+    use std::time::Duration;
+
     use oxc_diagnostics::{
         DiagnosticService, NamedSource, OxcDiagnostic,
         reporter::{DiagnosticReporter, DiagnosticResult},
     };
     use oxc_span::Span;
 
-    use super::GithubReporter;
+    use super::{GithubOutputFormatter, GithubReporter};
+    use crate::output_formatter::{InternalFormatter, LintCommandInfo};
+
+    #[test]
+    fn lint_command_info() {
+        let formatter = GithubOutputFormatter;
+        let result = formatter.lint_command_info(&LintCommandInfo {
+            number_of_files: 5,
+            number_of_rules: Some(10),
+            threads_count: 12,
+            start_time: Duration::new(1, 0),
+        });
+
+        assert_eq!(
+            result.unwrap(),
+            "Finished in 1.0s on 5 files with 10 rules using 12 threads.\n"
+        );
+    }
+
+    #[test]
+    fn lint_command_info_unknown_rules() {
+        let formatter = GithubOutputFormatter;
+        let result = formatter.lint_command_info(&LintCommandInfo {
+            number_of_files: 5,
+            number_of_rules: None,
+            threads_count: 12,
+            start_time: Duration::new(1, 0),
+        });
+
+        assert_eq!(result.unwrap(), "Finished in 1.0s on 5 files using 12 threads.\n");
+    }
 
     #[test]
     fn reporter_finish() {
diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs
index 627b7ba1d0ad9..12b8734913f97 100644
--- a/apps/oxlint/src/output_formatter/mod.rs
+++ b/apps/oxlint/src/output_formatter/mod.rs
@@ -69,6 +69,30 @@ pub struct LintCommandInfo {
     pub start_time: Duration,
 }
 
+impl LintCommandInfo {
+    pub(super) fn format_execution_summary(&self) -> String {
+        let ms = self.start_time.as_millis();
+        let time = if ms < 1000 {
+            format!("{ms}ms")
+        } else {
+            format!("{:.1}s", self.start_time.as_secs_f64())
+        };
+        let s = if self.number_of_files == 1 { "" } else { "s" };
+
+        if let Some(number_of_rules) = self.number_of_rules {
+            format!(
+                "Finished in {time} on {} file{s} with {number_of_rules} rules using {} threads.\n",
+                self.number_of_files, self.threads_count
+            )
+        } else {
+            format!(
+                "Finished in {time} on {} file{s} using {} threads.\n",
+                self.number_of_files, self.threads_count
+            )
+        }
+    }
+}
+
 /// An Interface for the different output formats.
 /// The Formatter is then managed by [`OutputFormatter`].
 trait InternalFormatter {
diff --git a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github --report-unused-disable-directives disable-directive.js@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github --report-unused-disable-directives disable-directive.js@oxlint.snap
index 2731ee39cb629..43c22eabb5369 100644
--- a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github --report-unused-disable-directives disable-directive.js@oxlint.snap
+++ b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github --report-unused-disable-directives disable-directive.js@oxlint.snap
@@ -10,6 +10,7 @@ working directory: fixtures/cli/output_formatter_diagnostic
 ::warning file=disable-directive.js,line=15,endLine=15,col=1,endColumn=47,title=oxlint::Unused eslint-disable directive (no problems were reported).
 
 Found 3 warnings and 0 errors.
+Finished in <variable>ms on 1 file with 2 rules using 1 threads.
 ----------
 CLI result: LintSucceeded
 ----------
diff --git a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github ok.js@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github ok.js@oxlint.snap
index db4d86c123ef5..f6ec1e85c19b4 100644
--- a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github ok.js@oxlint.snap
+++ b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github ok.js@oxlint.snap
@@ -6,6 +6,7 @@ arguments: --format=github ok.js
 working directory: fixtures/cli/output_formatter_diagnostic
 ----------
 Found 0 warnings and 0 errors.
+Finished in <variable>ms on 1 file with 2 rules using 1 threads.
 ----------
 CLI result: LintSucceeded
 ----------
diff --git a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github parser-error.js@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github parser-error.js@oxlint.snap
index 65f7d24e64740..b3526e636f031 100644
--- a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github parser-error.js@oxlint.snap
+++ b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github parser-error.js@oxlint.snap
@@ -8,6 +8,7 @@ working directory: fixtures/cli/output_formatter_diagnostic
 ::error file=parser-error.js,line=3,endLine=3,col=9,endColumn=10,title=oxlint::Expected `;` but found `:`
 
 Found 0 warnings and 1 error.
+Finished in <variable>ms on 1 file with 2 rules using 1 threads.
 ----------
 CLI result: LintFoundErrors
 ----------


Test patch:
diff --git a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github test.js@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github test.js@oxlint.snap
index 4236ea56e8676..9ae382613fadc 100644
--- a/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github test.js@oxlint.snap
+++ b/apps/oxlint/src/snapshots/fixtures__cli__output_formatter_diagnostic_--format=github test.js@oxlint.snap
@@ -10,6 +10,7 @@ working directory: fixtures/cli/output_formatter_diagnostic
 ::warning file=test.js,line=1,endLine=1,col=17,endColumn=18,title=eslint(no-unused-vars)::Parameter 'b' is declared but never used. Unused parameters should start with a '_'.
 
 Found 2 warnings and 1 error.
+Finished in <variable>ms on 1 file with 2 rules using 1 threads.
 ----------
 CLI result: LintFoundErrors
 ----------
diff --git a/apps/oxlint/test/fixtures/github_report_long_message/output.snap.md b/apps/oxlint/test/fixtures/github_report_long_message/output.snap.md
index 9840822e4a409..42c6cabee4081 100644
--- a/apps/oxlint/test/fixtures/github_report_long_message/output.snap.md
+++ b/apps/oxlint/test/fixtures/github_report_long_message/output.snap.md
@@ -6,6 +6,7 @@
 ::error file=files/index.ts,line=1,endLine=2,col=1,endColumn=1,title=plugin(long-message)::This is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long message
 
 Found 0 warnings and 1 error.
+Finished in Xms on 1 file with 1 rules using X threads.
 ```
 
 # stderr