diff --git a/.github/workflows/ty-ecosystem-analyzer.yaml b/.github/workflows/ty-ecosystem-analyzer.yaml index a1640e5974..a20018ea75 100644 --- a/.github/workflows/ty-ecosystem-analyzer.yaml +++ b/.github/workflows/ty-ecosystem-analyzer.yaml @@ -67,7 +67,7 @@ jobs: cd .. - uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@0aff03414da5d242e97a9f43fb502e085637a4a1" + uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e5c5f5b2d762af91b28490537fe0077334165693" ecosystem-analyzer \ --repository ruff \ diff --git a/.github/workflows/ty-ecosystem-report.yaml b/.github/workflows/ty-ecosystem-report.yaml index c9df4f1b28..904d19611d 100644 --- a/.github/workflows/ty-ecosystem-report.yaml +++ b/.github/workflows/ty-ecosystem-report.yaml @@ -52,7 +52,7 @@ jobs: cd .. - uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@0aff03414da5d242e97a9f43fb502e085637a4a1" + uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e5c5f5b2d762af91b28490537fe0077334165693" ecosystem-analyzer \ --verbose \ diff --git a/crates/ty/src/lib.rs b/crates/ty/src/lib.rs index 9c667b0c82..e93292936c 100644 --- a/crates/ty/src/lib.rs +++ b/crates/ty/src/lib.rs @@ -5,6 +5,7 @@ mod python_version; mod version; pub use args::Cli; +use ty_project::metadata::settings::TerminalSettings; use ty_static::EnvVars; use std::fmt::Write; @@ -21,7 +22,9 @@ use clap::{CommandFactory, Parser}; use colored::Colorize; use crossbeam::channel as crossbeam_channel; use rayon::ThreadPoolBuilder; -use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, DisplayDiagnostics, Severity}; +use ruff_db::diagnostic::{ + Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity, +}; use ruff_db::files::File; use ruff_db::max_parallelism; use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; @@ -193,6 +196,12 @@ pub enum ExitStatus { InternalError = 101, } +impl ExitStatus { + pub const fn is_internal_error(self) -> bool { + matches!(self, ExitStatus::InternalError) + } +} + impl Termination for ExitStatus { fn report(self) -> ExitCode { ExitCode::from(self as u8) @@ -334,11 +343,8 @@ impl MainLoop { let diagnostics_count = result.len(); let mut stdout = self.printer.stream_for_details().lock(); - let max_severity = result - .iter() - .map(Diagnostic::severity) - .max() - .unwrap_or(Severity::Info); + let exit_status = + exit_status_from_diagnostics(&result, terminal_settings); // Only render diagnostics if they're going to be displayed, since doing // so is expensive. @@ -359,25 +365,14 @@ impl MainLoop { )?; } - if max_severity.is_fatal() { + if exit_status.is_internal_error() { tracing::warn!( "A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details." ); } if self.watcher.is_none() { - return Ok(match max_severity { - Severity::Info => ExitStatus::Success, - Severity::Warning => { - if terminal_settings.error_on_warning { - ExitStatus::Failure - } else { - ExitStatus::Success - } - } - Severity::Error => ExitStatus::Failure, - Severity::Fatal => ExitStatus::InternalError, - }); + return Ok(exit_status); } } } else { @@ -410,6 +405,40 @@ impl MainLoop { } } +fn exit_status_from_diagnostics( + diagnostics: &[Diagnostic], + terminal_settings: &TerminalSettings, +) -> ExitStatus { + if diagnostics.is_empty() { + return ExitStatus::Success; + } + + let mut max_severity = Severity::Info; + let mut io_error = false; + + for diagnostic in diagnostics { + max_severity = max_severity.max(diagnostic.severity()); + io_error = io_error || matches!(diagnostic.id(), DiagnosticId::Io); + } + + if !max_severity.is_fatal() && io_error { + return ExitStatus::Error; + } + + match max_severity { + Severity::Info => ExitStatus::Success, + Severity::Warning => { + if terminal_settings.error_on_warning { + ExitStatus::Failure + } else { + ExitStatus::Success + } + } + Severity::Error => ExitStatus::Failure, + Severity::Fatal => ExitStatus::InternalError, + } +} + /// A progress reporter for `ty check`. struct IndicatifReporter { collector: CollectReporter, diff --git a/crates/ty/tests/cli/main.rs b/crates/ty/tests/cli/main.rs index 6640c4f598..a352ba355a 100644 --- a/crates/ty/tests/cli/main.rs +++ b/crates/ty/tests/cli/main.rs @@ -562,9 +562,9 @@ fn check_non_existing_path() -> anyhow::Result<()> { assert_cmd_snapshot!( case.command().arg("project/main.py").arg("project/tests"), - @r###" + @r" success: false - exit_code: 1 + exit_code: 2 ----- stdout ----- error[io]: `/project/main.py`: No such file or directory (os error 2) @@ -574,7 +574,7 @@ fn check_non_existing_path() -> anyhow::Result<()> { ----- stderr ----- WARN No python files found under the given path(s) - "### + " ); Ok(()) diff --git a/scripts/mypy_primer.sh b/scripts/mypy_primer.sh index c140721745..cdb08bfa69 100755 --- a/scripts/mypy_primer.sh +++ b/scripts/mypy_primer.sh @@ -20,7 +20,7 @@ cd .. echo "Project selector: ${PRIMER_SELECTOR}" # Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs uvx \ - --from="git+https://github.com/hauntsaninja/mypy_primer@ab5d30e2d4ecdaf7d6cc89395c7130143d6d3c82" \ + --from="git+https://github.com/hauntsaninja/mypy_primer@089ac1da83cf26aee9c98de412b7eb10e20b2212" \ mypy_primer \ --repo ruff \ --type-checker ty \