Implement a --show-source setting (#698)

This commit is contained in:
Harutaka Kawamura 2022-11-19 04:02:29 +09:00 committed by GitHub
parent 49559da54e
commit e81efa5a3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 166 additions and 16 deletions

22
Cargo.lock generated
View file

@ -49,6 +49,16 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7" checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
[[package]]
name = "annotate-snippets"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36"
dependencies = [
"unicode-width",
"yansi-term",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.66" version = "1.0.66"
@ -417,7 +427,7 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c" checksum = "a5b5db619f3556839cb2223ae86ff3f9a09da2c5013be42bc9af08c9589bf70c"
dependencies = [ dependencies = [
"annotate-snippets", "annotate-snippets 0.6.1",
] ]
[[package]] [[package]]
@ -2240,6 +2250,7 @@ dependencies = [
name = "ruff" name = "ruff"
version = "0.0.127" version = "0.0.127"
dependencies = [ dependencies = [
"annotate-snippets 0.9.1",
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
"atty", "atty",
@ -3312,3 +3323,12 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "yansi-term"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
dependencies = [
"winapi 0.3.9",
]

View file

@ -13,6 +13,7 @@ edition = "2021"
name = "ruff" name = "ruff"
[dependencies] [dependencies]
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { version = "1.0.66" } anyhow = { version = "1.0.66" }
atty = { version = "0.2.14" } atty = { version = "0.2.14" }
bincode = { version = "1.3.3" } bincode = { version = "1.3.3" }

View file

@ -191,7 +191,7 @@ ruff path/to/code/ --select F401 --select F403
See `ruff --help` for more: See `ruff --help` for more:
```shell ```shell
ruff: An extremely fast Python linter. Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] <FILES>... Usage: ruff [OPTIONS] <FILES>...
@ -231,10 +231,12 @@ Options:
List of mappings from file pattern to code to exclude List of mappings from file pattern to code to exclude
--format <FORMAT> --format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text, json] Output serialization format for error messages [default: text] [possible values: text, json]
--show-source
Show violations with source code
--show-files --show-files
See the files ruff will be run against with the current settings See the files Ruff will be run against with the current settings
--show-settings --show-settings
See ruff's settings See Ruff's settings
--add-noqa --add-noqa
Enable automatic additions of noqa directives to failing lines Enable automatic additions of noqa directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX> --dummy-variable-rgx <DUMMY_VARIABLE_RGX>
@ -244,7 +246,7 @@ Options:
--line-length <LINE_LENGTH> --line-length <LINE_LENGTH>
Set the line-length for length-associated checks and automatic formatting Set the line-length for length-associated checks and automatic formatting
--max-complexity <MAX_COMPLEXITY> --max-complexity <MAX_COMPLEXITY>
Set the maximum cyclomatic complexity for complexity-associated checks Max McCabe complexity allowed for a function
--stdin-filename <STDIN_FILENAME> --stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin The name of the file when passing it through stdin
-h, --help -h, --help

View file

@ -259,6 +259,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -295,6 +296,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -331,6 +333,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -367,6 +370,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -403,6 +407,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: Some(flake8_quotes::settings::Options { flake8_quotes: Some(flake8_quotes::settings::Options {
@ -482,6 +487,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -519,6 +525,7 @@ mod tests {
per_file_ignores: None, per_file_ignores: None,
dummy_variable_rgx: None, dummy_variable_rgx: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: Some(flake8_quotes::settings::Options { flake8_quotes: Some(flake8_quotes::settings::Options {

View file

@ -13,7 +13,7 @@ use crate::settings::configuration::Configuration;
use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion}; use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")] #[command(author, about = "Ruff: An extremely fast Python linter.")]
#[command(version)] #[command(version)]
pub struct Cli { pub struct Cli {
#[arg(required = true)] #[arg(required = true)]
@ -72,10 +72,13 @@ pub struct Cli {
/// Output serialization format for error messages. /// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)] #[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat, pub format: SerializationFormat,
/// See the files ruff will be run against with the current settings. /// Show violations with source code.
#[arg(long)]
pub show_source: bool,
/// See the files Ruff will be run against with the current settings.
#[arg(long)] #[arg(long)]
pub show_files: bool, pub show_files: bool,
/// See ruff's settings. /// See Ruff's settings.
#[arg(long)] #[arg(long)]
pub show_settings: bool, pub show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines. /// Enable automatic additions of noqa directives to failing lines.

View file

@ -22,7 +22,7 @@ use crate::check_tokens::check_tokens;
use crate::checks::{Check, CheckCode, CheckKind, LintSource}; use crate::checks::{Check, CheckCode, CheckKind, LintSource};
use crate::code_gen::SourceGenerator; use crate::code_gen::SourceGenerator;
use crate::directives::Directives; use crate::directives::Directives;
use crate::message::Message; use crate::message::{Message, Source};
use crate::noqa::add_noqa; use crate::noqa::add_noqa;
use crate::settings::Settings; use crate::settings::Settings;
use crate::source_code_locator::SourceCodeLocator; use crate::source_code_locator::SourceCodeLocator;
@ -176,7 +176,15 @@ pub fn lint_stdin(
// Convert to messages. // Convert to messages.
Ok(checks Ok(checks
.into_iter() .into_iter()
.map(|check| Message::from_check(path.to_string_lossy().to_string(), check)) .map(|check| {
let filename = path.to_string_lossy().to_string();
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, filename, source)
})
.collect()) .collect())
} }
@ -233,7 +241,15 @@ pub fn lint_path(
// Convert to messages. // Convert to messages.
let messages: Vec<Message> = checks let messages: Vec<Message> = checks
.into_iter() .into_iter()
.map(|check| Message::from_check(path.to_string_lossy().to_string(), check)) .map(|check| {
let filename = path.to_string_lossy().to_string();
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, filename, source)
})
.collect(); .collect();
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
cache::set(path, &metadata, settings, autofix, &messages, mode); cache::set(path, &metadata, settings, autofix, &messages, mode);

View file

@ -117,6 +117,7 @@ fn run_once(
location: Default::default(), location: Default::default(),
end_location: Default::default(), end_location: Default::default(),
filename: path.to_string_lossy().to_string(), filename: path.to_string_lossy().to_string(),
source: None,
}] }]
} else { } else {
error!("Failed to check {}: {message}", path.to_string_lossy()); error!("Failed to check {}: {message}", path.to_string_lossy());
@ -281,6 +282,9 @@ fn inner_main() -> Result<ExitCode> {
if let Some(fix) = fix { if let Some(fix) = fix {
configuration.fix = fix; configuration.fix = fix;
} }
if cli.show_source {
configuration.show_source = true;
}
if cli.show_settings && cli.show_files { if cli.show_settings && cli.show_files {
eprintln!("Error: specify --show-settings or show-files (not both)."); eprintln!("Error: specify --show-settings or show-files (not both).");

View file

@ -2,12 +2,16 @@ use std::cmp::Ordering;
use std::fmt; use std::fmt;
use std::path::Path; use std::path::Path;
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use colored::Colorize; use colored::Colorize;
use rustpython_parser::ast::Location; use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind}; use crate::checks::{Check, CheckKind};
use crate::fs::relativize_path; use crate::fs::relativize_path;
use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Message { pub struct Message {
@ -16,16 +20,18 @@ pub struct Message {
pub location: Location, pub location: Location,
pub end_location: Location, pub end_location: Location,
pub filename: String, pub filename: String,
pub source: Option<Source>,
} }
impl Message { impl Message {
pub fn from_check(filename: String, check: Check) -> Self { pub fn from_check(check: Check, filename: String, source: Option<Source>) -> Self {
Self { Self {
kind: check.kind, kind: check.kind,
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(), fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
location: Location::new(check.location.row(), check.location.column() + 1), location: Location::new(check.location.row(), check.location.column() + 1),
end_location: Location::new(check.end_location.row(), check.end_location.column() + 1), end_location: Location::new(check.end_location.row(), check.end_location.column() + 1),
filename, filename,
source,
} }
} }
} }
@ -48,8 +54,7 @@ impl PartialOrd for Message {
impl fmt::Display for Message { impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!( let label = format!(
f,
"{}{}{}{}{}{} {} {}", "{}{}{}{}{}{} {} {}",
relativize_path(Path::new(&self.filename)).white().bold(), relativize_path(Path::new(&self.filename)).white().bold(),
":".cyan(), ":".cyan(),
@ -58,7 +63,71 @@ impl fmt::Display for Message {
self.location.column(), self.location.column(),
":".cyan(), ":".cyan(),
self.kind.code().as_ref().red().bold(), self.kind.code().as_ref().red().bold(),
self.kind.body() self.kind.body(),
) );
match &self.source {
None => write!(f, "{}", label),
Some(source) => {
let snippet = Snippet {
title: Some(Annotation {
label: Some(&label),
annotation_type: AnnotationType::Error,
// The ID (error number) is already encoded in the `label`.
id: None,
}),
footer: vec![],
slices: vec![Slice {
source: &source.contents,
line_start: self.location.row(),
annotations: vec![SourceAnnotation {
label: self.kind.code().as_ref(),
annotation_type: AnnotationType::Error,
range: source.range,
}],
// The origin (file name, line number, and column number) is already encoded
// in the `label`.
origin: None,
fold: false,
}],
opt: FormatOptions {
color: true,
..Default::default()
},
};
// `split_once(' ')` strips "error: " from `message`.
let message = DisplayList::from(snippet).to_string();
let (_, message) = message.split_once(' ').unwrap();
write!(f, "{}", message)
}
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Source {
pub contents: String,
pub range: (usize, usize),
}
impl Source {
pub fn from_check(check: &Check, locator: &SourceCodeLocator) -> Self {
let source = locator.slice_source_code_range(&Range {
location: Location::new(check.location.row(), 0),
end_location: Location::new(check.end_location.row() + 1, 0),
});
let num_chars_in_range = locator
.slice_source_code_range(&Range {
location: check.location,
end_location: check.end_location,
})
.chars()
.count();
Source {
contents: source.to_string(),
range: (
check.location.column(),
check.location.column() + num_chars_in_range,
),
}
} }
} }

View file

@ -31,6 +31,7 @@ pub struct Configuration {
pub select: Vec<CheckCodePrefix>, pub select: Vec<CheckCodePrefix>,
pub src: Vec<PathBuf>, pub src: Vec<PathBuf>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
pub show_source: bool,
// Plugins // Plugins
pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings,
@ -134,6 +135,7 @@ impl Configuration {
.collect() .collect()
}) })
.unwrap_or_default(), .unwrap_or_default(),
show_source: options.show_source.unwrap_or_default(),
// Plugins // Plugins
flake8_annotations: options flake8_annotations: options
.flake8_annotations .flake8_annotations

View file

@ -34,6 +34,7 @@ pub struct Settings {
pub per_file_ignores: Vec<PerFileIgnore>, pub per_file_ignores: Vec<PerFileIgnore>,
pub src: Vec<PathBuf>, pub src: Vec<PathBuf>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
pub show_source: bool,
// Plugins // Plugins
pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings,
@ -67,6 +68,7 @@ impl Settings {
per_file_ignores: config.per_file_ignores, per_file_ignores: config.per_file_ignores,
src: config.src, src: config.src,
target_version: config.target_version, target_version: config.target_version,
show_source: config.show_source,
} }
} }
@ -87,6 +89,7 @@ impl Settings {
isort: Default::default(), isort: Default::default(),
mccabe: Default::default(), mccabe: Default::default(),
pep8_naming: Default::default(), pep8_naming: Default::default(),
show_source: Default::default(),
} }
} }
@ -107,6 +110,7 @@ impl Settings {
isort: Default::default(), isort: Default::default(),
mccabe: Default::default(), mccabe: Default::default(),
pep8_naming: Default::default(), pep8_naming: Default::default(),
show_source: Default::default(),
} }
} }
} }
@ -122,6 +126,7 @@ impl Hash for Settings {
for value in self.per_file_ignores.iter() { for value in self.per_file_ignores.iter() {
value.hash(state); value.hash(state);
} }
self.show_source.hash(state);
self.target_version.hash(state); self.target_version.hash(state);
// Add plugin properties in alphabetical order. // Add plugin properties in alphabetical order.
self.flake8_annotations.hash(state); self.flake8_annotations.hash(state);

View file

@ -24,6 +24,7 @@ pub struct Options {
pub select: Option<Vec<CheckCodePrefix>>, pub select: Option<Vec<CheckCodePrefix>>,
pub src: Option<Vec<String>>, pub src: Option<Vec<String>>,
pub target_version: Option<PythonVersion>, pub target_version: Option<PythonVersion>,
pub show_source: Option<bool>,
// Plugins // Plugins
pub flake8_annotations: Option<flake8_annotations::settings::Options>, pub flake8_annotations: Option<flake8_annotations::settings::Options>,
pub flake8_bugbear: Option<flake8_bugbear::settings::Options>, pub flake8_bugbear: Option<flake8_bugbear::settings::Options>,

View file

@ -146,6 +146,7 @@ mod tests {
dummy_variable_rgx: None, dummy_variable_rgx: None,
src: None, src: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -180,6 +181,7 @@ line-length = 79
dummy_variable_rgx: None, dummy_variable_rgx: None,
src: None, src: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -214,6 +216,7 @@ exclude = ["foo.py"]
dummy_variable_rgx: None, dummy_variable_rgx: None,
src: None, src: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -248,6 +251,7 @@ select = ["E501"]
dummy_variable_rgx: None, dummy_variable_rgx: None,
src: None, src: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -283,6 +287,7 @@ ignore = ["E501"]
dummy_variable_rgx: None, dummy_variable_rgx: None,
src: None, src: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: None, flake8_bugbear: None,
flake8_quotes: None, flake8_quotes: None,
@ -364,6 +369,7 @@ other-attribute = 1
dummy_variable_rgx: None, dummy_variable_rgx: None,
src: None, src: None,
target_version: None, target_version: None,
show_source: None,
flake8_annotations: None, flake8_annotations: None,
flake8_bugbear: Some(flake8_bugbear::settings::Options { flake8_bugbear: Some(flake8_bugbear::settings::Options {
extend_immutable_calls: Some(vec![ extend_immutable_calls: Some(vec![

View file

@ -49,6 +49,7 @@ pub struct UserConfiguration {
pub select: Vec<CheckCodePrefix>, pub select: Vec<CheckCodePrefix>,
pub src: Vec<PathBuf>, pub src: Vec<PathBuf>,
pub target_version: PythonVersion, pub target_version: PythonVersion,
pub show_source: bool,
// Plugins // Plugins
pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings,
@ -96,6 +97,7 @@ impl UserConfiguration {
select: configuration.select, select: configuration.select,
src: configuration.src, src: configuration.src,
target_version: configuration.target_version, target_version: configuration.target_version,
show_source: configuration.show_source,
flake8_annotations: configuration.flake8_annotations, flake8_annotations: configuration.flake8_annotations,
flake8_quotes: configuration.flake8_quotes, flake8_quotes: configuration.flake8_quotes,
flake8_tidy_imports: configuration.flake8_tidy_imports, flake8_tidy_imports: configuration.flake8_tidy_imports,

View file

@ -78,3 +78,15 @@ fn test_stdin_autofix_when_no_issues_should_still_print_contents() -> Result<()>
); );
Ok(()) Ok(())
} }
#[test]
fn test_show_source() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
let output = cmd
.args(["-", "--show-source"])
.write_stdin("l = 1")
.assert()
.failure();
assert!(str::from_utf8(&output.get_output().stdout)?.contains("l = 1"));
Ok(())
}