Compare commits
21 Commits
v0.1.0
...
options-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ca7407868 | ||
|
|
f1b00cafd4 | ||
|
|
dda4ceda71 | ||
|
|
195c000f5a | ||
|
|
a62c735f9e | ||
|
|
94b4bb0f57 | ||
|
|
fe485d791c | ||
|
|
d685107638 | ||
|
|
d85950ce5a | ||
|
|
88c0106421 | ||
|
|
f60aa85471 | ||
|
|
d942a777d7 | ||
|
|
8a529925b3 | ||
|
|
dc6b4ad2b4 | ||
|
|
21ea290d6a | ||
|
|
5da0f9111e | ||
|
|
cb6d74c27b | ||
|
|
73049df3ed | ||
|
|
bf0e5788ef | ||
|
|
4113d65836 | ||
|
|
4c2c9bf7e0 |
@@ -170,7 +170,8 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
|
||||
expressions, like function calls).
|
||||
|
||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`).
|
||||
1. Map the violation struct to a rule code in `crates/ruff_linter/src/codes.rs` (e.g., `B011`). New rules
|
||||
should be added in `RuleGroup::Preview`.
|
||||
|
||||
1. Add proper [testing](#rule-testing-fixtures-and-snapshots) for your rule.
|
||||
|
||||
|
||||
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -2259,6 +2259,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"serde_derive_internals 0.29.0",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
@@ -2624,7 +2625,7 @@ checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"serde_derive_internals 0.26.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
@@ -2664,9 +2665,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2684,9 +2685,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2704,6 +2705,17 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
@@ -3104,11 +3116,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -3117,9 +3128,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3128,9 +3139,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
|
||||
@@ -34,7 +34,7 @@ quote = { version = "1.0.23" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde = { version = "1.0.189", features = ["derive"] }
|
||||
serde_json = { version = "1.0.107" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.3.0", features = ["inline"] }
|
||||
@@ -46,7 +46,7 @@ syn = { version = "2.0.38" }
|
||||
test-case = { version = "3.2.1" }
|
||||
thiserror = { version = "1.0.49" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = { version = "0.1.37" }
|
||||
tracing = { version = "0.1.39" }
|
||||
tracing-indicatif = { version = "0.3.4" }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
|
||||
@@ -17,8 +17,8 @@ use ruff_linter::settings::DEFAULT_SELECTORS;
|
||||
use ruff_linter::warn_user;
|
||||
use ruff_workspace::options::{
|
||||
Flake8AnnotationsOptions, Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ErrMsgOptions,
|
||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintOptions,
|
||||
McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
||||
Flake8PytestStyleOptions, Flake8QuotesOptions, Flake8TidyImportsOptions, LintCommonOptions,
|
||||
LintOptions, McCabeOptions, Options, Pep8NamingOptions, PydocstyleOptions,
|
||||
};
|
||||
use ruff_workspace::pyproject::Pyproject;
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(crate) fn convert(
|
||||
|
||||
// Parse each supported option.
|
||||
let mut options = Options::default();
|
||||
let mut lint_options = LintOptions::default();
|
||||
let mut lint_options = LintCommonOptions::default();
|
||||
let mut flake8_annotations = Flake8AnnotationsOptions::default();
|
||||
let mut flake8_bugbear = Flake8BugbearOptions::default();
|
||||
let mut flake8_builtins = Flake8BuiltinsOptions::default();
|
||||
@@ -433,8 +433,11 @@ pub(crate) fn convert(
|
||||
}
|
||||
}
|
||||
|
||||
if lint_options != LintOptions::default() {
|
||||
options.lint = Some(lint_options);
|
||||
if lint_options != LintCommonOptions::default() {
|
||||
options.lint = Some(LintOptions {
|
||||
common: lint_options,
|
||||
..LintOptions::default()
|
||||
});
|
||||
}
|
||||
|
||||
// Create the pyproject.toml.
|
||||
@@ -465,7 +468,9 @@ mod tests {
|
||||
use ruff_linter::rules::flake8_quotes;
|
||||
use ruff_linter::rules::pydocstyle::settings::Convention;
|
||||
use ruff_linter::settings::types::PythonVersion;
|
||||
use ruff_workspace::options::{Flake8QuotesOptions, LintOptions, Options, PydocstyleOptions};
|
||||
use ruff_workspace::options::{
|
||||
Flake8QuotesOptions, LintCommonOptions, LintOptions, Options, PydocstyleOptions,
|
||||
};
|
||||
use ruff_workspace::pyproject::Pyproject;
|
||||
|
||||
use crate::converter::DEFAULT_SELECTORS;
|
||||
@@ -475,8 +480,8 @@ mod tests {
|
||||
use super::super::plugin::Plugin;
|
||||
use super::convert;
|
||||
|
||||
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintOptions {
|
||||
LintOptions {
|
||||
fn lint_default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> LintCommonOptions {
|
||||
LintCommonOptions {
|
||||
ignore: Some(vec![]),
|
||||
select: Some(
|
||||
DEFAULT_SELECTORS
|
||||
@@ -486,7 +491,7 @@ mod tests {
|
||||
.sorted_by_key(RuleSelector::prefix_and_code)
|
||||
.collect(),
|
||||
),
|
||||
..LintOptions::default()
|
||||
..LintCommonOptions::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +503,10 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -516,7 +524,10 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -534,7 +545,10 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
line_length: Some(LineLength::try_from(100).unwrap()),
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -551,7 +565,10 @@ mod tests {
|
||||
Some(vec![]),
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
@@ -569,13 +586,16 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(LintOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([])
|
||||
common: LintCommonOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([])
|
||||
},
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
@@ -597,12 +617,15 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(LintOptions {
|
||||
pydocstyle: Some(PydocstyleOptions {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: None,
|
||||
property_decorators: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Pydocstyle.into()])
|
||||
common: LintCommonOptions {
|
||||
pydocstyle: Some(PydocstyleOptions {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: None,
|
||||
property_decorators: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Pydocstyle.into()])
|
||||
},
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
@@ -621,13 +644,16 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
lint: Some(LintOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Flake8Quotes.into()])
|
||||
common: LintCommonOptions {
|
||||
flake8_quotes: Some(Flake8QuotesOptions {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
..lint_default_options([Linter::Flake8Quotes.into()])
|
||||
},
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
@@ -648,7 +674,10 @@ mod tests {
|
||||
);
|
||||
let expected = Pyproject::new(Options {
|
||||
target_version: Some(PythonVersion::Py38),
|
||||
lint: Some(lint_default_options([])),
|
||||
lint: Some(LintOptions {
|
||||
common: lint_default_options([]),
|
||||
..LintOptions::default()
|
||||
}),
|
||||
..Options::default()
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -366,6 +366,15 @@ pub struct FormatCommand {
|
||||
respect_gitignore: bool,
|
||||
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
|
||||
no_respect_gitignore: bool,
|
||||
/// List of paths, used to omit files and/or directories from analysis.
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "FILE_PATTERN",
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
|
||||
/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
|
||||
/// Use `--no-force-exclude` to disable.
|
||||
#[arg(
|
||||
@@ -386,9 +395,9 @@ pub struct FormatCommand {
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
|
||||
/// Enable preview mode; checks will include unstable rules and fixes.
|
||||
/// Enable preview mode; enables unstable formatting.
|
||||
/// Use `--no-preview` to disable.
|
||||
#[arg(long, overrides_with("no_preview"), hide = true)]
|
||||
#[arg(long, overrides_with("no_preview"))]
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
@@ -522,6 +531,7 @@ impl FormatCommand {
|
||||
self.respect_gitignore,
|
||||
self.no_respect_gitignore,
|
||||
),
|
||||
exclude: self.exclude,
|
||||
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||
@@ -658,6 +668,8 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
}
|
||||
if let Some(preview) = &self.preview {
|
||||
config.preview = Some(*preview);
|
||||
config.lint.preview = Some(*preview);
|
||||
config.format.preview = Some(*preview);
|
||||
}
|
||||
if let Some(per_file_ignores) = &self.per_file_ignores {
|
||||
config.lint.per_file_ignores = Some(collect_per_file_ignores(per_file_ignores.clone()));
|
||||
|
||||
@@ -10,7 +10,7 @@ use ruff_linter::linter::add_noqa_to_path;
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(crate) fn add_noqa(
|
||||
&paths
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(ignore::DirEntry::path)
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
@@ -45,14 +45,15 @@ pub(crate) fn add_noqa(
|
||||
let modifications: usize = paths
|
||||
.par_iter()
|
||||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
.filter_map(|resolved_file| {
|
||||
let SourceType::Python(source_type @ (PySourceType::Python | PySourceType::Stub)) =
|
||||
SourceType::from(path)
|
||||
SourceType::from(resolved_file.path())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let package = path
|
||||
let path = resolved_file.path();
|
||||
let package = resolved_file
|
||||
.path()
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
|
||||
@@ -22,7 +22,10 @@ use ruff_linter::{fs, warn_user_once, IOError};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, PyprojectDiscoveryStrategy,
|
||||
ResolvedFile,
|
||||
};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::cache::{self, Cache};
|
||||
@@ -42,8 +45,7 @@ pub(crate) fn check(
|
||||
// Collect all the Python files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
debug!("Identified files to lint in: {:?}", start.elapsed());
|
||||
|
||||
if paths.is_empty() {
|
||||
warn_user_once!("No Python files found under the given path(s)");
|
||||
@@ -77,7 +79,7 @@ pub(crate) fn check(
|
||||
&paths
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(ignore::DirEntry::path)
|
||||
.map(ResolvedFile::path)
|
||||
.collect::<Vec<_>>(),
|
||||
pyproject_config,
|
||||
);
|
||||
@@ -98,95 +100,114 @@ pub(crate) fn check(
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
let mut diagnostics: Diagnostics = paths
|
||||
.par_iter()
|
||||
.map(|entry| {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
let package = path
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
let diagnostics_per_file = paths.par_iter().filter_map(|resolved_file| {
|
||||
let result = match resolved_file {
|
||||
Ok(resolved_file) => {
|
||||
let path = resolved_file.path();
|
||||
let package = path
|
||||
.parent()
|
||||
.and_then(|parent| package_roots.get(parent))
|
||||
.and_then(|package| *package);
|
||||
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
|
||||
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
||||
let cache = caches.as_ref().and_then(|caches| {
|
||||
if let Some(cache) = caches.get(&cache_root) {
|
||||
Some(cache)
|
||||
} else {
|
||||
debug!("No cache found for {}", cache_root.display());
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
lint_path(
|
||||
path,
|
||||
package,
|
||||
&settings.linter,
|
||||
cache,
|
||||
noqa,
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
if !resolved_file.is_root()
|
||||
&& match_exclusion(
|
||||
resolved_file.path(),
|
||||
resolved_file.file_name(),
|
||||
&settings.linter.exclude,
|
||||
)
|
||||
.map_err(|e| {
|
||||
(Some(path.to_owned()), {
|
||||
let mut error = e.to_string();
|
||||
for cause in e.chain() {
|
||||
write!(&mut error, "\n Cause: {cause}").unwrap();
|
||||
}
|
||||
error
|
||||
})
|
||||
})
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Err(e) => Err((
|
||||
if let Error::WithPath { path, .. } = e {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
e.io_error()
|
||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||
)),
|
||||
}
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.linter.rules.enabled(Rule::IOError) {
|
||||
let dummy =
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
Diagnostics::new(
|
||||
vec![Message::from_diagnostic(
|
||||
Diagnostic::new(IOError { message }, TextRange::default()),
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
ImportMap::default(),
|
||||
FxHashMap::default(),
|
||||
)
|
||||
let cache_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
|
||||
let cache = caches.as_ref().and_then(|caches| {
|
||||
if let Some(cache) = caches.get(&cache_root) {
|
||||
Some(cache)
|
||||
} else {
|
||||
warn!(
|
||||
"{}{}{} {message}",
|
||||
"Failed to lint ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
Diagnostics::default()
|
||||
debug!("No cache found for {}", cache_root.display());
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
lint_path(
|
||||
path,
|
||||
package,
|
||||
&settings.linter,
|
||||
cache,
|
||||
noqa,
|
||||
fix_mode,
|
||||
unsafe_fixes,
|
||||
)
|
||||
.map_err(|e| {
|
||||
(Some(path.to_path_buf()), {
|
||||
let mut error = e.to_string();
|
||||
for cause in e.chain() {
|
||||
write!(&mut error, "\n Cause: {cause}").unwrap();
|
||||
}
|
||||
error
|
||||
})
|
||||
})
|
||||
}
|
||||
Err(e) => Err((
|
||||
if let Error::WithPath { path, .. } = e {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
warn!("{} {message}", "Encountered error:".bold());
|
||||
None
|
||||
},
|
||||
e.io_error()
|
||||
.map_or_else(|| e.to_string(), io::Error::to_string),
|
||||
)),
|
||||
};
|
||||
|
||||
Some(result.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.linter.rules.enabled(Rule::IOError) {
|
||||
let dummy =
|
||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||
|
||||
Diagnostics::new(
|
||||
vec![Message::from_diagnostic(
|
||||
Diagnostic::new(IOError { message }, TextRange::default()),
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
ImportMap::default(),
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
warn!(
|
||||
"{}{}{} {message}",
|
||||
"Failed to lint ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
Diagnostics::default()
|
||||
}
|
||||
})
|
||||
})
|
||||
.reduce(Diagnostics::default, |mut acc, item| {
|
||||
acc += item;
|
||||
acc
|
||||
});
|
||||
} else {
|
||||
warn!("{} {message}", "Encountered error:".bold());
|
||||
Diagnostics::default()
|
||||
}
|
||||
}))
|
||||
});
|
||||
|
||||
diagnostics.messages.sort();
|
||||
// Aggregate the diagnostics of all checked files and count the checked files.
|
||||
// This can't be a regular for loop because we use `par_iter`.
|
||||
let (mut all_diagnostics, checked_files) = diagnostics_per_file
|
||||
.fold(
|
||||
|| (Diagnostics::default(), 0u64),
|
||||
|(all_diagnostics, checked_files), file_diagnostics| {
|
||||
(all_diagnostics + file_diagnostics, checked_files + 1)
|
||||
},
|
||||
)
|
||||
.reduce(
|
||||
|| (Diagnostics::default(), 0u64),
|
||||
|a, b| (a.0 + b.0, a.1 + b.1),
|
||||
);
|
||||
|
||||
all_diagnostics.messages.sort();
|
||||
|
||||
// Store the caches.
|
||||
if let Some(caches) = caches {
|
||||
@@ -196,9 +217,9 @@ pub(crate) fn check(
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
debug!("Checked {:?} files in: {:?}", paths.len(), duration);
|
||||
debug!("Checked {:?} files in: {:?}", checked_files, duration);
|
||||
|
||||
Ok(diagnostics)
|
||||
Ok(all_diagnostics)
|
||||
}
|
||||
|
||||
/// Wraps [`lint_path`](crate::diagnostics::lint_path) in a [`catch_unwind`](std::panic::catch_unwind) and emits
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
|
||||
use ruff_linter::packaging;
|
||||
use ruff_linter::settings::flags;
|
||||
use ruff_workspace::resolver::{python_file_at_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||
@@ -22,6 +22,14 @@ pub(crate) fn check_stdin(
|
||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
|
||||
let lint_settings = &pyproject_config.settings.linter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
||||
{
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
}
|
||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)
|
||||
|
||||
@@ -20,7 +20,7 @@ use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_workspace::resolver::python_files_in_path;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_files_in_path};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
@@ -61,26 +61,42 @@ pub(crate) fn format(
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let (results, errors): (Vec<_>, Vec<_>) = paths
|
||||
let (mut results, errors): (Vec<_>, Vec<_>) = paths
|
||||
.into_par_iter()
|
||||
.filter_map(|entry| {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.into_path();
|
||||
|
||||
Ok(resolved_file) => {
|
||||
let path = resolved_file.path();
|
||||
let SourceType::Python(source_type) = SourceType::from(&path) else {
|
||||
// Ignore any non-Python files.
|
||||
return None;
|
||||
};
|
||||
|
||||
let resolved_settings = resolver.resolve(&path, &pyproject_config);
|
||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
||||
|
||||
// Ignore files that are excluded from formatting
|
||||
if !resolved_file.is_root()
|
||||
&& match_exclusion(
|
||||
path,
|
||||
resolved_file.file_name(),
|
||||
&resolved_settings.formatter.exclude,
|
||||
)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
match catch_unwind(|| {
|
||||
format_path(&path, &resolved_settings.formatter, source_type, mode)
|
||||
format_path(path, &resolved_settings.formatter, source_type, mode)
|
||||
}) {
|
||||
Ok(inner) => inner.map(|result| FormatPathResult { path, result }),
|
||||
Err(error) => Err(FormatCommandError::Panic(Some(path), error)),
|
||||
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||
path: resolved_file.into_path(),
|
||||
result,
|
||||
}),
|
||||
Err(error) => Err(FormatCommandError::Panic(
|
||||
Some(resolved_file.into_path()),
|
||||
error,
|
||||
)),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -104,6 +120,8 @@ pub(crate) fn format(
|
||||
error!("{error}");
|
||||
}
|
||||
|
||||
results.sort_unstable_by(|a, b| a.path.cmp(&b.path));
|
||||
|
||||
let summary = FormatSummary::new(results.as_slice(), mode);
|
||||
|
||||
// Report on the formatting changes.
|
||||
@@ -137,7 +155,7 @@ pub(crate) fn format(
|
||||
}
|
||||
|
||||
/// Format the file at the given [`Path`].
|
||||
#[tracing::instrument(skip_all, fields(path = %path.display()))]
|
||||
#[tracing::instrument(level="debug", skip_all, fields(path = %path.display()))]
|
||||
fn format_path(
|
||||
path: &Path,
|
||||
settings: &FormatterSettings,
|
||||
|
||||
@@ -6,7 +6,7 @@ use log::error;
|
||||
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_workspace::resolver::python_file_at_path;
|
||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
@@ -33,6 +33,14 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let format_settings = &pyproject_config.settings.formatter;
|
||||
if filename
|
||||
.file_name()
|
||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
||||
{
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
}
|
||||
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
|
||||
@@ -25,12 +25,13 @@ pub(crate) fn show_files(
|
||||
}
|
||||
|
||||
// Print the list of files.
|
||||
for entry in paths
|
||||
.iter()
|
||||
for path in paths
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.sorted_by(|a, b| a.path().cmp(b.path()))
|
||||
.map(ResolvedFile::into_path)
|
||||
.sorted_unstable()
|
||||
{
|
||||
writeln!(writer, "{}", entry.path().to_string_lossy())?;
|
||||
writeln!(writer, "{}", path.to_string_lossy())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use anyhow::{bail, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
|
||||
|
||||
use crate::args::CliOverrides;
|
||||
|
||||
@@ -19,16 +19,17 @@ pub(crate) fn show_settings(
|
||||
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
|
||||
|
||||
// Print the list of files.
|
||||
let Some(entry) = paths
|
||||
.iter()
|
||||
let Some(path) = paths
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.sorted_by(|a, b| a.path().cmp(b.path()))
|
||||
.map(ResolvedFile::into_path)
|
||||
.sorted_unstable()
|
||||
.next()
|
||||
else {
|
||||
bail!("No files found under the given path");
|
||||
};
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
|
||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::ops::AddAssign;
|
||||
use std::ops::{Add, AddAssign};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
@@ -11,7 +11,6 @@ use anyhow::{Context, Result};
|
||||
use colored::Colorize;
|
||||
use filetime::FileTime;
|
||||
use log::{debug, error, warn};
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -20,6 +19,7 @@ use ruff_linter::logging::DisplayParseError;
|
||||
use ruff_linter::message::Message;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::registry::AsRule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
@@ -61,7 +61,7 @@ impl FileCacheKey {
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub(crate) struct Diagnostics {
|
||||
pub(crate) messages: Vec<Message>,
|
||||
pub(crate) fixed: FxHashMap<String, FixTable>,
|
||||
pub(crate) fixed: FixMap,
|
||||
pub(crate) imports: ImportMap,
|
||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
@@ -74,7 +74,7 @@ impl Diagnostics {
|
||||
) -> Self {
|
||||
Self {
|
||||
messages,
|
||||
fixed: FxHashMap::default(),
|
||||
fixed: FixMap::default(),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
}
|
||||
@@ -142,22 +142,68 @@ impl Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Diagnostics {
|
||||
type Output = Diagnostics;
|
||||
|
||||
fn add(mut self, other: Self) -> Self::Output {
|
||||
self += other;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Diagnostics {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.messages.extend(other.messages);
|
||||
self.imports.extend(other.imports);
|
||||
for (filename, fixed) in other.fixed {
|
||||
self.fixed += other.fixed;
|
||||
self.notebook_indexes.extend(other.notebook_indexes);
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of fixes indexed by file path.
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub(crate) struct FixMap(FxHashMap<String, FixTable>);
|
||||
|
||||
impl FixMap {
|
||||
/// Returns `true` if there are no fixes in the map.
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the fixes in the map, along with the file path.
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (&String, &FixTable)> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the fixes in the map.
|
||||
pub(crate) fn values(&self) -> impl Iterator<Item = &FixTable> {
|
||||
self.0.values()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, FixTable)> for FixMap {
|
||||
fn from_iter<T: IntoIterator<Item = (String, FixTable)>>(iter: T) -> Self {
|
||||
Self(
|
||||
iter.into_iter()
|
||||
.filter(|(_, fixes)| !fixes.is_empty())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for FixMap {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
for (filename, fixed) in rhs.0 {
|
||||
if fixed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let fixed_in_file = self.fixed.entry(filename).or_default();
|
||||
let fixed_in_file = self.0.entry(filename).or_default();
|
||||
for (rule, count) in fixed {
|
||||
if count > 0 {
|
||||
*fixed_in_file.entry(rule).or_default() += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.notebook_indexes.extend(other.notebook_indexes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +364,7 @@ pub(crate) fn lint_path(
|
||||
|
||||
Ok(Diagnostics {
|
||||
messages,
|
||||
fixed: FxHashMap::from_iter([(fs::relativize_path(path), fixed)]),
|
||||
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
})
|
||||
@@ -436,7 +482,7 @@ pub(crate) fn lint_stdin(
|
||||
|
||||
Ok(Diagnostics {
|
||||
messages,
|
||||
fixed: FxHashMap::from_iter([(
|
||||
fixed: FixMap::from_iter([(
|
||||
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
|
||||
fixed,
|
||||
)]),
|
||||
|
||||
@@ -7,11 +7,9 @@ use anyhow::Result;
|
||||
use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use itertools::{iterate, Itertools};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use ruff_linter::fs::relativize_path;
|
||||
use ruff_linter::linter::FixTable;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::message::{
|
||||
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
|
||||
@@ -22,7 +20,7 @@ use ruff_linter::registry::{AsRule, Rule};
|
||||
use ruff_linter::settings::flags::{self};
|
||||
use ruff_linter::settings::types::{SerializationFormat, UnsafeFixes};
|
||||
|
||||
use crate::diagnostics::Diagnostics;
|
||||
use crate::diagnostics::{Diagnostics, FixMap};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
@@ -462,7 +460,7 @@ fn show_fix_status(fix_mode: flags::FixMode, fixables: Option<&FixableStatistics
|
||||
(!fix_mode.is_apply()) && fixables.is_some_and(FixableStatistics::any_applicable_fixes)
|
||||
}
|
||||
|
||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FxHashMap<String, FixTable>) -> Result<()> {
|
||||
fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> {
|
||||
let total = fixed
|
||||
.values()
|
||||
.map(|table| table.values().sum::<usize>())
|
||||
|
||||
@@ -13,7 +13,7 @@ const BIN_NAME: &str = "ruff";
|
||||
#[test]
|
||||
fn default_options() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated"])
|
||||
.args(["format", "--isolated", "--stdin-filename", "test.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
@@ -50,6 +50,9 @@ fn format_options() -> Result<()> {
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
tab-size = 8
|
||||
line-length = 84
|
||||
|
||||
[format]
|
||||
indent-style = "tab"
|
||||
quote-style = "single"
|
||||
@@ -64,7 +67,7 @@ line-ending = "cr-lf"
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't change quotes")
|
||||
print("Shouldn't change quotes. It exceeds the line width with the tab size 8")
|
||||
|
||||
|
||||
if condition:
|
||||
@@ -76,7 +79,9 @@ if condition:
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(arg1, arg2):
|
||||
print("Shouldn't change quotes")
|
||||
print(
|
||||
"Shouldn't change quotes. It exceeds the line width with the tab size 8"
|
||||
)
|
||||
|
||||
|
||||
if condition:
|
||||
@@ -88,6 +93,108 @@ if condition:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-exclude = ["out"]
|
||||
|
||||
[format]
|
||||
exclude = ["test.py", "generated.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.py"),
|
||||
r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Excluded file but passed to the CLI directly, should be formatted
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("generated.py"),
|
||||
r#"NUMBERS = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||
]
|
||||
OTHER = "OTHER"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let out_dir = tempdir.path().join("out");
|
||||
fs::create_dir(&out_dir)?;
|
||||
|
||||
fs::write(out_dir.join("a.py"), "a = a")?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(["format", "--check", "--config"])
|
||||
.arg(ruff_toml.file_name().unwrap())
|
||||
// Explicitly pass test.py, should be formatted regardless of it being excluded by format.exclude
|
||||
.arg(test_path.file_name().unwrap())
|
||||
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Would reformat: main.py
|
||||
Would reformat: test.py
|
||||
2 files would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[format]
|
||||
exclude = ["generated.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "-"])
|
||||
.pass_stdin(r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == '__main__':
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is not yet stable, and subject to change in future versions.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_option_inheritance() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -1252,8 +1252,8 @@ fn diff_does_not_show_display_only_fixes_with_unsafe_fixes_enabled() {
|
||||
])
|
||||
.pass_stdin("def add_to_list(item, some_list=[]): ..."),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
@@ -1276,8 +1276,8 @@ fn diff_only_unsafe_fixes_available() {
|
||||
])
|
||||
.pass_stdin("x = {'a': 1, 'a': 1}\nprint(('foo'))\n"),
|
||||
@r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -31,14 +31,15 @@ inline-quotes = "single"
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
.args(["--stdin-filename", "test.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"a = "abcba".strip("aba")"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
-:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
-:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:1:5: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:1:5: B005 Using `.strip()` with multi-character strings is misleading
|
||||
test.py:1:19: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 2 fixable with the `--fix` option.
|
||||
|
||||
@@ -155,3 +156,117 @@ inline-quotes = "single"
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
extend-exclude = ["out"]
|
||||
|
||||
[lint]
|
||||
exclude = ["test.py", "generated.py"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.py"),
|
||||
r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Excluded file but passed to the CLI directly, should be linted
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("generated.py"),
|
||||
r#"NUMBERS = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||
]
|
||||
OTHER = "OTHER"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let out_dir = tempdir.path().join("out");
|
||||
fs::create_dir(&out_dir)?;
|
||||
|
||||
fs::write(out_dir.join("a.py"), r#"a = "a""#)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
// Explicitly pass test.py, should be linted regardless of it being excluded by lint.exclude
|
||||
.arg(test_path.file_name().unwrap())
|
||||
// Lint all other files in the directory, should respect the `exclude` and `lint.exclude` options
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:4:16: Q000 [*] Double quotes found but single quotes preferred
|
||||
main.py:5:12: Q000 [*] Double quotes found but single quotes preferred
|
||||
test.py:3:15: Q000 [*] Double quotes found but single quotes preferred
|
||||
Found 3 errors.
|
||||
[*] 3 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend-select = ["B", "Q"]
|
||||
|
||||
[lint]
|
||||
exclude = ["generated.py"]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("check")
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--stdin-filename", "generated.py"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
from test import say_hy
|
||||
|
||||
if __name__ == "__main__":
|
||||
say_hy("dear Ruff contributor")
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::{fmt, fs, io, iter};
|
||||
|
||||
use anyhow::{bail, format_err, Context, Error};
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use ignore::DirEntry;
|
||||
use imara_diff::intern::InternedInput;
|
||||
use imara_diff::sink::Counter;
|
||||
use imara_diff::{diff, Algorithm};
|
||||
@@ -36,14 +35,14 @@ use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||
use ruff_python_formatter::{
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||
};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, Resolver};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||
|
||||
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn ruff_check_paths(
|
||||
dirs: &[PathBuf],
|
||||
) -> anyhow::Result<(
|
||||
Vec<Result<DirEntry, ignore::Error>>,
|
||||
Vec<Result<ResolvedFile, ignore::Error>>,
|
||||
Resolver,
|
||||
PyprojectConfig,
|
||||
)> {
|
||||
@@ -467,9 +466,9 @@ fn format_dev_project(
|
||||
let iter = { paths.into_par_iter() };
|
||||
#[cfg(feature = "singlethreaded")]
|
||||
let iter = { paths.into_iter() };
|
||||
iter.map(|dir_entry| {
|
||||
iter.map(|path| {
|
||||
let result = format_dir_entry(
|
||||
dir_entry,
|
||||
path,
|
||||
stability_check,
|
||||
write,
|
||||
&black_options,
|
||||
@@ -527,24 +526,20 @@ fn format_dev_project(
|
||||
|
||||
/// Error handling in between walkdir and `format_dev_file`
|
||||
fn format_dir_entry(
|
||||
dir_entry: Result<DirEntry, ignore::Error>,
|
||||
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||
stability_check: bool,
|
||||
write: bool,
|
||||
options: &BlackOptions,
|
||||
resolver: &Resolver,
|
||||
pyproject_config: &PyprojectConfig,
|
||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||
let dir_entry = match dir_entry.context("Iterating the files in the repository failed") {
|
||||
Ok(dir_entry) => dir_entry,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let file = dir_entry.path().to_path_buf();
|
||||
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
||||
// For some reason it does not filter in the beginning
|
||||
if dir_entry.file_name() == "pyproject.toml" {
|
||||
return Ok((Ok(Statistics::default()), file));
|
||||
if resolved_file.file_name() == "pyproject.toml" {
|
||||
return Ok((Ok(Statistics::default()), resolved_file.into_path()));
|
||||
}
|
||||
|
||||
let path = dir_entry.path().to_path_buf();
|
||||
let path = resolved_file.into_path();
|
||||
let mut options = options.to_py_format_options(&path);
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
//!
|
||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruff_workspace::options::Options;
|
||||
@@ -107,6 +108,24 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
|
||||
output.push('\n');
|
||||
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
|
||||
output.push('\n');
|
||||
|
||||
if !field.aliases.is_empty() {
|
||||
let title = if field.aliases.len() == 1 {
|
||||
"Alias"
|
||||
} else {
|
||||
"Aliases"
|
||||
};
|
||||
output.push_str(&format!(
|
||||
"**{title}**: {}\n",
|
||||
field
|
||||
.aliases
|
||||
.iter()
|
||||
.map(|alias| format!("`{alias}`"))
|
||||
.join(", ")
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output.push_str(&format!(
|
||||
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
|
||||
if let Some(set_name) = parent_set.name() {
|
||||
|
||||
@@ -95,7 +95,7 @@ impl std::fmt::Display for IndentStyle {
|
||||
///
|
||||
/// Determines the visual width of a tab character (`\t`) and the number of
|
||||
/// spaces per indent when using [`IndentStyle::Space`].
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct IndentWidth(NonZeroU8);
|
||||
|
||||
@@ -54,7 +54,7 @@ impl<'a> Printer<'a> {
|
||||
|
||||
/// Prints the passed in element as well as all its content,
|
||||
/// starting at the specified indentation level
|
||||
#[tracing::instrument(name = "Printer::print", skip_all)]
|
||||
#[tracing::instrument(level = "debug", name = "Printer::print", skip_all)]
|
||||
pub fn print_with_indent(
|
||||
mut self,
|
||||
document: &'a Document,
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pydocstyle/D300.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
def with_backslash():
|
||||
"""Sum\\mary."""
|
||||
|
||||
|
||||
def ends_in_quote():
|
||||
'Sum\\mary."'
|
||||
|
||||
|
||||
def contains_quote():
|
||||
'Sum"\\mary.'
|
||||
@@ -10,6 +10,10 @@ def double_quotes_backslash_uppercase():
|
||||
R"""Sum\\mary."""
|
||||
|
||||
|
||||
def shouldnt_add_raw_here():
|
||||
"Ruff \U000026a1"
|
||||
|
||||
|
||||
def make_unique_pod_id(pod_id: str) -> str | None:
|
||||
r"""
|
||||
Generate a unique Pod name.
|
||||
|
||||
17
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py
vendored
Normal file
17
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Test that type parameters are considered used."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from .foo import Record as Record1
|
||||
from .bar import Record as Record2
|
||||
|
||||
type RecordCallback[R: Record1] = Callable[[R], None]
|
||||
|
||||
|
||||
def process_record[R: Record2](record: R) -> None:
|
||||
...
|
||||
5
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py
vendored
Normal file
5
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Test lazy evaluation of type alias values."""
|
||||
|
||||
type RecordCallback[R: Record] = Callable[[R], None]
|
||||
|
||||
from collections.abc import Callable
|
||||
@@ -68,6 +68,10 @@ class Apples:
|
||||
def _missing_(cls, value):
|
||||
pass
|
||||
|
||||
# Allow anonymous functions.
|
||||
def _(self):
|
||||
pass
|
||||
|
||||
|
||||
def __foo_bar__(): # this is not checked by the [bad-dunder-name] rule
|
||||
...
|
||||
|
||||
@@ -6,6 +6,14 @@ for item in {1}:
|
||||
for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
for item in {1,}:
|
||||
print(f"I can count to {item}!")
|
||||
|
||||
for item in {
|
||||
"apples", "lemons", "water"
|
||||
}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/pylint/literal_membership.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Errors
|
||||
1 in [1, 2, 3]
|
||||
1 in (1, 2, 3)
|
||||
1 in (
|
||||
1, 2, 3
|
||||
)
|
||||
|
||||
# OK
|
||||
fruits = ["cherry", "grapes"]
|
||||
"cherry" in fruits
|
||||
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
71
crates/ruff_linter/resources/test/fixtures/pylint/misplaced_bare_raise.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# bare raise is an except block
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
if True:
|
||||
raise
|
||||
|
||||
# bare raise is in a finally block which is in an except block
|
||||
try:
|
||||
raise Exception
|
||||
except Exception:
|
||||
try:
|
||||
raise Exception
|
||||
finally:
|
||||
raise
|
||||
|
||||
# bare raise is in an __exit__ method
|
||||
class ContextManager:
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *args):
|
||||
raise
|
||||
|
||||
try:
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def f():
|
||||
try:
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def g():
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
def h():
|
||||
try:
|
||||
if True:
|
||||
def i():
|
||||
raise # [misplaced-bare-raise]
|
||||
except Exception:
|
||||
pass
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
def i():
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
class C:
|
||||
raise # [misplaced-bare-raise]
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
raise # [misplaced-bare-raise]
|
||||
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
54
crates/ruff_linter/resources/test/fixtures/pylint/too_many_boolean_expressions.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
if a:
|
||||
...
|
||||
elif (a and b):
|
||||
...
|
||||
elif (a and b) and c:
|
||||
...
|
||||
elif (a and b) and c and d:
|
||||
...
|
||||
elif (a and b) and c and d and e:
|
||||
...
|
||||
elif (a and b) and c and d and e and f:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
...
|
||||
elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
...
|
||||
else:
|
||||
...
|
||||
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
44
crates/ruff_linter/resources/test/fixtures/pylint/unspecified_encoding.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import io
|
||||
import sys
|
||||
import tempfile
|
||||
import io as hugo
|
||||
import codecs
|
||||
|
||||
# Errors.
|
||||
open("test.txt")
|
||||
io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
tempfile.NamedTemporaryFile("w")
|
||||
tempfile.TemporaryFile("w")
|
||||
codecs.open("test.txt")
|
||||
tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
||||
# Non-errors.
|
||||
open("test.txt", encoding="utf-8")
|
||||
open("test.bin", "wb")
|
||||
open("test.bin", mode="wb")
|
||||
open("test.txt", "r", -1, "utf-8")
|
||||
open("test.txt", mode=sys.argv[1])
|
||||
|
||||
def func(*args, **kwargs):
|
||||
open(*args)
|
||||
open("text.txt", **kwargs)
|
||||
|
||||
io.TextIOWrapper(io.FileIO("test.txt"), encoding="utf-8")
|
||||
io.TextIOWrapper(io.FileIO("test.txt"), "utf-8")
|
||||
tempfile.TemporaryFile("w", encoding="utf-8")
|
||||
tempfile.TemporaryFile("w", -1, "utf-8")
|
||||
tempfile.TemporaryFile("wb")
|
||||
tempfile.TemporaryFile()
|
||||
tempfile.NamedTemporaryFile("w", encoding="utf-8")
|
||||
tempfile.NamedTemporaryFile("w", -1, "utf-8")
|
||||
tempfile.NamedTemporaryFile("wb")
|
||||
tempfile.NamedTemporaryFile()
|
||||
codecs.open("test.txt", encoding="utf-8")
|
||||
codecs.open("test.bin", "wb")
|
||||
codecs.open("test.bin", mode="wb")
|
||||
codecs.open("test.txt", "r", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", encoding="utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "wb")
|
||||
tempfile.SpooledTemporaryFile(0, )
|
||||
@@ -786,6 +786,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SubprocessRunWithoutCheck) {
|
||||
pylint::rules::subprocess_run_without_check(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnspecifiedEncoding) {
|
||||
pylint::rules::unspecified_encoding(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::PytestRaisesWithoutException,
|
||||
Rule::PytestRaisesTooBroad,
|
||||
@@ -1194,6 +1197,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ComparisonWithItself) {
|
||||
pylint::rules::comparison_with_itself(checker, left, ops, comparators);
|
||||
}
|
||||
if checker.enabled(Rule::LiteralMembership) {
|
||||
pylint::rules::literal_membership(checker, compare);
|
||||
}
|
||||
if checker.enabled(Rule::ComparisonOfConstant) {
|
||||
pylint::rules::comparison_of_constant(checker, left, ops, comparators);
|
||||
}
|
||||
|
||||
@@ -964,7 +964,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Raise(ast::StmtRaise { exc, .. }) => {
|
||||
Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => {
|
||||
if checker.enabled(Rule::RaiseNotImplemented) {
|
||||
if let Some(expr) = exc {
|
||||
pyflakes::rules::raise_not_implemented(checker, expr);
|
||||
@@ -1004,6 +1004,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_raise::rules::unnecessary_paren_on_raise_exception(checker, expr);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MisplacedBareRaise) {
|
||||
pylint::rules::misplaced_bare_raise(checker, raise);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
@@ -1067,6 +1070,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CheckAndRemoveFromSet) {
|
||||
refurb::rules::check_and_remove_from_set(checker, if_);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyBooleanExpressions) {
|
||||
pylint::rules::too_many_boolean_expressions(checker, if_);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{Expr, TypeParam};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::{ScopeId, Snapshot};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -10,7 +10,7 @@ pub(crate) struct Deferred<'a> {
|
||||
pub(crate) scopes: Vec<ScopeId>,
|
||||
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
||||
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) type_param_definitions: Vec<(&'a TypeParam, Snapshot)>,
|
||||
pub(crate) type_param_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) for_loops: Vec<Snapshot>,
|
||||
|
||||
@@ -524,6 +524,7 @@ where
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
self.semantic.push_scope(ScopeKind::Function(function_def));
|
||||
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
|
||||
@@ -562,6 +563,7 @@ where
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
self.semantic.push_scope(ScopeKind::Class(class_def));
|
||||
self.semantic.flags -= SemanticModelFlags::EXCEPTION_HANDLER;
|
||||
|
||||
// Extract any global bindings from the class body.
|
||||
if let Some(globals) = Globals::from_body(body) {
|
||||
@@ -580,9 +582,9 @@ where
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
// The value in a `type` alias has annotation semantics, in that it's never
|
||||
// evaluated at runtime.
|
||||
self.visit_annotation(value);
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((value, self.semantic.snapshot()));
|
||||
self.semantic.pop_scope();
|
||||
self.visit_expr(name);
|
||||
}
|
||||
@@ -1387,9 +1389,14 @@ where
|
||||
}
|
||||
}
|
||||
// Step 2: Traversal
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((type_param, self.semantic.snapshot()));
|
||||
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
|
||||
bound: Some(bound), ..
|
||||
}) = type_param
|
||||
{
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((bound, self.semantic.snapshot()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1764,12 +1771,9 @@ impl<'a> Checker<'a> {
|
||||
for (type_param, snapshot) in type_params {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
|
||||
bound: Some(bound), ..
|
||||
}) = type_param
|
||||
{
|
||||
self.visit_annotation(bound);
|
||||
}
|
||||
self.semantic.flags |=
|
||||
SemanticModelFlags::TYPE_PARAM_DEFINITION | SemanticModelFlags::TYPE_DEFINITION;
|
||||
self.visit_expr(type_param);
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
|
||||
@@ -253,3 +253,9 @@ impl From<NonZeroU8> for TabSize {
|
||||
Self(tab_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TabSize> for NonZeroU8 {
|
||||
fn from(value: TabSize) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// "text.txt".strip(".txt") # "ex"
|
||||
/// "text.txt".strip(".txt") # "e"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -5,8 +5,8 @@ use rustc_hash::FxHashMap;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::node::AstNode;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AstNode;
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Decorator, Expr, ExprContext};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
|
||||
@@ -6,7 +6,7 @@ use log::error;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, whitespace, Constant, ElifElseClause, Expr, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -87,6 +87,7 @@ mod tests {
|
||||
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D.py"))]
|
||||
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D301.py"))]
|
||||
#[test_case(Rule::TripleSingleQuotes, Path::new("D.py"))]
|
||||
#[test_case(Rule::TripleSingleQuotes, Path::new("D300.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use memchr::memchr_iter;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -46,18 +46,21 @@ use crate::docstrings::Docstring;
|
||||
#[violation]
|
||||
pub struct EscapeSequenceInDocstring;
|
||||
|
||||
impl Violation for EscapeSequenceInDocstring {
|
||||
impl AlwaysFixableViolation for EscapeSequenceInDocstring {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(r#"Use `r"""` if any backslashes in a docstring"#)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!(r#"Add `r` prefix"#)
|
||||
}
|
||||
}
|
||||
|
||||
/// D301
|
||||
pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
|
||||
// Docstring is already raw.
|
||||
let contents = docstring.contents;
|
||||
if contents.starts_with('r') || contents.starts_with("ur") {
|
||||
if docstring.leading_quote().contains(['r', 'R']) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -67,11 +70,15 @@ pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
|
||||
if memchr_iter(b'\\', bytes).any(|position| {
|
||||
let escaped_char = bytes.get(position.saturating_add(1));
|
||||
// Allow continuations (backslashes followed by newlines) and Unicode escapes.
|
||||
!matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'N'))
|
||||
!matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'U' | b'N'))
|
||||
}) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
EscapeSequenceInDocstring,
|
||||
let mut diagnostic = Diagnostic::new(EscapeSequenceInDocstring, docstring.range());
|
||||
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
|
||||
"r".to_owned() + docstring.contents,
|
||||
docstring.range(),
|
||||
));
|
||||
)));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_codegen::Quote;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -37,6 +37,8 @@ pub struct TripleSingleQuotes {
|
||||
}
|
||||
|
||||
impl Violation for TripleSingleQuotes {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TripleSingleQuotes { expected_quote } = self;
|
||||
@@ -45,12 +47,25 @@ impl Violation for TripleSingleQuotes {
|
||||
Quote::Single => format!(r#"Use triple single quotes `'''`"#),
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let TripleSingleQuotes { expected_quote } = self;
|
||||
Some(match expected_quote {
|
||||
Quote::Double => format!("Convert to triple double quotes"),
|
||||
Quote::Single => format!("Convert to triple single quotes"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// D300
|
||||
pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
|
||||
let leading_quote = docstring.leading_quote();
|
||||
|
||||
let prefixes = docstring
|
||||
.leading_quote()
|
||||
.trim_end_matches(|c| c == '\'' || c == '"')
|
||||
.to_owned();
|
||||
|
||||
let expected_quote = if docstring.body().contains("\"\"\"") {
|
||||
Quote::Single
|
||||
} else {
|
||||
@@ -60,18 +75,34 @@ pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
|
||||
match expected_quote {
|
||||
Quote::Single => {
|
||||
if !leading_quote.ends_with("'''") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TripleSingleQuotes { expected_quote },
|
||||
docstring.range(),
|
||||
));
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
|
||||
|
||||
let body = docstring.body().as_str();
|
||||
if !body.ends_with('\'') {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{prefixes}'''{body}'''"),
|
||||
docstring.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
Quote::Double => {
|
||||
if !leading_quote.ends_with("\"\"\"") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TripleSingleQuotes { expected_quote },
|
||||
docstring.range(),
|
||||
));
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
|
||||
|
||||
let body = docstring.body().as_str();
|
||||
if !body.ends_with('"') {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("{prefixes}\"\"\"{body}\"\"\""),
|
||||
docstring.range(),
|
||||
)));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,102 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D.py:307:5: D300 Use triple double quotes `"""`
|
||||
D.py:307:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
306 | def triple_single_quotes_raw():
|
||||
307 | r'''Summary.'''
|
||||
| ^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:312:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
304 304 |
|
||||
305 305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
306 306 | def triple_single_quotes_raw():
|
||||
307 |- r'''Summary.'''
|
||||
307 |+ r"""Summary."""
|
||||
308 308 |
|
||||
309 309 |
|
||||
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
|
||||
D.py:312:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
311 | def triple_single_quotes_raw_uppercase():
|
||||
312 | R'''Summary.'''
|
||||
| ^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:317:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
309 309 |
|
||||
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
311 311 | def triple_single_quotes_raw_uppercase():
|
||||
312 |- R'''Summary.'''
|
||||
312 |+ R"""Summary."""
|
||||
313 313 |
|
||||
314 314 |
|
||||
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
|
||||
D.py:317:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
316 | def single_quotes_raw():
|
||||
317 | r'Summary.'
|
||||
| ^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:322:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
314 314 |
|
||||
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
316 316 | def single_quotes_raw():
|
||||
317 |- r'Summary.'
|
||||
317 |+ r"""Summary."""
|
||||
318 318 |
|
||||
319 319 |
|
||||
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
|
||||
D.py:322:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
321 | def single_quotes_raw_uppercase():
|
||||
322 | R'Summary.'
|
||||
| ^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:328:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
319 319 |
|
||||
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
321 321 | def single_quotes_raw_uppercase():
|
||||
322 |- R'Summary.'
|
||||
322 |+ R"""Summary."""
|
||||
323 323 |
|
||||
324 324 |
|
||||
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
|
||||
D.py:328:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
326 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
327 | def single_quotes_raw_uppercase_backslash():
|
||||
328 | R'Sum\mary.'
|
||||
| ^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:645:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
326 326 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
327 327 | def single_quotes_raw_uppercase_backslash():
|
||||
328 |- R'Sum\mary.'
|
||||
328 |+ R"""Sum\mary."""
|
||||
329 329 |
|
||||
330 330 |
|
||||
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
|
||||
D.py:645:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
645 | "\
|
||||
@@ -51,8 +106,21 @@ D.py:645:5: D300 Use triple double quotes `"""`
|
||||
647 |
|
||||
648 | class StatementOnSameLineAsDocstring:
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:649:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
642 642 |
|
||||
643 643 |
|
||||
644 644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
645 |- "\
|
||||
646 |- "
|
||||
645 |+ """\
|
||||
646 |+ """
|
||||
647 647 |
|
||||
648 648 | class StatementOnSameLineAsDocstring:
|
||||
649 649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
|
||||
D.py:649:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
648 | class StatementOnSameLineAsDocstring:
|
||||
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
@@ -60,15 +128,37 @@ D.py:649:5: D300 Use triple double quotes `"""`
|
||||
650 | def sort_services(self):
|
||||
651 | pass
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:654:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
646 646 | "
|
||||
647 647 |
|
||||
648 648 | class StatementOnSameLineAsDocstring:
|
||||
649 |- "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
|
||||
649 |+ """After this docstring there's another statement on the same line separated by a semicolon.""" ; priorities=1
|
||||
650 650 | def sort_services(self):
|
||||
651 651 | pass
|
||||
652 652 |
|
||||
|
||||
D.py:654:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
653 | class StatementOnSameLineAsDocstring:
|
||||
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:658:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
651 651 | pass
|
||||
652 652 |
|
||||
653 653 | class StatementOnSameLineAsDocstring:
|
||||
654 |- "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
|
||||
654 |+ """After this docstring there's another statement on the same line separated by a semicolon."""; priorities=1
|
||||
655 655 |
|
||||
656 656 |
|
||||
657 657 | class CommentAfterDocstring:
|
||||
|
||||
D.py:658:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
657 | class CommentAfterDocstring:
|
||||
658 | "After this docstring there's a comment." # priorities=1
|
||||
@@ -76,8 +166,19 @@ D.py:658:5: D300 Use triple double quotes `"""`
|
||||
659 | def sort_services(self):
|
||||
660 | pass
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D.py:664:5: D300 Use triple double quotes `"""`
|
||||
ℹ Fix
|
||||
655 655 |
|
||||
656 656 |
|
||||
657 657 | class CommentAfterDocstring:
|
||||
658 |- "After this docstring there's a comment." # priorities=1
|
||||
658 |+ """After this docstring there's a comment.""" # priorities=1
|
||||
659 659 | def sort_services(self):
|
||||
660 660 | pass
|
||||
661 661 |
|
||||
|
||||
D.py:664:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
663 | def newline_after_closing_quote(self):
|
||||
664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
@@ -85,5 +186,15 @@ D.py:664:5: D300 Use triple double quotes `"""`
|
||||
665 | | but continuations shouldn't be considered multi-line"
|
||||
| |_________________________________________________________^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
ℹ Fix
|
||||
661 661 |
|
||||
662 662 |
|
||||
663 663 | def newline_after_closing_quote(self):
|
||||
664 |- "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
665 |- but continuations shouldn't be considered multi-line"
|
||||
664 |+ """We enforce a newline after the closing quote for a multi-line docstring \
|
||||
665 |+ but continuations shouldn't be considered multi-line"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D300.py:6:5: D300 Use triple double quotes `"""`
|
||||
|
|
||||
5 | def ends_in_quote():
|
||||
6 | 'Sum\\mary."'
|
||||
| ^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
D300.py:10:5: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
9 | def contains_quote():
|
||||
10 | 'Sum"\\mary.'
|
||||
| ^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
ℹ Fix
|
||||
7 7 |
|
||||
8 8 |
|
||||
9 9 | def contains_quote():
|
||||
10 |- 'Sum"\\mary.'
|
||||
10 |+ """Sum"\\mary."""
|
||||
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D.py:328:5: D301 Use `r"""` if any backslashes in a docstring
|
||||
|
|
||||
326 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
327 | def single_quotes_raw_uppercase_backslash():
|
||||
328 | R'Sum\mary.'
|
||||
| ^^^^^^^^^^^^ D301
|
||||
|
|
||||
|
||||
D.py:333:5: D301 Use `r"""` if any backslashes in a docstring
|
||||
D.py:333:5: D301 [*] Use `r"""` if any backslashes in a docstring
|
||||
|
|
||||
331 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
332 | def double_quotes_backslash():
|
||||
333 | """Sum\\mary."""
|
||||
| ^^^^^^^^^^^^^^^^ D301
|
||||
|
|
||||
= help: Add `r` prefix
|
||||
|
||||
D.py:338:5: D301 Use `r"""` if any backslashes in a docstring
|
||||
|
|
||||
336 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
337 | def double_quotes_backslash_uppercase():
|
||||
338 | R"""Sum\\mary."""
|
||||
| ^^^^^^^^^^^^^^^^^ D301
|
||||
|
|
||||
ℹ Suggested fix
|
||||
330 330 |
|
||||
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
332 332 | def double_quotes_backslash():
|
||||
333 |- """Sum\\mary."""
|
||||
333 |+ r"""Sum\\mary."""
|
||||
334 334 |
|
||||
335 335 |
|
||||
336 336 | @expect('D301: Use r""" if any backslashes in a docstring')
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D301.py:2:5: D301 Use `r"""` if any backslashes in a docstring
|
||||
D301.py:2:5: D301 [*] Use `r"""` if any backslashes in a docstring
|
||||
|
|
||||
1 | def double_quotes_backslash():
|
||||
2 | """Sum\\mary."""
|
||||
| ^^^^^^^^^^^^^^^^ D301
|
||||
|
|
||||
= help: Add `r` prefix
|
||||
|
||||
D301.py:10:5: D301 Use `r"""` if any backslashes in a docstring
|
||||
|
|
||||
9 | def double_quotes_backslash_uppercase():
|
||||
10 | R"""Sum\\mary."""
|
||||
| ^^^^^^^^^^^^^^^^^ D301
|
||||
|
|
||||
ℹ Suggested fix
|
||||
1 1 | def double_quotes_backslash():
|
||||
2 |- """Sum\\mary."""
|
||||
2 |+ r"""Sum\\mary."""
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 5 | def double_quotes_backslash_raw():
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
bom.py:1:1: D300 Use triple double quotes `"""`
|
||||
bom.py:1:1: D300 [*] Use triple double quotes `"""`
|
||||
|
|
||||
1 | ''' SAM macro definitions '''
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
||||
|
|
||||
= help: Convert to triple double quotes
|
||||
|
||||
ℹ Fix
|
||||
1 |-''' SAM macro definitions '''
|
||||
1 |+""" SAM macro definitions """
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ mod tests {
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_16.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_17.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_18.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_19.py"))]
|
||||
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
|
||||
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
|
||||
#[test_case(Rule::LateFutureImport, Path::new("F404.py"))]
|
||||
@@ -135,6 +136,7 @@ mod tests {
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_17.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_18.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_19.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_20.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F821_20.py:3:24: F821 Undefined name `Record`
|
||||
|
|
||||
1 | """Test lazy evaluation of type alias values."""
|
||||
2 |
|
||||
3 | type RecordCallback[R: Record] = Callable[[R], None]
|
||||
| ^^^^^^ F821
|
||||
4 |
|
||||
5 | from collections.abc import Callable
|
||||
|
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings) -> bool {
|
||||
pub(super) fn in_dunder_method(
|
||||
dunder_name: &str,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
let scope = semantic.current_scope();
|
||||
let ScopeKind::Function(ast::StmtFunctionDef {
|
||||
name,
|
||||
@@ -32,7 +36,7 @@ pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &LinterSettings
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if name != "__init__" {
|
||||
if name != dunder_name {
|
||||
return false;
|
||||
}
|
||||
let Some(parent) = semantic.first_non_type_parent_scope(scope) else {
|
||||
|
||||
@@ -133,8 +133,11 @@ mod tests {
|
||||
Rule::SubprocessRunWithoutCheck,
|
||||
Path::new("subprocess_run_without_check.py")
|
||||
)]
|
||||
#[test_case(Rule::UnspecifiedEncoding, Path::new("unspecified_encoding.py"))]
|
||||
#[test_case(Rule::BadDunderMethodName, Path::new("bad_dunder_method_name.py"))]
|
||||
#[test_case(Rule::NoSelfUse, Path::new("no_self_use.py"))]
|
||||
#[test_case(Rule::MisplacedBareRaise, Path::new("misplaced_bare_raise.py"))]
|
||||
#[test_case(Rule::LiteralMembership, Path::new("literal_membership.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -228,6 +231,22 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_boolean_expressions() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/too_many_boolean_expressions.py"),
|
||||
&LinterSettings {
|
||||
pylint: pylint::settings::Settings {
|
||||
max_bool_expr: 5,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::TooManyBooleanExpressions)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_statements() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn bad_dunder_method_name(checker: &mut Checker, class_body: &[Stmt])
|
||||
.iter()
|
||||
.filter_map(ruff_python_ast::Stmt::as_function_def_stmt)
|
||||
.filter(|method| {
|
||||
if is_known_dunder_method(&method.name) {
|
||||
if is_known_dunder_method(&method.name) || matches!(method.name.as_str(), "_") {
|
||||
return false;
|
||||
}
|
||||
method.name.starts_with('_') && method.name.ends_with('_')
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use ast::ExprContext;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -38,7 +36,7 @@ impl AlwaysFixableViolation for IterationOverSet {
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Use a sequence type instead of a `set` when iterating over values")
|
||||
format!("Convert to `tuple`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +52,14 @@ pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
|
||||
|
||||
let mut diagnostic = Diagnostic::new(IterationOverSet, expr.range());
|
||||
|
||||
let tuple = checker.generator().expr(&Expr::Tuple(ast::ExprTuple {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
}));
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
format!("({tuple})"),
|
||||
expr.range(),
|
||||
)));
|
||||
let tuple = if let [elt] = elts.as_slice() {
|
||||
let elt = checker.locator().slice(elt);
|
||||
format!("({elt},)")
|
||||
} else {
|
||||
let set = checker.locator().slice(expr);
|
||||
format!("({})", &set[1..set.len() - 1])
|
||||
};
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(tuple, expr.range())));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for membership tests on `list` and `tuple` literals.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When testing for membership in a static sequence, prefer a `set` literal
|
||||
/// over a `list` or `tuple`, as Python optimizes `set` membership tests.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// 1 in [1, 2, 3]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// 1 in {1, 2, 3}
|
||||
/// ```
|
||||
/// ## References
|
||||
/// - [What’s New In Python 3.2](https://docs.python.org/3/whatsnew/3.2.html#optimizations)
|
||||
#[violation]
|
||||
pub struct LiteralMembership;
|
||||
|
||||
impl AlwaysFixableViolation for LiteralMembership {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use a `set` literal when testing for membership")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Convert to `set`")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR6201
|
||||
pub(crate) fn literal_membership(checker: &mut Checker, compare: &ast::ExprCompare) {
|
||||
let [op] = compare.ops.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(op, CmpOp::In | CmpOp::NotIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [right] = compare.comparators.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(right, Expr::List(_) | Expr::Tuple(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(LiteralMembership, right.range());
|
||||
|
||||
let literal = checker.locator().slice(right);
|
||||
let set = format!("{{{}}}", &literal[1..literal.len() - 1]);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(set, right.range())));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for bare `raise` statements outside of exception handlers.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// A bare `raise` statement without an exception object will re-raise the last
|
||||
/// exception that was active in the current scope, and is typically used
|
||||
/// within an exception handler to re-raise the caught exception.
|
||||
///
|
||||
/// If a bare `raise` is used outside of an exception handler, it will generate
|
||||
/// an error due to the lack of an active exception.
|
||||
///
|
||||
/// Note that a bare `raise` within a `finally` block will work in some cases
|
||||
/// (namely, when the exception is raised within the `try` block), but should
|
||||
/// be avoided as it can lead to confusing behavior.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import Any
|
||||
///
|
||||
///
|
||||
/// def is_some(obj: Any) -> bool:
|
||||
/// if obj is None:
|
||||
/// raise
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Any
|
||||
///
|
||||
///
|
||||
/// def is_some(obj: Any) -> bool:
|
||||
/// if obj is None:
|
||||
/// raise ValueError("`obj` cannot be `None`")
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MisplacedBareRaise;
|
||||
|
||||
impl Violation for MisplacedBareRaise {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Bare `raise` statement is not inside an exception handler")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLE0704
|
||||
pub(crate) fn misplaced_bare_raise(checker: &mut Checker, raise: &ast::StmtRaise) {
|
||||
if raise.exc.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if checker.semantic().in_exception_handler() {
|
||||
return;
|
||||
}
|
||||
|
||||
if in_dunder_method("__exit__", checker.semantic(), checker.settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(MisplacedBareRaise, raise.range()));
|
||||
}
|
||||
@@ -24,10 +24,12 @@ pub(crate) use invalid_envvar_value::*;
|
||||
pub(crate) use invalid_str_return::*;
|
||||
pub(crate) use invalid_string_characters::*;
|
||||
pub(crate) use iteration_over_set::*;
|
||||
pub(crate) use literal_membership::*;
|
||||
pub(crate) use load_before_global_declaration::*;
|
||||
pub(crate) use logging::*;
|
||||
pub(crate) use magic_value_comparison::*;
|
||||
pub(crate) use manual_import_from::*;
|
||||
pub(crate) use misplaced_bare_raise::*;
|
||||
pub(crate) use named_expr_without_context::*;
|
||||
pub(crate) use nested_min_max::*;
|
||||
pub(crate) use no_self_use::*;
|
||||
@@ -43,6 +45,7 @@ pub(crate) use subprocess_popen_preexec_fn::*;
|
||||
pub(crate) use subprocess_run_without_check::*;
|
||||
pub(crate) use sys_exit_alias::*;
|
||||
pub(crate) use too_many_arguments::*;
|
||||
pub(crate) use too_many_boolean_expressions::*;
|
||||
pub(crate) use too_many_branches::*;
|
||||
pub(crate) use too_many_public_methods::*;
|
||||
pub(crate) use too_many_return_statements::*;
|
||||
@@ -52,6 +55,7 @@ pub(crate) use type_name_incorrect_variance::*;
|
||||
pub(crate) use type_param_name_mismatch::*;
|
||||
pub(crate) use unexpected_special_method_signature::*;
|
||||
pub(crate) use unnecessary_direct_lambda_call::*;
|
||||
pub(crate) use unspecified_encoding::*;
|
||||
pub(crate) use useless_else_on_loop::*;
|
||||
pub(crate) use useless_import_alias::*;
|
||||
pub(crate) use useless_return::*;
|
||||
@@ -84,10 +88,12 @@ mod invalid_envvar_value;
|
||||
mod invalid_str_return;
|
||||
mod invalid_string_characters;
|
||||
mod iteration_over_set;
|
||||
mod literal_membership;
|
||||
mod load_before_global_declaration;
|
||||
mod logging;
|
||||
mod magic_value_comparison;
|
||||
mod manual_import_from;
|
||||
mod misplaced_bare_raise;
|
||||
mod named_expr_without_context;
|
||||
mod nested_min_max;
|
||||
mod no_self_use;
|
||||
@@ -103,6 +109,7 @@ mod subprocess_popen_preexec_fn;
|
||||
mod subprocess_run_without_check;
|
||||
mod sys_exit_alias;
|
||||
mod too_many_arguments;
|
||||
mod too_many_boolean_expressions;
|
||||
mod too_many_branches;
|
||||
mod too_many_public_methods;
|
||||
mod too_many_return_statements;
|
||||
@@ -112,6 +119,7 @@ mod type_name_incorrect_variance;
|
||||
mod type_param_name_mismatch;
|
||||
mod unexpected_special_method_signature;
|
||||
mod unnecessary_direct_lambda_call;
|
||||
mod unspecified_encoding;
|
||||
mod useless_else_on_loop;
|
||||
mod useless_import_alias;
|
||||
mod useless_return;
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::in_dunder_init;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `__init__` methods that return values.
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn return_in_init(checker: &mut Checker, stmt: &Stmt) {
|
||||
}
|
||||
}
|
||||
|
||||
if in_dunder_init(checker.semantic(), checker.settings) {
|
||||
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(ReturnInInit, stmt.range()));
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use ast::{Expr, StmtIf};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for too many Boolean expressions in an `if` statement.
|
||||
///
|
||||
/// By default, this rule allows up to 5 expressions. This can be configured
|
||||
/// using the [`pylint.max-bool-expr`] option.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `if` statements with many Boolean expressions are harder to understand
|
||||
/// and maintain. Consider assigning the result of the Boolean expression,
|
||||
/// or any of its sub-expressions, to a variable.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// if a and b and c and d and e and f and g and h:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `pylint.max-bool-expr`
|
||||
#[violation]
|
||||
pub struct TooManyBooleanExpressions {
|
||||
expressions: usize,
|
||||
max_expressions: usize,
|
||||
}
|
||||
|
||||
impl Violation for TooManyBooleanExpressions {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TooManyBooleanExpressions {
|
||||
expressions,
|
||||
max_expressions,
|
||||
} = self;
|
||||
format!("Too many Boolean expressions ({expressions} > {max_expressions})")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR0916
|
||||
pub(crate) fn too_many_boolean_expressions(checker: &mut Checker, stmt: &StmtIf) {
|
||||
if let Some(bool_op) = stmt.test.as_bool_op_expr() {
|
||||
let expressions = count_bools(bool_op);
|
||||
if expressions > checker.settings.pylint.max_bool_expr {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyBooleanExpressions {
|
||||
expressions,
|
||||
max_expressions: checker.settings.pylint.max_bool_expr,
|
||||
},
|
||||
bool_op.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for elif in &stmt.elif_else_clauses {
|
||||
if let Some(bool_op) = elif.test.as_ref().and_then(Expr::as_bool_op_expr) {
|
||||
let expressions = count_bools(bool_op);
|
||||
if expressions > checker.settings.pylint.max_bool_expr {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyBooleanExpressions {
|
||||
expressions,
|
||||
max_expressions: checker.settings.pylint.max_bool_expr,
|
||||
},
|
||||
bool_op.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Count the number of Boolean expressions in a `bool_op` expression.
|
||||
fn count_bools(bool_op: &ast::ExprBoolOp) -> usize {
|
||||
bool_op
|
||||
.values
|
||||
.iter()
|
||||
.map(|expr| {
|
||||
if let Expr::BoolOp(bool_op) = expr {
|
||||
count_bools(bool_op)
|
||||
} else {
|
||||
1
|
||||
}
|
||||
})
|
||||
.sum::<usize>()
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::call_path::{format_call_path, CallPath};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `open` and related calls without an explicit `encoding`
|
||||
/// argument.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `open` in text mode without an explicit encoding can lead to
|
||||
/// non-portable code, with differing behavior across platforms.
|
||||
///
|
||||
/// Instead, consider using the `encoding` parameter to enforce a specific
|
||||
/// encoding.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// open("file.txt")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// open("file.txt", encoding="utf-8")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
#[violation]
|
||||
pub struct UnspecifiedEncoding {
|
||||
function_name: String,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl Violation for UnspecifiedEncoding {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnspecifiedEncoding {
|
||||
function_name,
|
||||
mode,
|
||||
} = self;
|
||||
|
||||
match mode {
|
||||
Mode::Supported => {
|
||||
format!("`{function_name}` in text mode without explicit `encoding` argument")
|
||||
}
|
||||
Mode::Unsupported => {
|
||||
format!("`{function_name}` without explicit `encoding` argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PLW1514
|
||||
pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
let Some((function_name, mode)) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(&call.func)
|
||||
.filter(|call_path| is_violation(call, call_path))
|
||||
.map(|call_path| {
|
||||
(
|
||||
format_call_path(call_path.as_slice()),
|
||||
Mode::from(&call_path),
|
||||
)
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnspecifiedEncoding {
|
||||
function_name,
|
||||
mode,
|
||||
},
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression is a string literal containing a `b` character.
|
||||
fn is_binary_mode(expr: &ast::Expr) -> Option<bool> {
|
||||
Some(expr.as_constant_expr()?.value.as_str()?.value.contains('b'))
|
||||
}
|
||||
|
||||
/// Returns `true` if the given call lacks an explicit `encoding`.
|
||||
fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool {
|
||||
// If we have something like `*args`, which might contain the encoding argument, abort.
|
||||
if call
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.any(ruff_python_ast::Expr::is_starred_expr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If we have something like `**kwargs`, which might contain the encoding argument, abort.
|
||||
if call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
match call_path.as_slice() {
|
||||
["" | "codecs" | "_io", "open"] => {
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
||||
if is_binary_mode(mode_arg).unwrap_or(true) {
|
||||
// binary mode or unknown mode is no violation
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// else mode not specified, defaults to text mode
|
||||
call.arguments.find_argument("encoding", 3).is_none()
|
||||
}
|
||||
["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => {
|
||||
let mode_pos = usize::from(call_path[1] == "SpooledTemporaryFile");
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", mode_pos) {
|
||||
if is_binary_mode(mode_arg).unwrap_or(true) {
|
||||
// binary mode or unknown mode is no violation
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// defaults to binary mode
|
||||
return false;
|
||||
}
|
||||
call.arguments
|
||||
.find_argument("encoding", mode_pos + 2)
|
||||
.is_none()
|
||||
}
|
||||
["io" | "_io", "TextIOWrapper"] => call.arguments.find_argument("encoding", 1).is_none(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
/// The call supports a `mode` argument.
|
||||
Supported,
|
||||
/// The call does not support a `mode` argument.
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl From<&CallPath<'_>> for Mode {
|
||||
fn from(value: &CallPath<'_>) -> Self {
|
||||
match value.as_slice() {
|
||||
["" | "codecs" | "_io", "open"] => Mode::Supported,
|
||||
["tempfile", "TemporaryFile" | "NamedTemporaryFile" | "SpooledTemporaryFile"] => {
|
||||
Mode::Supported
|
||||
}
|
||||
["io" | "_io", "TextIOWrapper"] => Mode::Unsupported,
|
||||
_ => Mode::Unsupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::in_dunder_init;
|
||||
use crate::rules::pylint::helpers::in_dunder_method;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `__init__` methods that are turned into generators by the
|
||||
@@ -40,7 +40,7 @@ impl Violation for YieldInInit {
|
||||
|
||||
/// PLE0100
|
||||
pub(crate) fn yield_in_init(checker: &mut Checker, expr: &Expr) {
|
||||
if in_dunder_init(checker.semantic(), checker.settings) {
|
||||
if in_dunder_method("__init__", checker.semantic(), checker.settings) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(YieldInInit, expr.range()));
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct Settings {
|
||||
pub allow_magic_value_types: Vec<ConstantType>,
|
||||
pub max_args: usize,
|
||||
pub max_returns: usize,
|
||||
pub max_bool_expr: usize,
|
||||
pub max_branches: usize,
|
||||
pub max_statements: usize,
|
||||
pub max_public_methods: usize,
|
||||
@@ -51,6 +52,7 @@ impl Default for Settings {
|
||||
allow_magic_value_types: vec![ConstantType::Str, ConstantType::Bytes],
|
||||
max_args: 5,
|
||||
max_returns: 6,
|
||||
max_bool_expr: 5,
|
||||
max_branches: 12,
|
||||
max_statements: 50,
|
||||
max_public_methods: 20,
|
||||
|
||||
@@ -9,7 +9,7 @@ iteration_over_set.py:3:13: PLC0208 [*] Use a sequence type instead of a `set` w
|
||||
| ^^^ PLC0208
|
||||
4 | print(f"I can count to {item}!")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
@@ -28,7 +28,7 @@ iteration_over_set.py:6:13: PLC0208 [*] Use a sequence type instead of a `set` w
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||
7 | print(f"I like {item}.")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
3 3 | for item in {1}:
|
||||
@@ -38,90 +38,136 @@ iteration_over_set.py:6:13: PLC0208 [*] Use a sequence type instead of a `set` w
|
||||
6 |+for item in ("apples", "lemons", "water"): # flags in-line set literals
|
||||
7 7 | print(f"I like {item}.")
|
||||
8 8 |
|
||||
9 9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
9 9 | for item in {1,}:
|
||||
|
||||
iteration_over_set.py:9:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:9:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
7 | print(f"I like {item}.")
|
||||
8 |
|
||||
9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
10 |
|
||||
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
9 | for item in {1,}:
|
||||
| ^^^^ PLC0208
|
||||
10 | print(f"I can count to {item}!")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
6 6 | for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
7 7 | print(f"I like {item}.")
|
||||
8 8 |
|
||||
9 |-numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
9 |+numbers_list = [i for i in (1, 2, 3)] # flags sets in list comprehensions
|
||||
10 10 |
|
||||
11 11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
12 12 |
|
||||
9 |-for item in {1,}:
|
||||
9 |+for item in (1,):
|
||||
10 10 | print(f"I can count to {item}!")
|
||||
11 11 |
|
||||
12 12 | for item in {
|
||||
|
||||
iteration_over_set.py:11:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:12:13: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
10 |
|
||||
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
12 |
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
10 | print(f"I can count to {item}!")
|
||||
11 |
|
||||
12 | for item in {
|
||||
| _____________^
|
||||
13 | | "apples", "lemons", "water"
|
||||
14 | | }: # flags in-line set literals
|
||||
| |_^ PLC0208
|
||||
15 | print(f"I like {item}.")
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
9 9 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
10 10 |
|
||||
11 |-numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
11 |+numbers_set = {i for i in (1, 2, 3)} # flags sets in set comprehensions
|
||||
12 12 |
|
||||
13 13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
14 14 |
|
||||
|
||||
iteration_over_set.py:13:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
12 |
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
14 |
|
||||
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
|
||||
ℹ Fix
|
||||
10 10 |
|
||||
11 11 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
12 12 |
|
||||
13 |-numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
13 |+numbers_dict = {str(i): i for i in (1, 2, 3)} # flags sets in dict comprehensions
|
||||
14 14 |
|
||||
15 15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
9 9 | for item in {1,}:
|
||||
10 10 | print(f"I can count to {item}!")
|
||||
11 11 |
|
||||
12 |-for item in {
|
||||
12 |+for item in (
|
||||
13 13 | "apples", "lemons", "water"
|
||||
14 |-}: # flags in-line set literals
|
||||
14 |+): # flags in-line set literals
|
||||
15 15 | print(f"I like {item}.")
|
||||
16 16 |
|
||||
17 17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
iteration_over_set.py:15:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:17:28: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
14 |
|
||||
15 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
15 | print(f"I like {item}.")
|
||||
16 |
|
||||
17 | # Non-errors
|
||||
17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
18 |
|
||||
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
|
||||
= help: Use a sequence type instead of a `set` when iterating over values
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
12 12 |
|
||||
13 13 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
14 14 |
|
||||
15 |-numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
15 |+numbers_gen = (i for i in (1, 2, 3)) # flags sets in generator expressions
|
||||
14 14 | }: # flags in-line set literals
|
||||
15 15 | print(f"I like {item}.")
|
||||
16 16 |
|
||||
17 17 | # Non-errors
|
||||
17 |-numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
17 |+numbers_list = [i for i in (1, 2, 3)] # flags sets in list comprehensions
|
||||
18 18 |
|
||||
19 19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
20 20 |
|
||||
|
||||
iteration_over_set.py:19:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
18 |
|
||||
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
20 |
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
16 16 |
|
||||
17 17 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
18 18 |
|
||||
19 |-numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
19 |+numbers_set = {i for i in (1, 2, 3)} # flags sets in set comprehensions
|
||||
20 20 |
|
||||
21 21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
22 22 |
|
||||
|
||||
iteration_over_set.py:21:36: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
20 |
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
22 |
|
||||
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
18 18 |
|
||||
19 19 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
20 20 |
|
||||
21 |-numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
21 |+numbers_dict = {str(i): i for i in (1, 2, 3)} # flags sets in dict comprehensions
|
||||
22 22 |
|
||||
23 23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
24 24 |
|
||||
|
||||
iteration_over_set.py:23:27: PLC0208 [*] Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
22 |
|
||||
23 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
24 |
|
||||
25 | # Non-errors
|
||||
|
|
||||
= help: Convert to `tuple`
|
||||
|
||||
ℹ Fix
|
||||
20 20 |
|
||||
21 21 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
22 22 |
|
||||
23 |-numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
23 |+numbers_gen = (i for i in (1, 2, 3)) # flags sets in generator expressions
|
||||
24 24 |
|
||||
25 25 | # Non-errors
|
||||
26 26 |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
misplaced_bare_raise.py:30:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
29 | try:
|
||||
30 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
31 | except Exception:
|
||||
32 | pass
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:36:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
34 | def f():
|
||||
35 | try:
|
||||
36 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
37 | except Exception:
|
||||
38 | pass
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:41:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
40 | def g():
|
||||
41 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
42 |
|
||||
43 | def h():
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:47:17: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
45 | if True:
|
||||
46 | def i():
|
||||
47 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
48 | except Exception:
|
||||
49 | pass
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:50:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
48 | except Exception:
|
||||
49 | pass
|
||||
50 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
51 |
|
||||
52 | raise # [misplaced-bare-raise]
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:52:1: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
50 | raise # [misplaced-bare-raise]
|
||||
51 |
|
||||
52 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
53 |
|
||||
54 | try:
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:58:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
56 | except:
|
||||
57 | def i():
|
||||
58 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
59 |
|
||||
60 | try:
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:64:9: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
62 | except:
|
||||
63 | class C:
|
||||
64 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
65 |
|
||||
66 | try:
|
||||
|
|
||||
|
||||
misplaced_bare_raise.py:71:5: PLE0704 Bare `raise` statement is not inside an exception handler
|
||||
|
|
||||
69 | pass
|
||||
70 | finally:
|
||||
71 | raise # [misplaced-bare-raise]
|
||||
| ^^^^^ PLE0704
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
literal_membership.py:2:6: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
1 | # Errors
|
||||
2 | 1 in [1, 2, 3]
|
||||
| ^^^^^^^^^ PLR6201
|
||||
3 | 1 in (1, 2, 3)
|
||||
4 | 1 in (
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
2 |-1 in [1, 2, 3]
|
||||
2 |+1 in {1, 2, 3}
|
||||
3 3 | 1 in (1, 2, 3)
|
||||
4 4 | 1 in (
|
||||
5 5 | 1, 2, 3
|
||||
|
||||
literal_membership.py:3:6: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
1 | # Errors
|
||||
2 | 1 in [1, 2, 3]
|
||||
3 | 1 in (1, 2, 3)
|
||||
| ^^^^^^^^^ PLR6201
|
||||
4 | 1 in (
|
||||
5 | 1, 2, 3
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
2 2 | 1 in [1, 2, 3]
|
||||
3 |-1 in (1, 2, 3)
|
||||
3 |+1 in {1, 2, 3}
|
||||
4 4 | 1 in (
|
||||
5 5 | 1, 2, 3
|
||||
6 6 | )
|
||||
|
||||
literal_membership.py:4:6: PLR6201 [*] Use a `set` literal when testing for membership
|
||||
|
|
||||
2 | 1 in [1, 2, 3]
|
||||
3 | 1 in (1, 2, 3)
|
||||
4 | 1 in (
|
||||
| ______^
|
||||
5 | | 1, 2, 3
|
||||
6 | | )
|
||||
| |_^ PLR6201
|
||||
7 |
|
||||
8 | # OK
|
||||
|
|
||||
= help: Convert to `set`
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Errors
|
||||
2 2 | 1 in [1, 2, 3]
|
||||
3 3 | 1 in (1, 2, 3)
|
||||
4 |-1 in (
|
||||
4 |+1 in {
|
||||
5 5 | 1, 2, 3
|
||||
6 |-)
|
||||
6 |+}
|
||||
7 7 |
|
||||
8 8 | # OK
|
||||
9 9 | fruits = ["cherry", "grapes"]
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
unspecified_encoding.py:8:1: PLW1514 `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
| ^^^^ PLW1514
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
|
|
||||
|
||||
unspecified_encoding.py:9:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
| ^^^^^^^^^^^^^^^^ PLW1514
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:10:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
| ^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:11:1: PLW1514 `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:12:1: PLW1514 `tempfile.TemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:13:1: PLW1514 `codecs.open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
| ^^^^^^^^^^^ PLW1514
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
|
||||
unspecified_encoding.py:14:1: PLW1514 `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
15 |
|
||||
16 | # Non-errors.
|
||||
|
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
too_many_boolean_expressions.py:11:6: PLR0916 Too many Boolean expressions (6 > 5)
|
||||
|
|
||||
9 | elif (a and b) and c and d and e:
|
||||
10 | ...
|
||||
11 | elif (a and b) and c and d and e and f:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
12 | ...
|
||||
13 | elif (a and b) and c and d and e and f and g:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:13:6: PLR0916 Too many Boolean expressions (7 > 5)
|
||||
|
|
||||
11 | elif (a and b) and c and d and e and f:
|
||||
12 | ...
|
||||
13 | elif (a and b) and c and d and e and f and g:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
14 | ...
|
||||
15 | elif (a and b) and c and d and e and f and g and h:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:15:6: PLR0916 Too many Boolean expressions (8 > 5)
|
||||
|
|
||||
13 | elif (a and b) and c and d and e and f and g:
|
||||
14 | ...
|
||||
15 | elif (a and b) and c and d and e and f and g and h:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
16 | ...
|
||||
17 | elif (a and b) and c and d and e and f and g and h and i:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:17:6: PLR0916 Too many Boolean expressions (9 > 5)
|
||||
|
|
||||
15 | elif (a and b) and c and d and e and f and g and h:
|
||||
16 | ...
|
||||
17 | elif (a and b) and c and d and e and f and g and h and i:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
18 | ...
|
||||
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:19:6: PLR0916 Too many Boolean expressions (10 > 5)
|
||||
|
|
||||
17 | elif (a and b) and c and d and e and f and g and h and i:
|
||||
18 | ...
|
||||
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
20 | ...
|
||||
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:21:6: PLR0916 Too many Boolean expressions (11 > 5)
|
||||
|
|
||||
19 | elif (a and b) and c and d and e and f and g and h and i and j:
|
||||
20 | ...
|
||||
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
22 | ...
|
||||
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:23:6: PLR0916 Too many Boolean expressions (12 > 5)
|
||||
|
|
||||
21 | elif (a and b) and c and d and e and f and g and h and i and j and k:
|
||||
22 | ...
|
||||
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
24 | ...
|
||||
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:25:6: PLR0916 Too many Boolean expressions (13 > 5)
|
||||
|
|
||||
23 | elif (a and b) and c and d and e and f and g and h and i and j and k and l:
|
||||
24 | ...
|
||||
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
26 | ...
|
||||
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:27:6: PLR0916 Too many Boolean expressions (14 > 5)
|
||||
|
|
||||
25 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m:
|
||||
26 | ...
|
||||
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
28 | ...
|
||||
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:29:6: PLR0916 Too many Boolean expressions (15 > 5)
|
||||
|
|
||||
27 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n:
|
||||
28 | ...
|
||||
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
30 | ...
|
||||
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:31:6: PLR0916 Too many Boolean expressions (16 > 5)
|
||||
|
|
||||
29 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o:
|
||||
30 | ...
|
||||
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
32 | ...
|
||||
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:33:6: PLR0916 Too many Boolean expressions (17 > 5)
|
||||
|
|
||||
31 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p:
|
||||
32 | ...
|
||||
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
34 | ...
|
||||
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:35:6: PLR0916 Too many Boolean expressions (18 > 5)
|
||||
|
|
||||
33 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q:
|
||||
34 | ...
|
||||
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
36 | ...
|
||||
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:37:6: PLR0916 Too many Boolean expressions (19 > 5)
|
||||
|
|
||||
35 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r:
|
||||
36 | ...
|
||||
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
38 | ...
|
||||
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:39:6: PLR0916 Too many Boolean expressions (20 > 5)
|
||||
|
|
||||
37 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s:
|
||||
38 | ...
|
||||
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
40 | ...
|
||||
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:41:6: PLR0916 Too many Boolean expressions (21 > 5)
|
||||
|
|
||||
39 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t:
|
||||
40 | ...
|
||||
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
42 | ...
|
||||
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:43:6: PLR0916 Too many Boolean expressions (22 > 5)
|
||||
|
|
||||
41 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u:
|
||||
42 | ...
|
||||
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
44 | ...
|
||||
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:45:6: PLR0916 Too many Boolean expressions (23 > 5)
|
||||
|
|
||||
43 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v:
|
||||
44 | ...
|
||||
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
46 | ...
|
||||
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:47:6: PLR0916 Too many Boolean expressions (24 > 5)
|
||||
|
|
||||
45 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w:
|
||||
46 | ...
|
||||
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
48 | ...
|
||||
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:49:6: PLR0916 Too many Boolean expressions (25 > 5)
|
||||
|
|
||||
47 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x:
|
||||
48 | ...
|
||||
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
50 | ...
|
||||
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
|
|
||||
|
||||
too_many_boolean_expressions.py:51:6: PLR0916 Too many Boolean expressions (26 > 5)
|
||||
|
|
||||
49 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y:
|
||||
50 | ...
|
||||
51 | elif (a and b) and c and d and e and f and g and h and i and j and k and l and m and n and o and p and q and r and s and t and u and v and w and x and y and z:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLR0916
|
||||
52 | ...
|
||||
53 | else:
|
||||
|
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use itertools::Itertools;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::node::AstNode;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AstNode;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::rules::{
|
||||
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
|
||||
pycodestyle, pydocstyle, pyflakes, pylint, pyupgrade,
|
||||
};
|
||||
use crate::settings::types::{PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion};
|
||||
use crate::{codes, RuleSelector};
|
||||
|
||||
use super::line_width::{LineLength, TabSize};
|
||||
@@ -38,6 +38,7 @@ pub mod types;
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct LinterSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub project_root: PathBuf,
|
||||
|
||||
pub rules: RuleTable,
|
||||
@@ -131,6 +132,7 @@ impl LinterSettings {
|
||||
|
||||
pub fn new(project_root: &Path) -> Self {
|
||||
Self {
|
||||
exclude: FilePatternSet::default(),
|
||||
target_version: PythonVersion::default(),
|
||||
project_root: project_root.to_path_buf(),
|
||||
rules: DEFAULT_SELECTORS
|
||||
|
||||
@@ -16,6 +16,7 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
serde_derive_internals = "0.29.0"
|
||||
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use proc_macro2::TokenTree;
|
||||
use quote::{quote, quote_spanned};
|
||||
use serde_derive_internals::Ctxt;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Comma;
|
||||
use syn::{
|
||||
AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, ExprLit, Field,
|
||||
Fields, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Token, Type, TypePath,
|
||||
Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
|
||||
};
|
||||
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
@@ -38,25 +38,14 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
|
||||
.any(|attr| attr.path().is_ident("option_group"))
|
||||
{
|
||||
output.push(handle_option_group(field)?);
|
||||
} else if let Some(serde) = field
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("serde"))
|
||||
{
|
||||
} else if let Type::Path(ty) = &field.ty {
|
||||
let serde_field = serde_field_metadata(field)?;
|
||||
|
||||
// If a field has the `serde(flatten)` attribute, flatten the options into the parent
|
||||
// by calling `Type::record` instead of `visitor.visit_set`
|
||||
if let (Type::Path(ty), Meta::List(list)) = (&field.ty, &serde.meta) {
|
||||
for token in list.tokens.clone() {
|
||||
if let TokenTree::Ident(ident) = token {
|
||||
if ident == "flatten" {
|
||||
let ty_name = ty.path.require_ident()?;
|
||||
output.push(quote_spanned!(
|
||||
ident.span() => (#ty_name::record(visit))
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if serde_field.flatten() {
|
||||
let ty_name = ty.path.require_ident()?;
|
||||
output.push(quote_spanned!(ident.span() => (#ty_name::record(visit))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,6 +182,10 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
|
||||
} = attr.parse_args::<FieldAttributes>()?;
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
let serde_field = serde_field_metadata(field)?;
|
||||
let attributed_aliases = serde_field.aliases();
|
||||
let aliases = quote!(BTreeSet::from_iter([#(#attributed_aliases),*]));
|
||||
|
||||
Ok(quote_spanned!(
|
||||
ident.span() => {
|
||||
visit.record_field(#kebab_name, crate::options_base::OptionField{
|
||||
@@ -200,6 +193,7 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
|
||||
default: &#default,
|
||||
value_type: &#value_type,
|
||||
example: &#example,
|
||||
aliases: #aliases
|
||||
})
|
||||
}
|
||||
))
|
||||
@@ -248,3 +242,17 @@ fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
|
||||
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
|
||||
}
|
||||
}
|
||||
|
||||
fn serde_field_metadata(field: &Field) -> syn::Result<serde_derive_internals::attr::Field> {
|
||||
let context = Ctxt::new();
|
||||
let field = serde_derive_internals::attr::Field::from_ast(
|
||||
&context,
|
||||
0,
|
||||
field,
|
||||
None,
|
||||
&serde_derive_internals::attr::Default::Default,
|
||||
);
|
||||
context.check()?;
|
||||
|
||||
Ok(field)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
|
||||
};
|
||||
|
||||
// Map from: linter (e.g., `Flake8Bugbear`) to rule code (e.g.,`"002"`) to rule data (e.g.,
|
||||
// `(Rule::UnaryPrefixIncrement, RuleGroup::Unspecified, vec![])`).
|
||||
// `(Rule::UnaryPrefixIncrement, RuleGroup::Stable, vec![])`).
|
||||
let mut linter_to_rules: BTreeMap<Ident, BTreeMap<String, Rule>> = BTreeMap::new();
|
||||
|
||||
for arm in arms {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::node::AnyNodeRef;
|
||||
use crate::AnyNodeRef;
|
||||
use crate::{self as ast, Expr};
|
||||
|
||||
/// Unowned pendant to [`ast::Expr`] that stores a reference instead of a owned value.
|
||||
|
||||
@@ -8,9 +8,9 @@ use smallvec::SmallVec;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::call_path::CallPath;
|
||||
use crate::node::AnyNodeRef;
|
||||
use crate::parenthesize::parenthesized_range;
|
||||
use crate::statement_visitor::{walk_body, walk_stmt, StatementVisitor};
|
||||
use crate::AnyNodeRef;
|
||||
use crate::{
|
||||
self as ast, Arguments, CmpOp, Constant, ExceptHandler, Expr, MatchCase, Pattern, Stmt,
|
||||
TypeParam,
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::Path;
|
||||
|
||||
pub use expression::*;
|
||||
pub use int::*;
|
||||
pub use node::{AnyNode, AnyNodeRef, AstNode, NodeKind};
|
||||
pub use nodes::*;
|
||||
|
||||
pub mod all;
|
||||
@@ -14,7 +15,7 @@ pub mod helpers;
|
||||
pub mod identifier;
|
||||
pub mod imports;
|
||||
mod int;
|
||||
pub mod node;
|
||||
mod node;
|
||||
mod nodes;
|
||||
pub mod parenthesize;
|
||||
pub mod relocate;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_python_trivia::{BackwardsTokenizer, CommentRanges, SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
|
||||
use crate::node::AnyNodeRef;
|
||||
use crate::AnyNodeRef;
|
||||
use crate::ExpressionRef;
|
||||
|
||||
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::node::{AnyNodeRef, AstNode};
|
||||
use crate::{
|
||||
Alias, Arguments, BoolOp, CmpOp, Comprehension, Constant, Decorator, ElifElseClause,
|
||||
ExceptHandler, Expr, Keyword, MatchCase, Mod, Operator, Parameter, ParameterWithDefault,
|
||||
Parameters, Pattern, PatternArguments, PatternKeyword, Stmt, TypeParam, TypeParams, UnaryOp,
|
||||
WithItem,
|
||||
};
|
||||
use crate::{AnyNodeRef, AstNode};
|
||||
|
||||
/// Visitor that traverses all nodes recursively in pre-order.
|
||||
pub trait PreorderVisitor<'a> {
|
||||
|
||||
@@ -2,12 +2,12 @@ use std::fmt::{Debug, Write};
|
||||
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::visitor::preorder::{
|
||||
walk_alias, walk_comprehension, walk_except_handler, walk_expr, walk_keyword, walk_match_case,
|
||||
walk_module, walk_parameter, walk_parameters, walk_pattern, walk_stmt, walk_type_param,
|
||||
walk_with_item, PreorderVisitor,
|
||||
};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{
|
||||
Alias, BoolOp, CmpOp, Comprehension, Constant, ExceptHandler, Expr, Keyword, MatchCase, Mod,
|
||||
Operator, Parameter, Parameters, Pattern, Stmt, TypeParam, UnaryOp, WithItem,
|
||||
|
||||
@@ -5,12 +5,12 @@ use ruff_python_ast as ast;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::visitor::{
|
||||
walk_alias, walk_comprehension, walk_except_handler, walk_expr, walk_keyword, walk_match_case,
|
||||
walk_parameter, walk_parameters, walk_pattern, walk_stmt, walk_type_param, walk_with_item,
|
||||
Visitor,
|
||||
};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{
|
||||
Alias, BoolOp, CmpOp, Comprehension, ExceptHandler, Expr, Keyword, MatchCase, Operator,
|
||||
Parameter, Parameters, Pattern, Stmt, TypeParam, UnaryOp, WithItem,
|
||||
|
||||
@@ -150,7 +150,7 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
N: Ranged,
|
||||
Separator: Format<PyFormatContext<'ast>>,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
self.result = self.result.and_then(|()| {
|
||||
if self.entries.is_one_or_more() {
|
||||
write!(self.fmt, [token(","), separator])?;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
}
|
||||
|
||||
pub(crate) fn finish(&mut self) -> FormatResult<()> {
|
||||
self.result.and_then(|_| {
|
||||
self.result.and_then(|()| {
|
||||
if let Some(last_end) = self.entries.position() {
|
||||
let magic_trailing_comma = has_magic_trailing_comma(
|
||||
TextRange::new(last_end, self.sequence_end),
|
||||
|
||||
@@ -180,7 +180,7 @@ mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_ast::node::AnyNode;
|
||||
use ruff_python_ast::AnyNode;
|
||||
use ruff_python_ast::{StmtBreak, StmtContinue};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode};
|
||||
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_ast::{AnyNodeRef, AstNode};
|
||||
use ruff_python_trivia::{
|
||||
is_pragma_comment, lines_after, lines_after_ignoring_trivia, lines_before,
|
||||
};
|
||||
|
||||
@@ -96,8 +96,8 @@ pub(crate) use format::{
|
||||
leading_alternate_branch_comments, leading_comments, leading_node_comments, trailing_comments,
|
||||
};
|
||||
use ruff_formatter::{SourceCode, SourceCodeSlice};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::visitor::preorder::{PreorderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::Mod;
|
||||
use ruff_python_trivia::{CommentRanges, PythonWhitespace};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
@@ -52,7 +52,7 @@ impl<'a> From<AnyNodeRef<'a>> for NodeRefEqualityKey<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::comments::node_key::NodeRefEqualityKey;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::StmtContinue;
|
||||
use ruff_text_size::TextRange;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr, MatchCase, ModModule, Parameters};
|
||||
use ruff_python_trivia::{
|
||||
find_only_token_in_range, indentation_at_offset, BackwardsTokenizer, CommentRanges,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||
use std::iter::Peekable;
|
||||
|
||||
use ruff_formatter::{SourceCode, SourceCodeSlice};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Mod, Stmt};
|
||||
// The interface is designed to only export the members relevant for iterating nodes in
|
||||
// pre-order.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::{write, FormatRuleWithOptions};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Constant, Expr, ExprAttribute, ExprConstant};
|
||||
use ruff_python_trivia::{find_only_token_in_range, SimpleTokenKind};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprAwait;
|
||||
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Expr, ExprBinOp};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{BoolOp, ExprBoolOp};
|
||||
|
||||
use crate::expression::binary_like::BinaryLike;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::FormatRuleWithOptions;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
|
||||
use crate::comments::{dangling_comments, SourceComment};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{CmpOp, Expr, ExprCompare};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::FormatRuleWithOptions;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Constant, ExprConstant};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Expr, ExprDict};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprDictComp;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use memchr::memchr2;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use ruff_formatter::FormatResult;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprFString;
|
||||
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprFormattedValue;
|
||||
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user