mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:41:23 +00:00
[ty] Add GitLab output format (#20155)
## Summary This wires up the GitLab output format moved into `ruff_db` in https://github.com/astral-sh/ruff/pull/20117 to the ty CLI. While I was here, I made one unrelated change to the CLI docs. Clap was rendering the escapes around the `\[default\]` brackets for the `full` output, so I just switched those to parentheses: ``` --output-format <OUTPUT_FORMAT> The format to use for printing diagnostic messages Possible values: - full: Print diagnostics verbosely, with context and helpful hints \[default\] - concise: Print diagnostics concisely, one per line - gitlab: Print diagnostics in the JSON format expected by GitLab Code Quality reports ``` ## Test Plan New CLI test, and a manual test with `--config 'terminal.output-format = "gitlab"'` to make sure this works as a configuration option too. I also tried piping the output through jq to make sure it's at least valid JSON
This commit is contained in:
parent
4e97b97a76
commit
aee9350df1
7 changed files with 127 additions and 23 deletions
|
@ -1444,7 +1444,7 @@ pub enum DiagnosticFormat {
|
||||||
Junit,
|
Junit,
|
||||||
/// Print diagnostics in the JSON format used by GitLab [Code Quality] reports.
|
/// Print diagnostics in the JSON format used by GitLab [Code Quality] reports.
|
||||||
///
|
///
|
||||||
/// [Code Quality]: https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
|
/// [Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
Gitlab,
|
Gitlab,
|
||||||
}
|
}
|
||||||
|
|
3
crates/ty/docs/cli.md
generated
3
crates/ty/docs/cli.md
generated
|
@ -60,8 +60,9 @@ over all configuration files.</p>
|
||||||
</dd><dt id="ty-check--output-format"><a href="#ty-check--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>The format to use for printing diagnostic messages</p>
|
</dd><dt id="ty-check--output-format"><a href="#ty-check--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>The format to use for printing diagnostic messages</p>
|
||||||
<p>Possible values:</p>
|
<p>Possible values:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>full</code>: Print diagnostics verbosely, with context and helpful hints [default]</li>
|
<li><code>full</code>: Print diagnostics verbosely, with context and helpful hints (default)</li>
|
||||||
<li><code>concise</code>: Print diagnostics concisely, one per line</li>
|
<li><code>concise</code>: Print diagnostics concisely, one per line</li>
|
||||||
|
<li><code>gitlab</code>: Print diagnostics in the JSON format expected by GitLab Code Quality reports</li>
|
||||||
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
|
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
|
||||||
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
|
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
|
||||||
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
|
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
|
||||||
|
|
|
@ -306,7 +306,7 @@ impl clap::Args for RulesArg {
|
||||||
/// The diagnostic output format.
|
/// The diagnostic output format.
|
||||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
|
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
|
||||||
pub enum OutputFormat {
|
pub enum OutputFormat {
|
||||||
/// Print diagnostics verbosely, with context and helpful hints \[default\].
|
/// Print diagnostics verbosely, with context and helpful hints (default).
|
||||||
///
|
///
|
||||||
/// Diagnostic messages may include additional context and
|
/// Diagnostic messages may include additional context and
|
||||||
/// annotations on the input to help understand the message.
|
/// annotations on the input to help understand the message.
|
||||||
|
@ -321,6 +321,9 @@ pub enum OutputFormat {
|
||||||
/// dropped.
|
/// dropped.
|
||||||
#[value(name = "concise")]
|
#[value(name = "concise")]
|
||||||
Concise,
|
Concise,
|
||||||
|
/// Print diagnostics in the JSON format expected by GitLab Code Quality reports.
|
||||||
|
#[value(name = "gitlab")]
|
||||||
|
Gitlab,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<OutputFormat> for ty_project::metadata::options::OutputFormat {
|
impl From<OutputFormat> for ty_project::metadata::options::OutputFormat {
|
||||||
|
@ -328,6 +331,7 @@ impl From<OutputFormat> for ty_project::metadata::options::OutputFormat {
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::Full => Self::Full,
|
OutputFormat::Full => Self::Full,
|
||||||
OutputFormat::Concise => Self::Concise,
|
OutputFormat::Concise => Self::Concise,
|
||||||
|
OutputFormat::Gitlab => Self::Gitlab,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use clap::{CommandFactory, Parser};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use crossbeam::channel as crossbeam_channel;
|
use crossbeam::channel as crossbeam_channel;
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
|
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, DisplayDiagnostics, Severity};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::max_parallelism;
|
use ruff_db::max_parallelism;
|
||||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||||
|
@ -319,37 +319,48 @@ impl MainLoop {
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_human_readable = terminal_settings.output_format.is_human_readable();
|
||||||
|
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
|
if is_human_readable {
|
||||||
writeln!(
|
writeln!(
|
||||||
self.printer.stream_for_success_summary(),
|
self.printer.stream_for_success_summary(),
|
||||||
"{}",
|
"{}",
|
||||||
"All checks passed!".green().bold()
|
"All checks passed!".green().bold()
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
if self.watcher.is_none() {
|
if self.watcher.is_none() {
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut max_severity = Severity::Info;
|
|
||||||
let diagnostics_count = result.len();
|
let diagnostics_count = result.len();
|
||||||
|
|
||||||
let mut stdout = self.printer.stream_for_details().lock();
|
let mut stdout = self.printer.stream_for_details().lock();
|
||||||
for diagnostic in result {
|
let max_severity = result
|
||||||
|
.iter()
|
||||||
|
.map(Diagnostic::severity)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(Severity::Info);
|
||||||
|
|
||||||
// Only render diagnostics if they're going to be displayed, since doing
|
// Only render diagnostics if they're going to be displayed, since doing
|
||||||
// so is expensive.
|
// so is expensive.
|
||||||
if stdout.is_enabled() {
|
if stdout.is_enabled() {
|
||||||
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
|
write!(
|
||||||
}
|
stdout,
|
||||||
|
"{}",
|
||||||
max_severity = max_severity.max(diagnostic.severity());
|
DisplayDiagnostics::new(db, &display_config, &result)
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_human_readable {
|
||||||
writeln!(
|
writeln!(
|
||||||
self.printer.stream_for_failure_summary(),
|
self.printer.stream_for_failure_summary(),
|
||||||
"Found {} diagnostic{}",
|
"Found {} diagnostic{}",
|
||||||
diagnostics_count,
|
diagnostics_count,
|
||||||
if diagnostics_count > 1 { "s" } else { "" }
|
if diagnostics_count > 1 { "s" } else { "" }
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
if max_severity.is_fatal() {
|
if max_severity.is_fatal() {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
|
|
|
@ -618,6 +618,71 @@ fn concise_diagnostics() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gitlab_diagnostics() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_file(
|
||||||
|
"test.py",
|
||||||
|
r#"
|
||||||
|
print(x) # [unresolved-reference]
|
||||||
|
print(4[1]) # [non-subscriptable]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut settings = insta::Settings::clone_current();
|
||||||
|
settings.add_filter(r#"("fingerprint": ")[a-z0-9]+(",)"#, "$1[FINGERPRINT]$2");
|
||||||
|
let _s = settings.bind_to_scope();
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command().arg("--output-format=gitlab").arg("--warn").arg("unresolved-reference"), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"check_name": "unresolved-reference",
|
||||||
|
"description": "unresolved-reference: Name `x` used when not defined",
|
||||||
|
"severity": "minor",
|
||||||
|
"fingerprint": "[FINGERPRINT]",
|
||||||
|
"location": {
|
||||||
|
"path": "test.py",
|
||||||
|
"positions": {
|
||||||
|
"begin": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 7
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 2,
|
||||||
|
"column": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check_name": "non-subscriptable",
|
||||||
|
"description": "non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method",
|
||||||
|
"severity": "major",
|
||||||
|
"fingerprint": "[FINGERPRINT]",
|
||||||
|
"location": {
|
||||||
|
"path": "test.py",
|
||||||
|
"positions": {
|
||||||
|
"begin": {
|
||||||
|
"line": 3,
|
||||||
|
"column": 7
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 3,
|
||||||
|
"column": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
----- stderr -----
|
||||||
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// This tests the diagnostic format for revealed type.
|
/// This tests the diagnostic format for revealed type.
|
||||||
///
|
///
|
||||||
/// This test was introduced because changes were made to
|
/// This test was introduced because changes were made to
|
||||||
|
|
|
@ -1055,6 +1055,21 @@ pub enum OutputFormat {
|
||||||
///
|
///
|
||||||
/// This may use color when printing to a `tty`.
|
/// This may use color when printing to a `tty`.
|
||||||
Concise,
|
Concise,
|
||||||
|
/// Print diagnostics in the JSON format expected by GitLab [Code Quality] reports.
|
||||||
|
///
|
||||||
|
/// [Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
|
||||||
|
Gitlab,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputFormat {
|
||||||
|
/// Returns `true` if this format is intended for users to read directly, in contrast to
|
||||||
|
/// machine-readable or structured formats.
|
||||||
|
///
|
||||||
|
/// This can be used to check whether information beyond the diagnostics, such as a header or
|
||||||
|
/// `Found N diagnostics` footer, should be included.
|
||||||
|
pub const fn is_human_readable(&self) -> bool {
|
||||||
|
matches!(self, OutputFormat::Full | OutputFormat::Concise)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<OutputFormat> for DiagnosticFormat {
|
impl From<OutputFormat> for DiagnosticFormat {
|
||||||
|
@ -1062,6 +1077,7 @@ impl From<OutputFormat> for DiagnosticFormat {
|
||||||
match value {
|
match value {
|
||||||
OutputFormat::Full => Self::Full,
|
OutputFormat::Full => Self::Full,
|
||||||
OutputFormat::Concise => Self::Concise,
|
OutputFormat::Concise => Self::Concise,
|
||||||
|
OutputFormat::Gitlab => Self::Gitlab,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
ty.schema.json
generated
7
ty.schema.json
generated
|
@ -164,6 +164,13 @@
|
||||||
"enum": [
|
"enum": [
|
||||||
"concise"
|
"concise"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Print diagnostics in the JSON format expected by GitLab [Code Quality] reports.\n\n[Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"gitlab"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue