Compare commits
12 Commits
v0.1.13
...
charlie/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
295dc542b3 | ||
|
|
0c0d3db1b5 | ||
|
|
953d48b7f4 | ||
|
|
0003c730e0 | ||
|
|
b6ce4f5f3a | ||
|
|
e8d7a6dfce | ||
|
|
ef2798f758 | ||
|
|
957a1f35c4 | ||
|
|
009430e034 | ||
|
|
7504bf347b | ||
|
|
fee64b52ba | ||
|
|
3261d16e61 |
@@ -290,6 +290,10 @@ pub struct CheckCommand {
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For
|
||||
/// example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
/// Exit with status code "0", even upon detecting lint violations.
|
||||
#[arg(
|
||||
short,
|
||||
@@ -352,9 +356,6 @@ pub struct CheckCommand {
|
||||
conflicts_with = "watch",
|
||||
)]
|
||||
pub show_settings: bool,
|
||||
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]).
|
||||
#[arg(long, value_delimiter = ',', hide = true)]
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
/// Dev-only argument to show fixes
|
||||
#[arg(long, hide = true)]
|
||||
pub ecosystem_ci: bool,
|
||||
@@ -423,6 +424,10 @@ pub struct FormatCommand {
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For
|
||||
/// example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long, value_enum)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
@@ -571,6 +576,7 @@ impl FormatCommand {
|
||||
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
|
||||
target_version: self.target_version,
|
||||
cache_dir: self.cache_dir,
|
||||
extension: self.extension,
|
||||
|
||||
// Unsupported on the formatter CLI, but required on `Overrides`.
|
||||
..CliOverrides::default()
|
||||
@@ -739,7 +745,7 @@ impl ConfigurationTransformer for CliOverrides {
|
||||
config.target_version = Some(*target_version);
|
||||
}
|
||||
if let Some(extension) = &self.extension {
|
||||
config.lint.extension = Some(extension.clone().into_iter().collect());
|
||||
config.extension = Some(extension.iter().cloned().collect());
|
||||
}
|
||||
|
||||
config
|
||||
|
||||
@@ -106,13 +106,19 @@ pub(crate) fn format(
|
||||
match entry {
|
||||
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 settings = resolver.resolve(path);
|
||||
|
||||
let source_type = match settings.formatter.extension.get(path) {
|
||||
None => match SourceType::from(path) {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
// Ignore any non-Python files.
|
||||
return None;
|
||||
}
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
|
||||
// Ignore files that are excluded from formatting
|
||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||
&& match_exclusion(
|
||||
|
||||
@@ -53,16 +53,23 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
}
|
||||
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
let settings = &resolver.base_settings().formatter;
|
||||
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
let source_type = match path.and_then(|path| settings.extension.get(path)) {
|
||||
None => match path.map(SourceType::from).unwrap_or_default() {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
if mode.is_write() {
|
||||
parrot_stdin()?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
|
||||
// Format the file.
|
||||
match format_source_code(path, &resolver.base_settings().formatter, source_type, mode) {
|
||||
match format_source_code(path, settings, source_type, mode) {
|
||||
Ok(result) => match mode {
|
||||
FormatMode::Write => Ok(ExitStatus::Success),
|
||||
FormatMode::Check | FormatMode::Diff => {
|
||||
|
||||
@@ -35,7 +35,7 @@ pub(crate) fn show_settings(
|
||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||
writeln!(writer, "Settings path: {settings_path:?}")?;
|
||||
}
|
||||
writeln!(writer, "{settings:#?}")?;
|
||||
write!(writer, "{settings}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,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::{ExtensionMapping, UnsafeFixes};
|
||||
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};
|
||||
@@ -179,11 +179,6 @@ impl AddAssign for FixMap {
|
||||
}
|
||||
}
|
||||
|
||||
fn override_source_type(path: Option<&Path>, extension: &ExtensionMapping) -> Option<PySourceType> {
|
||||
let ext = path?.extension()?.to_str()?;
|
||||
extension.get(ext).map(PySourceType::from)
|
||||
}
|
||||
|
||||
/// Lint the source code at the given `Path`.
|
||||
pub(crate) fn lint_path(
|
||||
path: &Path,
|
||||
@@ -228,7 +223,7 @@ pub(crate) fn lint_path(
|
||||
|
||||
debug!("Checking: {}", path.display());
|
||||
|
||||
let source_type = match override_source_type(Some(path), &settings.extension) {
|
||||
let source_type = match settings.extension.get(path).map(PySourceType::from) {
|
||||
Some(source_type) => source_type,
|
||||
None => match SourceType::from(path) {
|
||||
SourceType::Toml(TomlSourceType::Pyproject) => {
|
||||
@@ -398,15 +393,14 @@ pub(crate) fn lint_stdin(
|
||||
fix_mode: flags::FixMode,
|
||||
) -> Result<Diagnostics> {
|
||||
// TODO(charlie): Support `pyproject.toml`.
|
||||
let source_type = if let Some(source_type) =
|
||||
override_source_type(path, &settings.linter.extension)
|
||||
{
|
||||
source_type
|
||||
} else {
|
||||
let SourceType::Python(source_type) = path.map(SourceType::from).unwrap_or_default() else {
|
||||
return Ok(Diagnostics::default());
|
||||
};
|
||||
source_type
|
||||
let source_type = match path.and_then(|path| settings.linter.extension.get(path)) {
|
||||
None => match path.map(SourceType::from).unwrap_or_default() {
|
||||
SourceType::Python(source_type) => source_type,
|
||||
SourceType::Toml(_) => {
|
||||
return Ok(Diagnostics::default());
|
||||
}
|
||||
},
|
||||
Some(language) => PySourceType::from(language),
|
||||
};
|
||||
|
||||
// Extract the sources from the file.
|
||||
|
||||
@@ -1468,3 +1468,73 @@ fn test_notebook_trailing_semicolon() {
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
include = ["*.ipy"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.ipy"),
|
||||
r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x=1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(tempdir.path())
|
||||
.arg("format")
|
||||
.arg("--no-cache")
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.args(["--extension", "ipy:ipynb"])
|
||||
.arg("."), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -436,3 +436,75 @@ ignore = ["D203", "D212"]
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
include = ["*.ipy"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("main.ipy"),
|
||||
r#"
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad6f36d9-4b7d-4562-8d00-f15a0f1fbb6d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
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(["--extension", "ipy:ipynb"])
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.ipy:cell 1:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
29
crates/ruff_cli/tests/show_settings.rs
Normal file
29
crates/ruff_cli/tests/show_settings.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||
("\"[^\\*\"]*/pyproject.toml", "\"[BASEPATH]/pyproject.toml"),
|
||||
("\".*/crates", "\"[BASEPATH]/crates"),
|
||||
("\".*/\\.ruff_cache", "\"[BASEPATH]/.ruff_cache"),
|
||||
("\".*/ruff\"", "\"[BASEPATH]\""),
|
||||
];
|
||||
#[cfg(target_os = "windows")]
|
||||
const TEST_FILTERS: &[(&str, &str)] = &[
|
||||
(r#""[^\*"]*\\pyproject.toml"#, "\"[BASEPATH]/pyproject.toml"),
|
||||
(r#"".*\\crates"#, "\"[BASEPATH]/crates"),
|
||||
(r#"".*\\\.ruff_cache"#, "\"[BASEPATH]/.ruff_cache"),
|
||||
(r#"".*\\ruff""#, "\"[BASEPATH]\""),
|
||||
(r#"\\+(\w\w|\s|")"#, "/$1"),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn display_default_settings() {
|
||||
insta::with_settings!({ filters => TEST_FILTERS.to_vec() }, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--show-settings", "unformatted.py"]).current_dir(Path::new("./resources/test/fixtures")));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
---
|
||||
source: crates/ruff_cli/tests/show_settings.rs
|
||||
info:
|
||||
program: ruff
|
||||
args:
|
||||
- check
|
||||
- "--show-settings"
|
||||
- unformatted.py
|
||||
---
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Resolved settings for: "[BASEPATH]/crates/ruff_cli/resources/test/fixtures/unformatted.py"
|
||||
Settings path: "[BASEPATH]/pyproject.toml"
|
||||
|
||||
# General Settings
|
||||
cache_dir = "[BASEPATH]/.ruff_cache"
|
||||
fix = false
|
||||
fix_only = false
|
||||
output_format = text
|
||||
show_fixes = false
|
||||
show_source = false
|
||||
unsafe_fixes = hint
|
||||
|
||||
# File Resolver Settings
|
||||
file_resolver.exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".ipynb_checkpoints",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pyenv",
|
||||
".pytest_cache",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
".vscode",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"site-packages",
|
||||
"venv",
|
||||
]
|
||||
file_resolver.extend_exclude = [
|
||||
"crates/ruff_linter/resources/",
|
||||
"crates/ruff_python_formatter/resources/",
|
||||
]
|
||||
file_resolver.force_exclude = false
|
||||
file_resolver.include = [
|
||||
"*.py",
|
||||
"*.pyi",
|
||||
"**/pyproject.toml",
|
||||
]
|
||||
file_resolver.extend_include = []
|
||||
file_resolver.respect_gitignore = true
|
||||
file_resolver.project_root = "[BASEPATH]"
|
||||
|
||||
# Linter Settings
|
||||
linter.exclude = []
|
||||
linter.project_root = "[BASEPATH]"
|
||||
linter.rules.enabled = [
|
||||
MultipleImportsOnOneLine,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleStatementsOnOneLineColon,
|
||||
MultipleStatementsOnOneLineSemicolon,
|
||||
UselessSemicolon,
|
||||
NoneComparison,
|
||||
TrueFalseComparison,
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
TypeComparison,
|
||||
BareExcept,
|
||||
LambdaAssignment,
|
||||
AmbiguousVariableName,
|
||||
AmbiguousClassName,
|
||||
AmbiguousFunctionName,
|
||||
IOError,
|
||||
SyntaxError,
|
||||
UnusedImport,
|
||||
ImportShadowedByLoopVar,
|
||||
UndefinedLocalWithImportStar,
|
||||
LateFutureImport,
|
||||
UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage,
|
||||
FutureFeatureNotDefined,
|
||||
PercentFormatInvalidFormat,
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments,
|
||||
PercentFormatMissingArgument,
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch,
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter,
|
||||
StringDotFormatInvalidFormat,
|
||||
StringDotFormatExtraNamedArguments,
|
||||
StringDotFormatExtraPositionalArguments,
|
||||
StringDotFormatMissingArguments,
|
||||
StringDotFormatMixingAutomatic,
|
||||
FStringMissingPlaceholders,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable,
|
||||
ExpressionsInStarAssignment,
|
||||
MultipleStarredExpressions,
|
||||
AssertTuple,
|
||||
IsLiteral,
|
||||
InvalidPrintSyntax,
|
||||
IfTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
DefaultExceptNotLast,
|
||||
ForwardAnnotationSyntaxError,
|
||||
RedefinedWhileUnused,
|
||||
UndefinedName,
|
||||
UndefinedExport,
|
||||
UndefinedLocal,
|
||||
UnusedVariable,
|
||||
UnusedAnnotation,
|
||||
RaiseNotImplemented,
|
||||
]
|
||||
linter.rules.should_fix = [
|
||||
MultipleImportsOnOneLine,
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultipleStatementsOnOneLineColon,
|
||||
MultipleStatementsOnOneLineSemicolon,
|
||||
UselessSemicolon,
|
||||
NoneComparison,
|
||||
TrueFalseComparison,
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
TypeComparison,
|
||||
BareExcept,
|
||||
LambdaAssignment,
|
||||
AmbiguousVariableName,
|
||||
AmbiguousClassName,
|
||||
AmbiguousFunctionName,
|
||||
IOError,
|
||||
SyntaxError,
|
||||
UnusedImport,
|
||||
ImportShadowedByLoopVar,
|
||||
UndefinedLocalWithImportStar,
|
||||
LateFutureImport,
|
||||
UndefinedLocalWithImportStarUsage,
|
||||
UndefinedLocalWithNestedImportStarUsage,
|
||||
FutureFeatureNotDefined,
|
||||
PercentFormatInvalidFormat,
|
||||
PercentFormatExpectedMapping,
|
||||
PercentFormatExpectedSequence,
|
||||
PercentFormatExtraNamedArguments,
|
||||
PercentFormatMissingArgument,
|
||||
PercentFormatMixedPositionalAndNamed,
|
||||
PercentFormatPositionalCountMismatch,
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter,
|
||||
StringDotFormatInvalidFormat,
|
||||
StringDotFormatExtraNamedArguments,
|
||||
StringDotFormatExtraPositionalArguments,
|
||||
StringDotFormatMissingArguments,
|
||||
StringDotFormatMixingAutomatic,
|
||||
FStringMissingPlaceholders,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable,
|
||||
ExpressionsInStarAssignment,
|
||||
MultipleStarredExpressions,
|
||||
AssertTuple,
|
||||
IsLiteral,
|
||||
InvalidPrintSyntax,
|
||||
IfTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
YieldOutsideFunction,
|
||||
ReturnOutsideFunction,
|
||||
DefaultExceptNotLast,
|
||||
ForwardAnnotationSyntaxError,
|
||||
RedefinedWhileUnused,
|
||||
UndefinedName,
|
||||
UndefinedExport,
|
||||
UndefinedLocal,
|
||||
UnusedVariable,
|
||||
UnusedAnnotation,
|
||||
RaiseNotImplemented,
|
||||
]
|
||||
linter.per_file_ignores = {}
|
||||
linter.safety_table.forced_safe = []
|
||||
linter.safety_table.forced_unsafe = []
|
||||
linter.target_version = Py37
|
||||
linter.preview = disabled
|
||||
linter.explicit_preview_rules = false
|
||||
linter.extension.mapping = {}
|
||||
linter.allowed_confusables = []
|
||||
linter.builtins = []
|
||||
linter.dummy_variable_rgx = ^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$
|
||||
linter.external = []
|
||||
linter.ignore_init_module_imports = false
|
||||
linter.logger_objects = []
|
||||
linter.namespace_packages = []
|
||||
linter.src = ["[BASEPATH]"]
|
||||
linter.tab_size = 4
|
||||
linter.line_length = 88
|
||||
linter.task_tags = [
|
||||
TODO,
|
||||
FIXME,
|
||||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
linter.flake8_annotations.suppress_dummy_args = false
|
||||
linter.flake8_annotations.suppress_none_returning = false
|
||||
linter.flake8_annotations.allow_star_arg_any = false
|
||||
linter.flake8_annotations.ignore_fully_untyped = false
|
||||
linter.flake8_bandit.hardcoded_tmp_directory = [
|
||||
/tmp,
|
||||
/var/tmp,
|
||||
/dev/shm,
|
||||
]
|
||||
linter.flake8_bandit.check_typed_exception = false
|
||||
linter.flake8_bugbear.extend_immutable_calls = []
|
||||
linter.flake8_builtins.builtins_ignorelist = []
|
||||
linter.flake8_comprehensions.allow_dict_calls_with_keyword_arguments = false
|
||||
linter.flake8_copyright.notice_rgx = (?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*
|
||||
linter.flake8_copyright.author = none
|
||||
linter.flake8_copyright.min_file_size = 0
|
||||
linter.flake8_errmsg.max_string_length = 0
|
||||
linter.flake8_gettext.functions_names = [
|
||||
_,
|
||||
gettext,
|
||||
ngettext,
|
||||
]
|
||||
linter.flake8_implicit_str_concat.allow_multiline = true
|
||||
linter.flake8_import_conventions.aliases = {"matplotlib": "mpl", "matplotlib.pyplot": "plt", "pandas": "pd", "seaborn": "sns", "tensorflow": "tf", "networkx": "nx", "plotly.express": "px", "polars": "pl", "numpy": "np", "panel": "pn", "pyarrow": "pa", "altair": "alt", "tkinter": "tk", "holoviews": "hv"}
|
||||
linter.flake8_import_conventions.banned_aliases = {}
|
||||
linter.flake8_import_conventions.banned_from = []
|
||||
linter.flake8_pytest_style.fixture_parentheses = true
|
||||
linter.flake8_pytest_style.parametrize_names_type = tuple
|
||||
linter.flake8_pytest_style.parametrize_values_type = list
|
||||
linter.flake8_pytest_style.parametrize_values_row_type = tuple
|
||||
linter.flake8_pytest_style.raises_require_match_for = [
|
||||
BaseException,
|
||||
Exception,
|
||||
ValueError,
|
||||
OSError,
|
||||
IOError,
|
||||
EnvironmentError,
|
||||
socket.error,
|
||||
]
|
||||
linter.flake8_pytest_style.raises_extend_require_match_for = []
|
||||
linter.flake8_pytest_style.mark_parentheses = true
|
||||
linter.flake8_quotes.inline_quotes = double
|
||||
linter.flake8_quotes.multiline_quotes = double
|
||||
linter.flake8_quotes.docstring_quotes = double
|
||||
linter.flake8_quotes.avoid_escape = true
|
||||
linter.flake8_self.ignore_names = [
|
||||
_make,
|
||||
_asdict,
|
||||
_replace,
|
||||
_fields,
|
||||
_field_defaults,
|
||||
_name_,
|
||||
_value_,
|
||||
]
|
||||
linter.flake8_tidy_imports.ban_relative_imports = "parents"
|
||||
linter.flake8_tidy_imports.banned_api = {}
|
||||
linter.flake8_tidy_imports.banned_module_level_imports = []
|
||||
linter.flake8_type_checking.strict = false
|
||||
linter.flake8_type_checking.exempt_modules = [
|
||||
typing,
|
||||
typing_extensions,
|
||||
]
|
||||
linter.flake8_type_checking.runtime_required_base_classes = []
|
||||
linter.flake8_type_checking.runtime_required_decorators = []
|
||||
linter.flake8_type_checking.quote_annotations = false
|
||||
linter.flake8_unused_arguments.ignore_variadic_names = false
|
||||
linter.isort.required_imports = []
|
||||
linter.isort.combine_as_imports = false
|
||||
linter.isort.force_single_line = false
|
||||
linter.isort.force_sort_within_sections = false
|
||||
linter.isort.detect_same_package = true
|
||||
linter.isort.case_sensitive = false
|
||||
linter.isort.force_wrap_aliases = false
|
||||
linter.isort.force_to_top = []
|
||||
linter.isort.known_modules = {}
|
||||
linter.isort.order_by_type = true
|
||||
linter.isort.relative_imports_order = furthest_to_closest
|
||||
linter.isort.single_line_exclusions = []
|
||||
linter.isort.split_on_trailing_comma = true
|
||||
linter.isort.classes = []
|
||||
linter.isort.constants = []
|
||||
linter.isort.variables = []
|
||||
linter.isort.no_lines_before = []
|
||||
linter.isort.lines_after_imports = -1
|
||||
linter.isort.lines_between_types = 0
|
||||
linter.isort.forced_separate = []
|
||||
linter.isort.section_order = [
|
||||
known { type = future },
|
||||
known { type = standard_library },
|
||||
known { type = third_party },
|
||||
known { type = first_party },
|
||||
known { type = local_folder },
|
||||
]
|
||||
linter.isort.no_sections = false
|
||||
linter.isort.from_first = false
|
||||
linter.isort.length_sort = false
|
||||
linter.isort.length_sort_straight = false
|
||||
linter.mccabe.max_complexity = 10
|
||||
linter.pep8_naming.ignore_names = [
|
||||
setUp,
|
||||
tearDown,
|
||||
setUpClass,
|
||||
tearDownClass,
|
||||
setUpModule,
|
||||
tearDownModule,
|
||||
asyncSetUp,
|
||||
asyncTearDown,
|
||||
setUpTestData,
|
||||
failureException,
|
||||
longMessage,
|
||||
maxDiff,
|
||||
]
|
||||
linter.pep8_naming.classmethod_decorators = []
|
||||
linter.pep8_naming.staticmethod_decorators = []
|
||||
linter.pycodestyle.max_line_length = 88
|
||||
linter.pycodestyle.max_doc_length = none
|
||||
linter.pycodestyle.ignore_overlong_task_comments = false
|
||||
linter.pyflakes.extend_generics = []
|
||||
linter.pylint.allow_magic_value_types = [
|
||||
str,
|
||||
bytes,
|
||||
]
|
||||
linter.pylint.allow_dunder_method_names = []
|
||||
linter.pylint.max_args = 5
|
||||
linter.pylint.max_positional_args = 5
|
||||
linter.pylint.max_returns = 6
|
||||
linter.pylint.max_bool_expr = 5
|
||||
linter.pylint.max_branches = 12
|
||||
linter.pylint.max_statements = 50
|
||||
linter.pylint.max_public_methods = 20
|
||||
linter.pylint.max_locals = 15
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
formatter.target_version = Py37
|
||||
formatter.preview = disabled
|
||||
formatter.line_width = 88
|
||||
formatter.line_ending = auto
|
||||
formatter.indent_style = space
|
||||
formatter.indent_width = 4
|
||||
formatter.quote_style = double
|
||||
formatter.magic_trailing_comma = respect
|
||||
formatter.docstring_code_format = disabled
|
||||
formatter.docstring_code_line_width = dynamic
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -35,6 +35,7 @@ mod source_code;
|
||||
use crate::formatter::Formatter;
|
||||
use crate::group_id::UniqueGroupIdBuilder;
|
||||
use crate::prelude::TagKind;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::marker::PhantomData;
|
||||
use std::num::{NonZeroU16, NonZeroU8, TryFromIntError};
|
||||
@@ -113,6 +114,12 @@ impl Default for IndentWidth {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IndentWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for IndentWidth {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
@@ -146,6 +153,12 @@ impl Default for LineWidth {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LineWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for LineWidth {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
|
||||
@@ -2,10 +2,28 @@
|
||||
# Errors.
|
||||
###
|
||||
incorrect_set = {"value1", 23, 5, "value1"}
|
||||
incorrect_set = {1, 1, 2}
|
||||
incorrect_set_multiline = {
|
||||
"value1",
|
||||
23,
|
||||
5,
|
||||
"value1",
|
||||
# B033
|
||||
}
|
||||
incorrect_set = {1, 1}
|
||||
incorrect_set = {1, 1,}
|
||||
incorrect_set = {0, 1, 1,}
|
||||
incorrect_set = {0, 1, 1}
|
||||
incorrect_set = {
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
}
|
||||
|
||||
###
|
||||
# Non-errors.
|
||||
###
|
||||
correct_set = {"value1", 23, 5}
|
||||
correct_set = {5, "5"}
|
||||
correct_set = {5}
|
||||
correct_set = {}
|
||||
|
||||
195
crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM113.py
vendored
Normal file
195
crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM113.py
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
def func():
|
||||
# SIM113
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# SIM113
|
||||
sum = 0
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
if g(x):
|
||||
break
|
||||
idx += 1
|
||||
sum += h(x, idx)
|
||||
|
||||
|
||||
def func():
|
||||
# SIM113
|
||||
idx = 0
|
||||
for x, y in zip(range(5), range(5)):
|
||||
g(x)
|
||||
h(x, y)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func():
|
||||
# SIM113
|
||||
idx = 0
|
||||
sum = 0
|
||||
for x in range(5):
|
||||
sum += h(x, idx)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func():
|
||||
# SIM113
|
||||
sum, idx = 0, 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK (index doesn't start at 0
|
||||
idx = 10
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK (incremented by more than 1)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 2
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK (incremented in `if`)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
if g(x):
|
||||
idx += 1
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK (`continue` in match-case)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
match g(x):
|
||||
case 1:
|
||||
h(x)
|
||||
case 2:
|
||||
continue
|
||||
case _:
|
||||
h(idx)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func():
|
||||
# OK (`continue` inside `with` clause)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
with context as c:
|
||||
if g(x):
|
||||
continue
|
||||
h(x, idx, c)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func():
|
||||
# OK (incremented in `try` block)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
try:
|
||||
g(x, idx)
|
||||
except:
|
||||
h(x)
|
||||
continue
|
||||
idx += 1
|
||||
|
||||
|
||||
def func(idx: 0):
|
||||
# OK (index is an argument)
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
# OK (index _may_ be non-zero)
|
||||
if x > 0:
|
||||
idx = 1
|
||||
else:
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
idx += 1
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
if idx > 10:
|
||||
idx *= 2
|
||||
h(x)
|
||||
|
||||
|
||||
def func():
|
||||
# OK (index used after loop)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
h(x)
|
||||
print(idx)
|
||||
|
||||
|
||||
def func():
|
||||
# OK (index within nested loop)
|
||||
idx = 0
|
||||
for x in range(5):
|
||||
for y in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func():
|
||||
# OK (index used within inner)
|
||||
idx = 0
|
||||
|
||||
def inner():
|
||||
print(idx)
|
||||
|
||||
for x in range(5):
|
||||
for y in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
|
||||
|
||||
def func():
|
||||
# OK (index used as nonlocal inner)
|
||||
idx = 0
|
||||
|
||||
def inner():
|
||||
nonlocal idx
|
||||
idx = 3
|
||||
|
||||
for x in range(5):
|
||||
for y in range(5):
|
||||
g(x, idx)
|
||||
idx += 1
|
||||
@@ -31,3 +31,6 @@ import torch
|
||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||
|
||||
(x.drop(["a"], axis=1, inplace=True))
|
||||
|
||||
# This method doesn't take exist in Pandas, so ignore it.
|
||||
x.rotate_z(45, inplace=True)
|
||||
|
||||
@@ -24,6 +24,12 @@ class Thing:
|
||||
super().__init__() # OK
|
||||
super().__class__(stuff=(1, 2, 3)) # OK
|
||||
|
||||
def __getattribute__(self, item):
|
||||
return object.__getattribute__(self, item) # OK
|
||||
|
||||
def do_thing(self, item):
|
||||
return object.__getattribute__(self, item) # PLC2801
|
||||
|
||||
|
||||
blah = lambda: {"a": 1}.__delitem__("a") # OK
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ constant = 5
|
||||
{value.attribute: value.upper() for value in data for constant in data}
|
||||
{constant[value]: value.upper() for value in data for constant in data}
|
||||
{value[constant]: value.upper() for value in data for constant in data}
|
||||
{local_id: token for token in tokens if (local_id := _extract_local_id(token)) is not None}
|
||||
{key: kwargs.get(key) for key in kwargs.keys() if not params.get(key)}
|
||||
|
||||
# Errors
|
||||
{"key": value.upper() for value in data}
|
||||
@@ -20,3 +22,5 @@ constant = 5
|
||||
{constant + constant: value.upper() for value in data}
|
||||
{constant.attribute: value.upper() for value in data}
|
||||
{constant[0]: value.upper() for value in data}
|
||||
{tokens: token for token in tokens}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_python_ast::Stmt;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bugbear, perflint, pyupgrade, refurb};
|
||||
use crate::rules::{flake8_bugbear, flake8_simplify, perflint, pyupgrade, refurb};
|
||||
|
||||
/// Run lint rules over all deferred for-loops in the [`SemanticModel`].
|
||||
pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
@@ -27,6 +27,9 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryEnumerate) {
|
||||
refurb::rules::unnecessary_enumerate(checker, stmt_for);
|
||||
}
|
||||
if checker.enabled(Rule::EnumerateForLoop) {
|
||||
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -980,9 +980,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_pie::rules::unnecessary_spread(checker, dict);
|
||||
}
|
||||
}
|
||||
Expr::Set(ast::ExprSet { elts, range: _ }) => {
|
||||
Expr::Set(set) => {
|
||||
if checker.enabled(Rule::DuplicateValue) {
|
||||
flake8_bugbear::rules::duplicate_value(checker, elts);
|
||||
flake8_bugbear::rules::duplicate_value(checker, set);
|
||||
}
|
||||
}
|
||||
Expr::Yield(_) => {
|
||||
|
||||
@@ -1258,9 +1258,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnusedLoopControlVariable,
|
||||
Rule::EnumerateForLoop,
|
||||
Rule::IncorrectDictIterator,
|
||||
Rule::UnnecessaryEnumerate,
|
||||
Rule::UnusedLoopControlVariable,
|
||||
Rule::YieldInForLoop,
|
||||
]) {
|
||||
checker.deferred.for_loops.push(checker.semantic.snapshot());
|
||||
|
||||
@@ -14,14 +14,15 @@ use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// Return the amount of indentation, expanding tabs to the next multiple of 8.
|
||||
fn expand_indent(line: &str) -> usize {
|
||||
/// Return the amount of indentation, expanding tabs to the next multiple of the settings' tab size.
|
||||
fn expand_indent(line: &str, settings: &LinterSettings) -> usize {
|
||||
let line = line.trim_end_matches(['\n', '\r']);
|
||||
|
||||
let mut indent = 0;
|
||||
let tab_size = settings.tab_size.as_usize();
|
||||
for c in line.bytes() {
|
||||
match c {
|
||||
b'\t' => indent = (indent / 8) * 8 + 8,
|
||||
b'\t' => indent = (indent / tab_size) * tab_size + tab_size,
|
||||
b' ' => indent += 1,
|
||||
_ => break,
|
||||
}
|
||||
@@ -84,7 +85,7 @@ pub(crate) fn check_logical_lines(
|
||||
TextRange::new(locator.line_start(first_token.start()), first_token.start())
|
||||
};
|
||||
|
||||
let indent_level = expand_indent(locator.slice(range));
|
||||
let indent_level = expand_indent(locator.slice(range), settings);
|
||||
|
||||
let indent_size = 4;
|
||||
|
||||
|
||||
@@ -454,6 +454,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Simplify, "109") => (RuleGroup::Stable, rules::flake8_simplify::rules::CompareWithTuple),
|
||||
(Flake8Simplify, "110") => (RuleGroup::Stable, rules::flake8_simplify::rules::ReimplementedBuiltin),
|
||||
(Flake8Simplify, "112") => (RuleGroup::Stable, rules::flake8_simplify::rules::UncapitalizedEnvironmentVariables),
|
||||
(Flake8Simplify, "113") => (RuleGroup::Preview, rules::flake8_simplify::rules::EnumerateForLoop),
|
||||
(Flake8Simplify, "114") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfWithSameArms),
|
||||
(Flake8Simplify, "115") => (RuleGroup::Stable, rules::flake8_simplify::rules::OpenFileWithContextHandler),
|
||||
(Flake8Simplify, "116") => (RuleGroup::Stable, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictLookup),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::hash::Hasher;
|
||||
use std::num::{NonZeroU16, NonZeroU8, ParseIntError};
|
||||
use std::str::FromStr;
|
||||
@@ -39,6 +40,12 @@ impl Default for LineLength {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LineLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheKey for LineLength {
|
||||
fn cache_key(&self, state: &mut CacheKeyHasher) {
|
||||
state.write_u16(self.0.get());
|
||||
@@ -248,6 +255,12 @@ impl Default for IndentWidth {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IndentWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonZeroU8> for IndentWidth {
|
||||
fn from(tab_size: NonZeroU8) -> Self {
|
||||
Self(tab_size)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::registry::Rule;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
const RULESET_SIZE: usize = 13;
|
||||
@@ -269,6 +269,21 @@ impl Debug for RuleSet {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RuleSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_empty() {
|
||||
write!(f, "[]")?;
|
||||
} else {
|
||||
writeln!(f, "[")?;
|
||||
for rule in self {
|
||||
writeln!(f, "\t{rule:?},")?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Rule> for RuleSet {
|
||||
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
|
||||
let mut set = RuleSet::empty();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `flake-annotations` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
@@ -11,3 +13,20 @@ pub struct Settings {
|
||||
pub allow_star_arg_any: bool,
|
||||
pub ignore_fully_untyped: bool,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_annotations",
|
||||
fields = [
|
||||
self.mypy_init_return,
|
||||
self.suppress_dummy_args,
|
||||
self.suppress_none_returning,
|
||||
self.allow_star_arg_any,
|
||||
self.ignore_fully_untyped
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `flake8-bandit` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
pub fn default_tmp_dirs() -> Vec<String> {
|
||||
["/tmp", "/var/tmp", "/dev/shm"]
|
||||
@@ -22,3 +24,17 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_bandit",
|
||||
fields = [
|
||||
self.hardcoded_tmp_directory | array,
|
||||
self.check_typed_exception
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ mod tests {
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
@@ -69,6 +70,24 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::DuplicateValue, Path::new("B033.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bugbear").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_without_explicit_strict() -> Result<()> {
|
||||
let snapshot = "B905.py";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use ruff_python_ast::Expr;
|
||||
use anyhow::{Context, Result};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -31,28 +33,82 @@ pub struct DuplicateValue {
|
||||
}
|
||||
|
||||
impl Violation for DuplicateValue {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DuplicateValue { value } = self;
|
||||
format!("Sets should not contain duplicate item `{value}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some(format!("Remove duplicate item"))
|
||||
}
|
||||
}
|
||||
|
||||
/// B033
|
||||
pub(crate) fn duplicate_value(checker: &mut Checker, elts: &Vec<Expr>) {
|
||||
pub(crate) fn duplicate_value(checker: &mut Checker, set: &ast::ExprSet) {
|
||||
let mut seen_values: FxHashSet<ComparableExpr> = FxHashSet::default();
|
||||
for elt in elts {
|
||||
for (index, elt) in set.elts.iter().enumerate() {
|
||||
if elt.is_literal_expr() {
|
||||
let comparable_value: ComparableExpr = elt.into();
|
||||
|
||||
if !seen_values.insert(comparable_value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
DuplicateValue {
|
||||
value: checker.generator().expr(elt),
|
||||
},
|
||||
elt.range(),
|
||||
));
|
||||
);
|
||||
|
||||
if checker.settings.preview.is_enabled() {
|
||||
diagnostic.try_set_fix(|| {
|
||||
remove_member(set, index, checker.locator().contents()).map(Fix::safe_edit)
|
||||
});
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the member at the given index from the [`ast::ExprSet`].
|
||||
fn remove_member(set: &ast::ExprSet, index: usize, source: &str) -> Result<Edit> {
|
||||
if index < set.elts.len() - 1 {
|
||||
// Case 1: the expression is _not_ the last node, so delete from the start of the
|
||||
// expression to the end of the subsequent comma.
|
||||
// Ex) Delete `"a"` in `{"a", "b", "c"}`.
|
||||
let mut tokenizer = SimpleTokenizer::starts_at(set.elts[index].end(), source);
|
||||
|
||||
// Find the trailing comma.
|
||||
tokenizer
|
||||
.find(|token| token.kind == SimpleTokenKind::Comma)
|
||||
.context("Unable to find trailing comma")?;
|
||||
|
||||
// Find the next non-whitespace token.
|
||||
let next = tokenizer
|
||||
.find(|token| {
|
||||
token.kind != SimpleTokenKind::Whitespace && token.kind != SimpleTokenKind::Newline
|
||||
})
|
||||
.context("Unable to find next token")?;
|
||||
|
||||
Ok(Edit::deletion(set.elts[index].start(), next.start()))
|
||||
} else if index > 0 {
|
||||
// Case 2: the expression is the last node, but not the _only_ node, so delete from the
|
||||
// start of the previous comma to the end of the expression.
|
||||
// Ex) Delete `"c"` in `{"a", "b", "c"}`.
|
||||
let mut tokenizer = SimpleTokenizer::starts_at(set.elts[index - 1].end(), source);
|
||||
|
||||
// Find the trailing comma.
|
||||
let comma = tokenizer
|
||||
.find(|token| token.kind == SimpleTokenKind::Comma)
|
||||
.context("Unable to find trailing comma")?;
|
||||
|
||||
Ok(Edit::deletion(comma.start(), set.elts[index].end()))
|
||||
} else {
|
||||
// Case 3: expression is the only node, so delete it.
|
||||
// Ex) Delete `"a"` in `{"a"}`.
|
||||
Ok(Edit::range_deletion(set.elts[index].range()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers;
|
||||
use ruff_python_ast::helpers::NameFinder;
|
||||
use ruff_python_ast::helpers::{NameFinder, StoredNameFinder};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Violation for UnusedLoopControlVariable {
|
||||
/// B007
|
||||
pub(crate) fn unused_loop_control_variable(checker: &mut Checker, stmt_for: &ast::StmtFor) {
|
||||
let control_names = {
|
||||
let mut finder = NameFinder::default();
|
||||
let mut finder = StoredNameFinder::default();
|
||||
finder.visit_expr(stmt_for.target.as_ref());
|
||||
finder.names
|
||||
};
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `flake8-bugbear` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub extend_immutable_calls: Vec<String>,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_bugbear",
|
||||
fields = [
|
||||
self.extend_immutable_calls | array
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,85 @@ B033.py:4:35: B033 Sets should not contain duplicate item `"value1"`
|
||||
3 | ###
|
||||
4 | incorrect_set = {"value1", 23, 5, "value1"}
|
||||
| ^^^^^^^^ B033
|
||||
5 | incorrect_set = {1, 1}
|
||||
5 | incorrect_set = {1, 1, 2}
|
||||
6 | incorrect_set_multiline = {
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:5:21: B033 Sets should not contain duplicate item `1`
|
||||
|
|
||||
3 | ###
|
||||
4 | incorrect_set = {"value1", 23, 5, "value1"}
|
||||
5 | incorrect_set = {1, 1}
|
||||
5 | incorrect_set = {1, 1, 2}
|
||||
| ^ B033
|
||||
6 |
|
||||
7 | ###
|
||||
6 | incorrect_set_multiline = {
|
||||
7 | "value1",
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:10:5: B033 Sets should not contain duplicate item `"value1"`
|
||||
|
|
||||
8 | 23,
|
||||
9 | 5,
|
||||
10 | "value1",
|
||||
| ^^^^^^^^ B033
|
||||
11 | # B033
|
||||
12 | }
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:13:21: B033 Sets should not contain duplicate item `1`
|
||||
|
|
||||
11 | # B033
|
||||
12 | }
|
||||
13 | incorrect_set = {1, 1}
|
||||
| ^ B033
|
||||
14 | incorrect_set = {1, 1,}
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:14:21: B033 Sets should not contain duplicate item `1`
|
||||
|
|
||||
12 | }
|
||||
13 | incorrect_set = {1, 1}
|
||||
14 | incorrect_set = {1, 1,}
|
||||
| ^ B033
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
16 | incorrect_set = {0, 1, 1}
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:15:24: B033 Sets should not contain duplicate item `1`
|
||||
|
|
||||
13 | incorrect_set = {1, 1}
|
||||
14 | incorrect_set = {1, 1,}
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
| ^ B033
|
||||
16 | incorrect_set = {0, 1, 1}
|
||||
17 | incorrect_set = {
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:16:24: B033 Sets should not contain duplicate item `1`
|
||||
|
|
||||
14 | incorrect_set = {1, 1,}
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
16 | incorrect_set = {0, 1, 1}
|
||||
| ^ B033
|
||||
17 | incorrect_set = {
|
||||
18 | 0,
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
B033.py:20:5: B033 Sets should not contain duplicate item `1`
|
||||
|
|
||||
18 | 0,
|
||||
19 | 1,
|
||||
20 | 1,
|
||||
| ^ B033
|
||||
21 | }
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B033.py:4:35: B033 [*] Sets should not contain duplicate item `"value1"`
|
||||
|
|
||||
2 | # Errors.
|
||||
3 | ###
|
||||
4 | incorrect_set = {"value1", 23, 5, "value1"}
|
||||
| ^^^^^^^^ B033
|
||||
5 | incorrect_set = {1, 1, 2}
|
||||
6 | incorrect_set_multiline = {
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | ###
|
||||
2 2 | # Errors.
|
||||
3 3 | ###
|
||||
4 |-incorrect_set = {"value1", 23, 5, "value1"}
|
||||
4 |+incorrect_set = {"value1", 23, 5}
|
||||
5 5 | incorrect_set = {1, 1, 2}
|
||||
6 6 | incorrect_set_multiline = {
|
||||
7 7 | "value1",
|
||||
|
||||
B033.py:5:21: B033 [*] Sets should not contain duplicate item `1`
|
||||
|
|
||||
3 | ###
|
||||
4 | incorrect_set = {"value1", 23, 5, "value1"}
|
||||
5 | incorrect_set = {1, 1, 2}
|
||||
| ^ B033
|
||||
6 | incorrect_set_multiline = {
|
||||
7 | "value1",
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | # Errors.
|
||||
3 3 | ###
|
||||
4 4 | incorrect_set = {"value1", 23, 5, "value1"}
|
||||
5 |-incorrect_set = {1, 1, 2}
|
||||
5 |+incorrect_set = {1, 2}
|
||||
6 6 | incorrect_set_multiline = {
|
||||
7 7 | "value1",
|
||||
8 8 | 23,
|
||||
|
||||
B033.py:10:5: B033 [*] Sets should not contain duplicate item `"value1"`
|
||||
|
|
||||
8 | 23,
|
||||
9 | 5,
|
||||
10 | "value1",
|
||||
| ^^^^^^^^ B033
|
||||
11 | # B033
|
||||
12 | }
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | "value1",
|
||||
8 8 | 23,
|
||||
9 9 | 5,
|
||||
10 |- "value1",
|
||||
11 10 | # B033
|
||||
12 11 | }
|
||||
13 12 | incorrect_set = {1, 1}
|
||||
|
||||
B033.py:13:21: B033 [*] Sets should not contain duplicate item `1`
|
||||
|
|
||||
11 | # B033
|
||||
12 | }
|
||||
13 | incorrect_set = {1, 1}
|
||||
| ^ B033
|
||||
14 | incorrect_set = {1, 1,}
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 | "value1",
|
||||
11 11 | # B033
|
||||
12 12 | }
|
||||
13 |-incorrect_set = {1, 1}
|
||||
13 |+incorrect_set = {1}
|
||||
14 14 | incorrect_set = {1, 1,}
|
||||
15 15 | incorrect_set = {0, 1, 1,}
|
||||
16 16 | incorrect_set = {0, 1, 1}
|
||||
|
||||
B033.py:14:21: B033 [*] Sets should not contain duplicate item `1`
|
||||
|
|
||||
12 | }
|
||||
13 | incorrect_set = {1, 1}
|
||||
14 | incorrect_set = {1, 1,}
|
||||
| ^ B033
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
16 | incorrect_set = {0, 1, 1}
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | # B033
|
||||
12 12 | }
|
||||
13 13 | incorrect_set = {1, 1}
|
||||
14 |-incorrect_set = {1, 1,}
|
||||
14 |+incorrect_set = {1,}
|
||||
15 15 | incorrect_set = {0, 1, 1,}
|
||||
16 16 | incorrect_set = {0, 1, 1}
|
||||
17 17 | incorrect_set = {
|
||||
|
||||
B033.py:15:24: B033 [*] Sets should not contain duplicate item `1`
|
||||
|
|
||||
13 | incorrect_set = {1, 1}
|
||||
14 | incorrect_set = {1, 1,}
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
| ^ B033
|
||||
16 | incorrect_set = {0, 1, 1}
|
||||
17 | incorrect_set = {
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | }
|
||||
13 13 | incorrect_set = {1, 1}
|
||||
14 14 | incorrect_set = {1, 1,}
|
||||
15 |-incorrect_set = {0, 1, 1,}
|
||||
15 |+incorrect_set = {0, 1,}
|
||||
16 16 | incorrect_set = {0, 1, 1}
|
||||
17 17 | incorrect_set = {
|
||||
18 18 | 0,
|
||||
|
||||
B033.py:16:24: B033 [*] Sets should not contain duplicate item `1`
|
||||
|
|
||||
14 | incorrect_set = {1, 1,}
|
||||
15 | incorrect_set = {0, 1, 1,}
|
||||
16 | incorrect_set = {0, 1, 1}
|
||||
| ^ B033
|
||||
17 | incorrect_set = {
|
||||
18 | 0,
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | incorrect_set = {1, 1}
|
||||
14 14 | incorrect_set = {1, 1,}
|
||||
15 15 | incorrect_set = {0, 1, 1,}
|
||||
16 |-incorrect_set = {0, 1, 1}
|
||||
16 |+incorrect_set = {0, 1}
|
||||
17 17 | incorrect_set = {
|
||||
18 18 | 0,
|
||||
19 19 | 1,
|
||||
|
||||
B033.py:20:5: B033 [*] Sets should not contain duplicate item `1`
|
||||
|
|
||||
18 | 0,
|
||||
19 | 1,
|
||||
20 | 1,
|
||||
| ^ B033
|
||||
21 | }
|
||||
|
|
||||
= help: Remove duplicate item
|
||||
|
||||
ℹ Safe fix
|
||||
17 17 | incorrect_set = {
|
||||
18 18 | 0,
|
||||
19 19 | 1,
|
||||
20 |- 1,
|
||||
21 20 | }
|
||||
22 21 |
|
||||
23 22 | ###
|
||||
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `flake8-builtins` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub builtins_ignorelist: Vec<String>,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_builtins",
|
||||
fields = [
|
||||
self.builtins_ignorelist | array
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `flake8-comprehensions` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub allow_dict_calls_with_keyword_arguments: bool,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_comprehensions",
|
||||
fields = [
|
||||
self.allow_dict_calls_with_keyword_arguments
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
@@ -24,3 +26,18 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_copyright",
|
||||
fields = [
|
||||
self.notice_rgx,
|
||||
self.author | optional,
|
||||
self.min_file_size,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `flake8-errmsg` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub max_string_length: usize,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_errmsg",
|
||||
fields = [
|
||||
self.max_string_length
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
@@ -20,3 +22,16 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_gettext",
|
||||
fields = [
|
||||
self.functions_names | array
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `flake8-implicit-str-concat` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
@@ -14,3 +16,16 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_implicit_str_concat",
|
||||
fields = [
|
||||
self.allow_multiline
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Settings for import conventions.
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
|
||||
@@ -44,3 +46,18 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_import_conventions",
|
||||
fields = [
|
||||
self.aliases | debug,
|
||||
self.banned_aliases | debug,
|
||||
self.banned_from | array,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Settings for the `flake8-pytest-style` plugin.
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::settings::types::IdentifierPattern;
|
||||
@@ -47,6 +49,25 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_pytest_style",
|
||||
fields = [
|
||||
self.fixture_parentheses,
|
||||
self.parametrize_names_type,
|
||||
self.parametrize_values_type,
|
||||
self.parametrize_values_row_type,
|
||||
self.raises_require_match_for | array,
|
||||
self.raises_extend_require_match_for | array,
|
||||
self.mark_parentheses
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by the [`TryFrom`] implementation of [`Settings`].
|
||||
#[derive(Debug)]
|
||||
pub enum SettingsError {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Settings for the `flake8-quotes` plugin.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
|
||||
@@ -39,6 +41,22 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_quotes",
|
||||
fields = [
|
||||
self.inline_quotes,
|
||||
self.multiline_quotes,
|
||||
self.docstring_quotes,
|
||||
self.avoid_escape
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Quote {
|
||||
#[must_use]
|
||||
pub const fn opposite(self) -> Self {
|
||||
@@ -56,3 +74,12 @@ impl Quote {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Quote {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Double => write!(f, "double"),
|
||||
Self::Single => write!(f, "single"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `flake8-self` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
// By default, ignore the `namedtuple` methods and attributes, as well as the
|
||||
// _sunder_ names in Enum, which are underscore-prefixed to prevent conflicts
|
||||
@@ -27,3 +29,16 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_self",
|
||||
fields = [
|
||||
self.ignore_names | array
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ mod tests {
|
||||
#[test_case(Rule::ReimplementedBuiltin, Path::new("SIM110.py"))]
|
||||
#[test_case(Rule::ReimplementedBuiltin, Path::new("SIM111.py"))]
|
||||
#[test_case(Rule::UncapitalizedEnvironmentVariables, Path::new("SIM112.py"))]
|
||||
#[test_case(Rule::EnumerateForLoop, Path::new("SIM113.py"))]
|
||||
#[test_case(Rule::OpenFileWithContextHandler, Path::new("SIM115.py"))]
|
||||
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
|
||||
#[test_case(Rule::InDictKeys, Path::new("SIM118.py"))]
|
||||
|
||||
@@ -287,7 +287,7 @@ impl AlwaysFixableViolation for ExprAndFalse {
|
||||
}
|
||||
|
||||
/// Return `true` if two `Expr` instances are equivalent names.
|
||||
fn is_same_expr<'a>(a: &'a Expr, b: &'a Expr) -> Option<&'a str> {
|
||||
pub(crate) fn is_same_expr<'a>(a: &'a Expr, b: &'a Expr) -> Option<&'a str> {
|
||||
if let (Expr::Name(ast::ExprName { id: a, .. }), Expr::Name(ast::ExprName { id: b, .. })) =
|
||||
(&a, &b)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor};
|
||||
use ruff_python_ast::{self as ast, Expr, Int, Number, Operator, Stmt};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `for` loops with explicit loop-index variables that can be replaced
|
||||
/// with `enumerate()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When iterating over a sequence, it's often desirable to keep track of the
|
||||
/// index of each element alongside the element itself. Prefer the `enumerate`
|
||||
/// builtin over manually incrementing a counter variable within the loop, as
|
||||
/// `enumerate` is more concise and idiomatic.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// fruits = ["apple", "banana", "cherry"]
|
||||
/// for fruit in fruits:
|
||||
/// print(f"{i + 1}. {fruit}")
|
||||
/// i += 1
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// fruits = ["apple", "banana", "cherry"]
|
||||
/// for i, fruit in enumerate(fruits):
|
||||
/// print(f"{i + 1}. {fruit}")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `enumerate`](https://docs.python.org/3/library/functions.html#enumerate)
|
||||
#[violation]
|
||||
pub struct EnumerateForLoop {
|
||||
index: String,
|
||||
}
|
||||
|
||||
impl Violation for EnumerateForLoop {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let EnumerateForLoop { index } = self;
|
||||
format!("Use `enumerate()` for index variable `{index}` in `for` loop")
|
||||
}
|
||||
}
|
||||
|
||||
/// SIM113
|
||||
pub(crate) fn enumerate_for_loop(checker: &mut Checker, for_stmt: &ast::StmtFor) {
|
||||
// If the loop contains a `continue`, abort.
|
||||
let mut visitor = LoopControlFlowVisitor::default();
|
||||
visitor.visit_body(&for_stmt.body);
|
||||
if visitor.has_continue {
|
||||
return;
|
||||
}
|
||||
|
||||
for stmt in &for_stmt.body {
|
||||
// Find the augmented assignment expression (e.g., `i += 1`).
|
||||
if let Some(index) = match_index_increment(stmt) {
|
||||
// Find the binding corresponding to the initialization (e.g., `i = 1`).
|
||||
let Some(id) = checker.semantic().resolve_name(index) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If it's not an assignment (e.g., it's a function argument), ignore it.
|
||||
let binding = checker.semantic().binding(id);
|
||||
if !binding.kind.is_assignment() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the variable is global or nonlocal, ignore it.
|
||||
if binding.is_global() || binding.is_nonlocal() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure that the index variable was initialized to 0.
|
||||
let Some(value) = typing::find_binding_value(&index.id, binding, checker.semantic())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !matches!(
|
||||
value,
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: Number::Int(Int::ZERO),
|
||||
..
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the binding is not at the same level as the `for` loop (e.g., it's in an `if`),
|
||||
// ignore it.
|
||||
let Some(for_loop_id) = checker.semantic().current_statement_id() else {
|
||||
continue;
|
||||
};
|
||||
let Some(assignment_id) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
if checker.semantic().parent_statement_id(for_loop_id)
|
||||
!= checker.semantic().parent_statement_id(assignment_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Identify the binding created by the augmented assignment.
|
||||
// TODO(charlie): There should be a way to go from `ExprName` to `BindingId` (like
|
||||
// `resolve_name`, but for bindings rather than references).
|
||||
let binding = {
|
||||
let mut bindings = checker
|
||||
.semantic()
|
||||
.current_scope()
|
||||
.get_all(&index.id)
|
||||
.map(|id| checker.semantic().binding(id))
|
||||
.filter(|binding| for_stmt.range().contains_range(binding.range()));
|
||||
|
||||
let Some(binding) = bindings.next() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If there are multiple assignments to this variable _within_ the loop, ignore it.
|
||||
if bindings.next().is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
binding
|
||||
};
|
||||
|
||||
// If the variable is used outside the loop, ignore it.
|
||||
if binding.references.iter().any(|id| {
|
||||
let reference = checker.semantic().reference(*id);
|
||||
!for_stmt.range().contains_range(reference.range())
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let diagnostic = Diagnostic::new(
|
||||
EnumerateForLoop {
|
||||
index: index.id.to_string(),
|
||||
},
|
||||
stmt.range(),
|
||||
);
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the statement is an index increment statement (e.g., `i += 1`), return
|
||||
/// the name of the index variable.
|
||||
fn match_index_increment(stmt: &Stmt) -> Option<&ast::ExprName> {
|
||||
let Stmt::AugAssign(ast::StmtAugAssign {
|
||||
target,
|
||||
op: Operator::Add,
|
||||
value,
|
||||
..
|
||||
}) = stmt
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let name = target.as_name_expr()?;
|
||||
|
||||
if matches!(
|
||||
value.as_ref(),
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: Number::Int(Int::ONE),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return Some(name);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct LoopControlFlowVisitor {
|
||||
has_continue: bool,
|
||||
}
|
||||
|
||||
impl StatementVisitor<'_> for LoopControlFlowVisitor {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::Continue(_) => self.has_continue = true,
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
// Don't recurse.
|
||||
}
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub(crate) use ast_ifexp::*;
|
||||
pub(crate) use ast_unary_op::*;
|
||||
pub(crate) use ast_with::*;
|
||||
pub(crate) use collapsible_if::*;
|
||||
pub(crate) use enumerate_for_loop::*;
|
||||
pub(crate) use if_else_block_instead_of_dict_get::*;
|
||||
pub(crate) use if_else_block_instead_of_dict_lookup::*;
|
||||
pub(crate) use if_else_block_instead_of_if_exp::*;
|
||||
@@ -23,6 +24,7 @@ mod ast_ifexp;
|
||||
mod ast_unary_op;
|
||||
mod ast_with;
|
||||
mod collapsible_if;
|
||||
mod enumerate_for_loop;
|
||||
mod fix_with;
|
||||
mod if_else_block_instead_of_dict_get;
|
||||
mod if_else_block_instead_of_dict_lookup;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM113.py:6:9: SIM113 Use `enumerate()` for index variable `idx` in `for` loop
|
||||
|
|
||||
4 | for x in range(5):
|
||||
5 | g(x, idx)
|
||||
6 | idx += 1
|
||||
| ^^^^^^^^ SIM113
|
||||
7 | h(x)
|
||||
|
|
||||
|
||||
SIM113.py:17:9: SIM113 Use `enumerate()` for index variable `idx` in `for` loop
|
||||
|
|
||||
15 | if g(x):
|
||||
16 | break
|
||||
17 | idx += 1
|
||||
| ^^^^^^^^ SIM113
|
||||
18 | sum += h(x, idx)
|
||||
|
|
||||
|
||||
SIM113.py:27:9: SIM113 Use `enumerate()` for index variable `idx` in `for` loop
|
||||
|
|
||||
25 | g(x)
|
||||
26 | h(x, y)
|
||||
27 | idx += 1
|
||||
| ^^^^^^^^ SIM113
|
||||
|
|
||||
|
||||
SIM113.py:36:9: SIM113 Use `enumerate()` for index variable `idx` in `for` loop
|
||||
|
|
||||
34 | for x in range(5):
|
||||
35 | sum += h(x, idx)
|
||||
36 | idx += 1
|
||||
| ^^^^^^^^ SIM113
|
||||
|
|
||||
|
||||
SIM113.py:44:9: SIM113 Use `enumerate()` for index variable `idx` in `for` loop
|
||||
|
|
||||
42 | for x in range(5):
|
||||
43 | g(x, idx)
|
||||
44 | idx += 1
|
||||
| ^^^^^^^^ SIM113
|
||||
45 | h(x)
|
||||
|
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
|
||||
@@ -22,9 +24,33 @@ pub enum Strictness {
|
||||
All,
|
||||
}
|
||||
|
||||
impl Display for Strictness {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Parents => write!(f, "\"parents\""),
|
||||
Self::All => write!(f, "\"all\""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, CacheKey, Default)]
|
||||
pub struct Settings {
|
||||
pub ban_relative_imports: Strictness,
|
||||
pub banned_api: FxHashMap<String, ApiBan>,
|
||||
pub banned_module_level_imports: Vec<String>,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_tidy_imports",
|
||||
fields = [
|
||||
self.ban_relative_imports,
|
||||
self.banned_api | debug,
|
||||
self.banned_module_level_imports | array,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, Int};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number};
|
||||
use ruff_python_semantic::analyze::typing::find_assigned_value;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -65,8 +65,7 @@ pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
|
||||
|
||||
match arg {
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
|
||||
let Some(int) = value.as_int() else { return };
|
||||
if *int != Int::ZERO {
|
||||
if !matches!(value, Number::Int(Int::ZERO)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -74,11 +73,13 @@ pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) {
|
||||
let Some(value) = find_assigned_value(id, checker.semantic()) else {
|
||||
return;
|
||||
};
|
||||
let Expr::NumberLiteral(ast::ExprNumberLiteral { value: num, .. }) = value else {
|
||||
return;
|
||||
};
|
||||
let Some(int) = num.as_int() else { return };
|
||||
if *int != Int::ZERO {
|
||||
if !matches!(
|
||||
value,
|
||||
Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: Number::Int(Int::ZERO),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `flake8-type-checking` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
@@ -22,3 +24,20 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_type_checking",
|
||||
fields = [
|
||||
self.strict,
|
||||
self.exempt_modules | array,
|
||||
self.runtime_required_base_classes | array,
|
||||
self.runtime_required_decorators | array,
|
||||
self.quote_annotations
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `flake8-unused-arguments` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub ignore_variadic_names: bool,
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.flake8_unused_arguments",
|
||||
fields = [
|
||||
self.ignore_variadic_names
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, iter};
|
||||
@@ -40,6 +41,18 @@ pub enum ImportType {
|
||||
LocalFolder,
|
||||
}
|
||||
|
||||
impl fmt::Display for ImportType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Future => write!(f, "future"),
|
||||
Self::StandardLibrary => write!(f, "standard_library"),
|
||||
Self::ThirdParty => write!(f, "third_party"),
|
||||
Self::FirstParty => write!(f, "first_party"),
|
||||
Self::LocalFolder => write!(f, "local_folder"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, CacheKey)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
@@ -48,6 +61,15 @@ pub enum ImportSection {
|
||||
UserDefined(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ImportSection {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Known(import_type) => write!(f, "known {{ type = {import_type} }}",),
|
||||
Self::UserDefined(string) => fmt::Debug::fmt(string, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Reason<'a> {
|
||||
NonZeroLevel,
|
||||
@@ -378,3 +400,18 @@ impl KnownModules {
|
||||
user_defined
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for KnownModules {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.known.is_empty() {
|
||||
write!(f, "{{}}")?;
|
||||
} else {
|
||||
writeln!(f, "{{")?;
|
||||
for (pattern, import_section) in &self.known {
|
||||
writeln!(f, "\t{pattern} => {import_section:?},")?;
|
||||
}
|
||||
write!(f, "}}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::rules::isort::categorize::KnownModules;
|
||||
@@ -32,6 +34,15 @@ impl Default for RelativeImportsOrder {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RelativeImportsOrder {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::ClosestToFurthest => write!(f, "closest_to_furthest"),
|
||||
Self::FurthestToClosest => write!(f, "furthest_to_closest"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Settings {
|
||||
@@ -94,6 +105,43 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.isort",
|
||||
fields = [
|
||||
self.required_imports | array,
|
||||
self.combine_as_imports,
|
||||
self.force_single_line,
|
||||
self.force_sort_within_sections,
|
||||
self.detect_same_package,
|
||||
self.case_sensitive,
|
||||
self.force_wrap_aliases,
|
||||
self.force_to_top | array,
|
||||
self.known_modules,
|
||||
self.order_by_type,
|
||||
self.relative_imports_order,
|
||||
self.single_line_exclusions | array,
|
||||
self.split_on_trailing_comma,
|
||||
self.classes | array,
|
||||
self.constants | array,
|
||||
self.variables | array,
|
||||
self.no_lines_before | array,
|
||||
self.lines_after_imports,
|
||||
self.lines_between_types,
|
||||
self.forced_separate | array,
|
||||
self.section_order | array,
|
||||
self.no_sections,
|
||||
self.from_first,
|
||||
self.length_sort,
|
||||
self.length_sort_straight
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by the [`TryFrom`] implementation of [`Settings`].
|
||||
#[derive(Debug)]
|
||||
pub enum SettingsError {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `mccabe` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
@@ -16,3 +18,16 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.mccabe",
|
||||
fields = [
|
||||
self.max_complexity
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,15 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the function doesn't take an `inplace` argument, abort.
|
||||
if !call
|
||||
.func
|
||||
.as_attribute_expr()
|
||||
.is_some_and(|func| accepts_inplace_argument(&func.attr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut seen_star = false;
|
||||
for keyword in call.arguments.keywords.iter().rev() {
|
||||
let Some(arg) = &keyword.arg else {
|
||||
@@ -134,3 +143,35 @@ fn convert_inplace_argument_to_assignment(
|
||||
|
||||
Some(Fix::unsafe_edits(insert_assignment, [remove_argument]))
|
||||
}
|
||||
|
||||
/// Returns `true` if the given method accepts an `inplace` argument when used on a Pandas
|
||||
/// `DataFrame`, `Series`, or `Index`.
|
||||
///
|
||||
/// See: <https://pandas.pydata.org/docs/reference/frame.html>
|
||||
fn accepts_inplace_argument(method: &str) -> bool {
|
||||
matches!(
|
||||
method,
|
||||
"where"
|
||||
| "mask"
|
||||
| "query"
|
||||
| "clip"
|
||||
| "eval"
|
||||
| "backfill"
|
||||
| "bfill"
|
||||
| "ffill"
|
||||
| "fillna"
|
||||
| "interpolate"
|
||||
| "dropna"
|
||||
| "pad"
|
||||
| "replace"
|
||||
| "drop"
|
||||
| "drop_duplicates"
|
||||
| "rename"
|
||||
| "rename_axis"
|
||||
| "reset_index"
|
||||
| "set_index"
|
||||
| "sort_values"
|
||||
| "sort_index"
|
||||
| "set_names"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -164,6 +164,8 @@ PD002.py:33:24: PD002 [*] `inplace=True` should be avoided; it has inconsistent
|
||||
32 |
|
||||
33 | (x.drop(["a"], axis=1, inplace=True))
|
||||
| ^^^^^^^^^^^^ PD002
|
||||
34 |
|
||||
35 | # This method doesn't take exist in Pandas, so ignore it.
|
||||
|
|
||||
= help: Assign to variable; remove `inplace` arg
|
||||
|
||||
@@ -173,5 +175,8 @@ PD002.py:33:24: PD002 [*] `inplace=True` should be avoided; it has inconsistent
|
||||
32 32 |
|
||||
33 |-(x.drop(["a"], axis=1, inplace=True))
|
||||
33 |+x = (x.drop(["a"], axis=1))
|
||||
34 34 |
|
||||
35 35 | # This method doesn't take exist in Pandas, so ignore it.
|
||||
36 36 | x.rotate_z(45, inplace=True)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::settings::types::IdentifierPattern;
|
||||
@@ -44,6 +46,21 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.pep8_naming",
|
||||
fields = [
|
||||
self.ignore_names | array,
|
||||
self.classmethod_decorators | array,
|
||||
self.staticmethod_decorators | array
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by the [`TryFrom`] implementation of [`Settings`].
|
||||
#[derive(Debug)]
|
||||
pub enum SettingsError {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Settings for the `pycodestyle` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt;
|
||||
|
||||
use crate::line_width::LineLength;
|
||||
|
||||
@@ -10,3 +12,18 @@ pub struct Settings {
|
||||
pub max_doc_length: Option<LineLength>,
|
||||
pub ignore_overlong_task_comments: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.pycodestyle",
|
||||
fields = [
|
||||
self.max_line_length,
|
||||
self.max_doc_length | optional,
|
||||
self.ignore_overlong_task_comments,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
//! Settings for the `pydocstyle` plugin.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::registry::Rule;
|
||||
@@ -71,9 +73,34 @@ impl Convention {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Convention {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Google => write!(f, "google"),
|
||||
Self::Numpy => write!(f, "numpy"),
|
||||
Self::Pep257 => write!(f, "pep257"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub convention: Option<Convention>,
|
||||
pub ignore_decorators: BTreeSet<String>,
|
||||
pub property_decorators: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.pydocstyle",
|
||||
fields = [
|
||||
self.convention | optional,
|
||||
self.ignore_decorators | debug,
|
||||
self.property_decorators | debug
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `Pyflakes` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub extend_generics: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.pyflakes",
|
||||
fields = [
|
||||
self.extend_generics | debug
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -80,11 +80,17 @@ pub(crate) fn unnecessary_dunder_call(checker: &mut Checker, call: &ast::ExprCal
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore certain dunder methods used in lambda expressions.
|
||||
// Ignore certain dunder method calls in lambda expressions. These methods would require
|
||||
// rewriting as a statement, which is not possible in a lambda expression.
|
||||
if allow_nested_expression(attr, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore dunder method calls within dunder methods definitions.
|
||||
if in_dunder_method_definition(checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore dunder methods used on `super`.
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
||||
if checker.semantic().is_builtin("super") {
|
||||
@@ -344,3 +350,13 @@ fn allow_nested_expression(dunder_name: &str, semantic: &SemanticModel) -> bool
|
||||
| "__ior__"
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`SemanticModel`] is currently in a dunder method definition.
|
||||
fn in_dunder_method_definition(semantic: &SemanticModel) -> bool {
|
||||
semantic.current_statements().any(|statement| {
|
||||
let Stmt::FunctionDef(func_def) = statement else {
|
||||
return false;
|
||||
};
|
||||
func_def.name.starts_with("__") && func_def.name.ends_with("__")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::{ExprNumberLiteral, LiteralExpressionRef, Number};
|
||||
|
||||
@@ -34,6 +36,18 @@ impl ConstantType {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConstantType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Bytes => write!(f, "bytes"),
|
||||
Self::Complex => write!(f, "complex"),
|
||||
Self::Float => write!(f, "float"),
|
||||
Self::Int => write!(f, "int"),
|
||||
Self::Str => write!(f, "str"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub allow_magic_value_types: Vec<ConstantType>,
|
||||
@@ -64,3 +78,25 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.pylint",
|
||||
fields = [
|
||||
self.allow_magic_value_types | array,
|
||||
self.allow_dunder_method_names | array,
|
||||
self.max_args,
|
||||
self.max_positional_args,
|
||||
self.max_returns,
|
||||
self.max_bool_expr,
|
||||
self.max_branches,
|
||||
self.max_statements,
|
||||
self.max_public_methods,
|
||||
self.max_locals
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,4 +321,12 @@ unnecessary_dunder_call.py:19:7: PLC2801 Unnecessary dunder call to `__neg__`. M
|
||||
|
|
||||
= help: Multiply by -1 instead
|
||||
|
||||
unnecessary_dunder_call.py:31:16: PLC2801 Unnecessary dunder call to `__getattribute__`. Access attribute directly or use getattr built-in function.
|
||||
|
|
||||
30 | def do_thing(self, item):
|
||||
31 | return object.__getattribute__(self, item) # PLC2801
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC2801
|
||||
|
|
||||
= help: Access attribute directly or use getattr built-in function
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
//! Settings for the `pyupgrade` plugin.
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default, CacheKey)]
|
||||
pub struct Settings {
|
||||
pub keep_runtime_typing: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.pyupgrade",
|
||||
fields = [
|
||||
self.keep_runtime_typing
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ mod tests {
|
||||
|
||||
use crate::pyproject_toml::lint_pyproject_toml;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::resolve_per_file_ignores;
|
||||
use crate::settings::types::{PerFileIgnore, PreviewMode, PythonVersion};
|
||||
use crate::settings::types::{PerFileIgnore, PerFileIgnores, PreviewMode, PythonVersion};
|
||||
use crate::test::{test_path, test_resource_path};
|
||||
use crate::{assert_messages, settings};
|
||||
|
||||
@@ -171,7 +170,7 @@ mod tests {
|
||||
let mut settings =
|
||||
settings::LinterSettings::for_rules(vec![Rule::UnusedNOQA, Rule::UnusedImport]);
|
||||
|
||||
settings.per_file_ignores = resolve_per_file_ignores(vec![PerFileIgnore::new(
|
||||
settings.per_file_ignores = PerFileIgnores::resolve(vec![PerFileIgnore::new(
|
||||
"RUF100_2.py".to_string(),
|
||||
&["F401".parse().unwrap()],
|
||||
None,
|
||||
@@ -228,7 +227,7 @@ mod tests {
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff/ruff_per_file_ignores.py"),
|
||||
&settings::LinterSettings {
|
||||
per_file_ignores: resolve_per_file_ignores(vec![PerFileIgnore::new(
|
||||
per_file_ignores: PerFileIgnores::resolve(vec![PerFileIgnore::new(
|
||||
"ruff_per_file_ignores.py".to_string(),
|
||||
&["F401".parse().unwrap(), "RUF100".parse().unwrap()],
|
||||
None,
|
||||
|
||||
@@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::NameFinder;
|
||||
use ruff_python_ast::helpers::StoredNameFinder;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -51,9 +51,9 @@ impl Violation for StaticKeyDictComprehension {
|
||||
pub(crate) fn static_key_dict_comprehension(checker: &mut Checker, dict_comp: &ast::ExprDictComp) {
|
||||
// Collect the bound names in the comprehension's generators.
|
||||
let names = {
|
||||
let mut visitor = NameFinder::default();
|
||||
let mut visitor = StoredNameFinder::default();
|
||||
for generator in &dict_comp.generators {
|
||||
visitor.visit_expr(&generator.target);
|
||||
visitor.visit_comprehension(generator);
|
||||
}
|
||||
visitor.names
|
||||
};
|
||||
|
||||
@@ -1,80 +1,90 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF011.py:15:2: RUF011 Dictionary comprehension uses static key: `"key"`
|
||||
RUF011.py:17:2: RUF011 Dictionary comprehension uses static key: `"key"`
|
||||
|
|
||||
14 | # Errors
|
||||
15 | {"key": value.upper() for value in data}
|
||||
16 | # Errors
|
||||
17 | {"key": value.upper() for value in data}
|
||||
| ^^^^^ RUF011
|
||||
16 | {True: value.upper() for value in data}
|
||||
17 | {0: value.upper() for value in data}
|
||||
18 | {True: value.upper() for value in data}
|
||||
19 | {0: value.upper() for value in data}
|
||||
|
|
||||
|
||||
RUF011.py:16:2: RUF011 Dictionary comprehension uses static key: `True`
|
||||
RUF011.py:18:2: RUF011 Dictionary comprehension uses static key: `True`
|
||||
|
|
||||
14 | # Errors
|
||||
15 | {"key": value.upper() for value in data}
|
||||
16 | {True: value.upper() for value in data}
|
||||
16 | # Errors
|
||||
17 | {"key": value.upper() for value in data}
|
||||
18 | {True: value.upper() for value in data}
|
||||
| ^^^^ RUF011
|
||||
17 | {0: value.upper() for value in data}
|
||||
18 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
19 | {0: value.upper() for value in data}
|
||||
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
|
|
||||
|
||||
RUF011.py:17:2: RUF011 Dictionary comprehension uses static key: `0`
|
||||
RUF011.py:19:2: RUF011 Dictionary comprehension uses static key: `0`
|
||||
|
|
||||
15 | {"key": value.upper() for value in data}
|
||||
16 | {True: value.upper() for value in data}
|
||||
17 | {0: value.upper() for value in data}
|
||||
17 | {"key": value.upper() for value in data}
|
||||
18 | {True: value.upper() for value in data}
|
||||
19 | {0: value.upper() for value in data}
|
||||
| ^ RUF011
|
||||
18 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
19 | {constant: value.upper() for value in data}
|
||||
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
21 | {constant: value.upper() for value in data}
|
||||
|
|
||||
|
||||
RUF011.py:18:2: RUF011 Dictionary comprehension uses static key: `(1, "a")`
|
||||
RUF011.py:20:2: RUF011 Dictionary comprehension uses static key: `(1, "a")`
|
||||
|
|
||||
16 | {True: value.upper() for value in data}
|
||||
17 | {0: value.upper() for value in data}
|
||||
18 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
18 | {True: value.upper() for value in data}
|
||||
19 | {0: value.upper() for value in data}
|
||||
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
| ^^^^^^^^ RUF011
|
||||
19 | {constant: value.upper() for value in data}
|
||||
20 | {constant + constant: value.upper() for value in data}
|
||||
21 | {constant: value.upper() for value in data}
|
||||
22 | {constant + constant: value.upper() for value in data}
|
||||
|
|
||||
|
||||
RUF011.py:19:2: RUF011 Dictionary comprehension uses static key: `constant`
|
||||
RUF011.py:21:2: RUF011 Dictionary comprehension uses static key: `constant`
|
||||
|
|
||||
17 | {0: value.upper() for value in data}
|
||||
18 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
19 | {constant: value.upper() for value in data}
|
||||
19 | {0: value.upper() for value in data}
|
||||
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
21 | {constant: value.upper() for value in data}
|
||||
| ^^^^^^^^ RUF011
|
||||
20 | {constant + constant: value.upper() for value in data}
|
||||
21 | {constant.attribute: value.upper() for value in data}
|
||||
22 | {constant + constant: value.upper() for value in data}
|
||||
23 | {constant.attribute: value.upper() for value in data}
|
||||
|
|
||||
|
||||
RUF011.py:20:2: RUF011 Dictionary comprehension uses static key: `constant + constant`
|
||||
RUF011.py:22:2: RUF011 Dictionary comprehension uses static key: `constant + constant`
|
||||
|
|
||||
18 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
19 | {constant: value.upper() for value in data}
|
||||
20 | {constant + constant: value.upper() for value in data}
|
||||
20 | {(1, "a"): value.upper() for value in data} # Constant tuple
|
||||
21 | {constant: value.upper() for value in data}
|
||||
22 | {constant + constant: value.upper() for value in data}
|
||||
| ^^^^^^^^^^^^^^^^^^^ RUF011
|
||||
21 | {constant.attribute: value.upper() for value in data}
|
||||
22 | {constant[0]: value.upper() for value in data}
|
||||
23 | {constant.attribute: value.upper() for value in data}
|
||||
24 | {constant[0]: value.upper() for value in data}
|
||||
|
|
||||
|
||||
RUF011.py:21:2: RUF011 Dictionary comprehension uses static key: `constant.attribute`
|
||||
RUF011.py:23:2: RUF011 Dictionary comprehension uses static key: `constant.attribute`
|
||||
|
|
||||
19 | {constant: value.upper() for value in data}
|
||||
20 | {constant + constant: value.upper() for value in data}
|
||||
21 | {constant.attribute: value.upper() for value in data}
|
||||
21 | {constant: value.upper() for value in data}
|
||||
22 | {constant + constant: value.upper() for value in data}
|
||||
23 | {constant.attribute: value.upper() for value in data}
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF011
|
||||
22 | {constant[0]: value.upper() for value in data}
|
||||
24 | {constant[0]: value.upper() for value in data}
|
||||
25 | {tokens: token for token in tokens}
|
||||
|
|
||||
|
||||
RUF011.py:22:2: RUF011 Dictionary comprehension uses static key: `constant[0]`
|
||||
RUF011.py:24:2: RUF011 Dictionary comprehension uses static key: `constant[0]`
|
||||
|
|
||||
20 | {constant + constant: value.upper() for value in data}
|
||||
21 | {constant.attribute: value.upper() for value in data}
|
||||
22 | {constant[0]: value.upper() for value in data}
|
||||
22 | {constant + constant: value.upper() for value in data}
|
||||
23 | {constant.attribute: value.upper() for value in data}
|
||||
24 | {constant[0]: value.upper() for value in data}
|
||||
| ^^^^^^^^^^^ RUF011
|
||||
25 | {tokens: token for token in tokens}
|
||||
|
|
||||
|
||||
RUF011.py:25:2: RUF011 Dictionary comprehension uses static key: `tokens`
|
||||
|
|
||||
23 | {constant.attribute: value.upper() for value in data}
|
||||
24 | {constant[0]: value.upper() for value in data}
|
||||
25 | {tokens: token for token in tokens}
|
||||
| ^^^^^^ RUF011
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::CacheKey;
|
||||
@@ -6,6 +6,7 @@ use rustc_hash::FxHashMap;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
display_settings,
|
||||
registry::{Rule, RuleSet},
|
||||
rule_selector::{PreviewOptions, Specificity},
|
||||
RuleSelector,
|
||||
@@ -95,6 +96,20 @@ impl FixSafetyTable {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FixSafetyTable {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.safety_table",
|
||||
fields = [
|
||||
self.forced_safe,
|
||||
self.forced_unsafe
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
//! command-line options. Structure is optimized for internal usage, as opposed
|
||||
//! to external visibility or parsing.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::{Glob, GlobMatcher};
|
||||
use once_cell::sync::Lazy;
|
||||
use path_absolutize::path_dedot;
|
||||
use regex::Regex;
|
||||
@@ -15,7 +14,7 @@ use crate::codes::RuleCodePrefix;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::line_width::LineLength;
|
||||
use crate::registry::{Linter, Rule, RuleSet};
|
||||
use crate::registry::{Linter, Rule};
|
||||
use crate::rules::{
|
||||
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
|
||||
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
|
||||
@@ -23,7 +22,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::{ExtensionMapping, FilePatternSet, PerFileIgnore, PythonVersion};
|
||||
use crate::settings::types::{ExtensionMapping, FilePatternSet, PerFileIgnores, PythonVersion};
|
||||
use crate::{codes, RuleSelector};
|
||||
|
||||
use super::line_width::IndentWidth;
|
||||
@@ -38,19 +37,139 @@ pub mod flags;
|
||||
pub mod rule_table;
|
||||
pub mod types;
|
||||
|
||||
/// `display_settings!` is a macro that can display and format struct fields in a readable,
|
||||
/// namespaced format. It's particularly useful at generating `Display` implementations
|
||||
/// for types used in settings.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use std::fmt;
|
||||
/// use ruff_linter::display_settings;
|
||||
/// #[derive(Default)]
|
||||
/// struct Settings {
|
||||
/// option_a: bool,
|
||||
/// sub_settings: SubSettings,
|
||||
/// option_b: String,
|
||||
/// }
|
||||
///
|
||||
/// struct SubSettings {
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// impl Default for SubSettings {
|
||||
/// fn default() -> Self {
|
||||
/// Self { name: "Default Name".into() }
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// impl fmt::Display for SubSettings {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// display_settings! {
|
||||
/// formatter = f,
|
||||
/// namespace = "sub_settings",
|
||||
/// fields = [
|
||||
/// self.name | quoted
|
||||
/// ]
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// impl fmt::Display for Settings {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// display_settings! {
|
||||
/// formatter = f,
|
||||
/// fields = [
|
||||
/// self.option_a,
|
||||
/// self.sub_settings | nested,
|
||||
/// self.option_b | quoted,
|
||||
/// ]
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// const EXPECTED_OUTPUT: &str = r#"option_a = false
|
||||
/// sub_settings.name = "Default Name"
|
||||
/// option_b = ""
|
||||
/// "#;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let settings = Settings::default();
|
||||
/// assert_eq!(format!("{settings}"), EXPECTED_OUTPUT);
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! display_settings {
|
||||
(formatter = $fmt:ident, namespace = $namespace:literal, fields = [$($settings:ident.$field:ident $(| $modifier:tt)?),* $(,)?]) => {
|
||||
{
|
||||
const _PREFIX: &str = concat!($namespace, ".");
|
||||
$(
|
||||
display_settings!(@field $fmt, _PREFIX, $settings.$field $(| $modifier)?);
|
||||
)*
|
||||
}
|
||||
};
|
||||
(formatter = $fmt:ident, fields = [$($settings:ident.$field:ident $(| $modifier:tt)?),* $(,)?]) => {
|
||||
{
|
||||
const _PREFIX: &str = "";
|
||||
$(
|
||||
display_settings!(@field $fmt, _PREFIX, $settings.$field $(| $modifier)?);
|
||||
)*
|
||||
}
|
||||
};
|
||||
(@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | debug) => {
|
||||
writeln!($fmt, "{}{} = {:?}", $prefix, stringify!($field), $settings.$field)?;
|
||||
};
|
||||
(@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | quoted) => {
|
||||
writeln!($fmt, "{}{} = \"{}\"", $prefix, stringify!($field), $settings.$field)?;
|
||||
};
|
||||
(@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | nested) => {
|
||||
write!($fmt, "{}", $settings.$field)?;
|
||||
};
|
||||
(@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | optional) => {
|
||||
{
|
||||
write!($fmt, "{}{} = ", $prefix, stringify!($field))?;
|
||||
match &$settings.$field {
|
||||
Some(value) => writeln!($fmt, "{}", value)?,
|
||||
None => writeln!($fmt, "none")?
|
||||
};
|
||||
}
|
||||
};
|
||||
(@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident | array) => {
|
||||
{
|
||||
write!($fmt, "{}{} = ", $prefix, stringify!($field))?;
|
||||
if $settings.$field.is_empty() {
|
||||
writeln!($fmt, "[]")?;
|
||||
} else {
|
||||
writeln!($fmt, "[")?;
|
||||
for elem in &$settings.$field {
|
||||
writeln!($fmt, "\t{elem},")?;
|
||||
}
|
||||
writeln!($fmt, "]")?;
|
||||
}
|
||||
}
|
||||
};
|
||||
(@field $fmt:ident, $prefix:ident, $settings:ident.$field:ident) => {
|
||||
writeln!($fmt, "{}{} = {}", $prefix, stringify!($field), $settings.$field)?;
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct LinterSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub extension: ExtensionMapping,
|
||||
pub project_root: PathBuf,
|
||||
|
||||
pub rules: RuleTable,
|
||||
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>,
|
||||
pub per_file_ignores: PerFileIgnores,
|
||||
pub fix_safety: FixSafetyTable,
|
||||
|
||||
pub target_version: PythonVersion,
|
||||
pub preview: PreviewMode,
|
||||
pub explicit_preview_rules: bool,
|
||||
pub extension: ExtensionMapping,
|
||||
|
||||
// Rule-specific settings
|
||||
pub allowed_confusables: FxHashSet<char>,
|
||||
@@ -93,6 +212,73 @@ pub struct LinterSettings {
|
||||
pub pyupgrade: pyupgrade::settings::Settings,
|
||||
}
|
||||
|
||||
impl Display for LinterSettings {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "\n# Linter Settings")?;
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter",
|
||||
fields = [
|
||||
self.exclude,
|
||||
self.project_root | debug,
|
||||
|
||||
self.rules | nested,
|
||||
self.per_file_ignores,
|
||||
self.fix_safety | nested,
|
||||
|
||||
self.target_version | debug,
|
||||
self.preview,
|
||||
self.explicit_preview_rules,
|
||||
self.extension | nested,
|
||||
|
||||
self.allowed_confusables | array,
|
||||
self.builtins | array,
|
||||
self.dummy_variable_rgx,
|
||||
self.external | array,
|
||||
self.ignore_init_module_imports,
|
||||
self.logger_objects | array,
|
||||
self.namespace_packages | debug,
|
||||
self.src | debug,
|
||||
self.tab_size,
|
||||
self.line_length,
|
||||
self.task_tags | array,
|
||||
self.typing_modules | array,
|
||||
]
|
||||
}
|
||||
writeln!(f, "\n# Linter Plugins")?;
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter",
|
||||
fields = [
|
||||
self.flake8_annotations | nested,
|
||||
self.flake8_bandit | nested,
|
||||
self.flake8_bugbear | nested,
|
||||
self.flake8_builtins | nested,
|
||||
self.flake8_comprehensions | nested,
|
||||
self.flake8_copyright | nested,
|
||||
self.flake8_errmsg | nested,
|
||||
self.flake8_gettext | nested,
|
||||
self.flake8_implicit_str_concat | nested,
|
||||
self.flake8_import_conventions | nested,
|
||||
self.flake8_pytest_style | nested,
|
||||
self.flake8_quotes | nested,
|
||||
self.flake8_self | nested,
|
||||
self.flake8_tidy_imports | nested,
|
||||
self.flake8_type_checking | nested,
|
||||
self.flake8_unused_arguments | nested,
|
||||
self.isort | nested,
|
||||
self.mccabe | nested,
|
||||
self.pep8_naming | nested,
|
||||
self.pycodestyle | nested,
|
||||
self.pyflakes | nested,
|
||||
self.pylint | nested,
|
||||
self.pyupgrade | nested,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_SELECTORS: &[RuleSelector] = &[
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
// Only include pycodestyle rules that do not overlap with the formatter
|
||||
@@ -152,7 +338,7 @@ impl LinterSettings {
|
||||
logger_objects: vec![],
|
||||
namespace_packages: vec![],
|
||||
|
||||
per_file_ignores: vec![],
|
||||
per_file_ignores: PerFileIgnores::default(),
|
||||
fix_safety: FixSafetyTable::default(),
|
||||
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
@@ -204,22 +390,3 @@ impl Default for LinterSettings {
|
||||
Self::new(path_dedot::CWD.as_path())
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a list of patterns, create a `GlobSet`.
|
||||
pub fn resolve_per_file_ignores(
|
||||
per_file_ignores: Vec<PerFileIgnore>,
|
||||
) -> Result<Vec<(GlobMatcher, GlobMatcher, RuleSet)>> {
|
||||
per_file_ignores
|
||||
.into_iter()
|
||||
.map(|per_file_ignore| {
|
||||
// Construct absolute path matcher.
|
||||
let absolute =
|
||||
Glob::new(&per_file_ignore.absolute.to_string_lossy())?.compile_matcher();
|
||||
|
||||
// Construct basename matcher.
|
||||
let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher();
|
||||
|
||||
Ok((absolute, basename, per_file_ignore.rules))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use crate::display_settings;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::registry::{Rule, RuleSet, RuleSetIterator};
|
||||
@@ -62,6 +63,20 @@ impl RuleTable {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RuleTable {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.rules",
|
||||
fields = [
|
||||
self.enabled,
|
||||
self.should_fix
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Rule> for RuleTable {
|
||||
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
|
||||
let rules = RuleSet::from_iter(iter);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -5,7 +6,7 @@ use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use pep440_rs::{Version as Pep440Version, VersionSpecifiers};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
@@ -17,9 +18,9 @@ use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PySourceType;
|
||||
|
||||
use crate::fs;
|
||||
use crate::registry::RuleSet;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
use crate::{display_settings, fs};
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
@@ -121,6 +122,15 @@ impl From<bool> for PreviewMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PreviewMode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Disabled => write!(f, "disabled"),
|
||||
Self::Enabled => write!(f, "enabled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle for unsafe fixes.
|
||||
/// `Hint` will not apply unsafe fixes but a message will be shown when they are available.
|
||||
/// `Disabled` will not apply unsafe fixes or show a message.
|
||||
@@ -133,6 +143,20 @@ pub enum UnsafeFixes {
|
||||
Enabled,
|
||||
}
|
||||
|
||||
impl Display for UnsafeFixes {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Hint => "hint",
|
||||
Self::Disabled => "disabled",
|
||||
Self::Enabled => "enabled",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for UnsafeFixes {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
@@ -178,6 +202,19 @@ impl FilePattern {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FilePattern {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:?}",
|
||||
match self {
|
||||
Self::Builtin(pattern) => pattern,
|
||||
Self::User(pattern, _) => pattern.as_str(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FilePattern {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
@@ -192,9 +229,14 @@ impl FromStr for FilePattern {
|
||||
pub struct FilePatternSet {
|
||||
set: GlobSet,
|
||||
cache_key: u64,
|
||||
// This field is only for displaying the internals
|
||||
// of `set`.
|
||||
#[allow(clippy::used_underscore_binding)]
|
||||
_set_internals: Vec<FilePattern>,
|
||||
}
|
||||
|
||||
impl FilePatternSet {
|
||||
#[allow(clippy::used_underscore_binding)]
|
||||
pub fn try_from_iter<I>(patterns: I) -> Result<Self, anyhow::Error>
|
||||
where
|
||||
I: IntoIterator<Item = FilePattern>,
|
||||
@@ -202,7 +244,10 @@ impl FilePatternSet {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
let mut hasher = CacheKeyHasher::new();
|
||||
|
||||
let mut _set_internals = vec![];
|
||||
|
||||
for pattern in patterns {
|
||||
_set_internals.push(pattern.clone());
|
||||
pattern.cache_key(&mut hasher);
|
||||
pattern.add_to(&mut builder)?;
|
||||
}
|
||||
@@ -212,10 +257,26 @@ impl FilePatternSet {
|
||||
Ok(FilePatternSet {
|
||||
set,
|
||||
cache_key: hasher.finish(),
|
||||
_set_internals,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FilePatternSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self._set_internals.is_empty() {
|
||||
write!(f, "[]")?;
|
||||
} else {
|
||||
writeln!(f, "[")?;
|
||||
for pattern in &self._set_internals {
|
||||
writeln!(f, "\t{pattern},")?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for FilePatternSet {
|
||||
type Target = GlobSet;
|
||||
|
||||
@@ -388,9 +449,23 @@ pub struct ExtensionMapping {
|
||||
}
|
||||
|
||||
impl ExtensionMapping {
|
||||
/// Return the [`Language`] for the given extension.
|
||||
pub fn get(&self, extension: &str) -> Option<Language> {
|
||||
self.mapping.get(extension).copied()
|
||||
/// Return the [`Language`] for the given file.
|
||||
pub fn get(&self, path: &Path) -> Option<Language> {
|
||||
let ext = path.extension()?.to_str()?;
|
||||
self.mapping.get(ext).copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExtensionMapping {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "linter.extension",
|
||||
fields = [
|
||||
self.mapping | debug
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +503,23 @@ pub enum SerializationFormat {
|
||||
Sarif,
|
||||
}
|
||||
|
||||
impl Display for SerializationFormat {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Text => write!(f, "text"),
|
||||
Self::Json => write!(f, "json"),
|
||||
Self::JsonLines => write!(f, "json_lines"),
|
||||
Self::Junit => write!(f, "junit"),
|
||||
Self::Grouped => write!(f, "grouped"),
|
||||
Self::Github => write!(f, "github"),
|
||||
Self::Gitlab => write!(f, "gitlab"),
|
||||
Self::Pylint => write!(f, "pylint"),
|
||||
Self::Azure => write!(f, "azure"),
|
||||
Self::Sarif => write!(f, "sarif"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SerializationFormat {
|
||||
fn default() -> Self {
|
||||
Self::Text
|
||||
@@ -467,3 +559,55 @@ impl Deref for Version {
|
||||
/// [`fnmatch`](https://docs.python.org/3/library/fnmatch.html) for
|
||||
/// pattern matching.
|
||||
pub type IdentifierPattern = glob::Pattern;
|
||||
|
||||
#[derive(Debug, CacheKey, Default)]
|
||||
pub struct PerFileIgnores {
|
||||
// Ordered as (absolute path matcher, basename matcher, rules)
|
||||
ignores: Vec<(GlobMatcher, GlobMatcher, RuleSet)>,
|
||||
}
|
||||
|
||||
impl PerFileIgnores {
|
||||
/// Given a list of patterns, create a `GlobSet`.
|
||||
pub fn resolve(per_file_ignores: Vec<PerFileIgnore>) -> Result<Self> {
|
||||
let ignores: Result<Vec<_>> = per_file_ignores
|
||||
.into_iter()
|
||||
.map(|per_file_ignore| {
|
||||
// Construct absolute path matcher.
|
||||
let absolute =
|
||||
Glob::new(&per_file_ignore.absolute.to_string_lossy())?.compile_matcher();
|
||||
|
||||
// Construct basename matcher.
|
||||
let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher();
|
||||
|
||||
Ok((absolute, basename, per_file_ignore.rules))
|
||||
})
|
||||
.collect();
|
||||
Ok(Self { ignores: ignores? })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PerFileIgnores {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_empty() {
|
||||
write!(f, "{{}}")?;
|
||||
} else {
|
||||
writeln!(f, "{{")?;
|
||||
for (absolute, basename, rules) in &self.ignores {
|
||||
writeln!(
|
||||
f,
|
||||
"\t{{ absolute = {absolute:#?}, basename = {basename:#?}, rules = {rules} }},"
|
||||
)?;
|
||||
}
|
||||
write!(f, "}}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PerFileIgnores {
|
||||
type Target = Vec<(GlobMatcher, GlobMatcher, RuleSet)>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ignores
|
||||
}
|
||||
}
|
||||
|
||||
@@ -914,6 +914,27 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Visitor`] to collect all stored [`Expr::Name`] nodes in an AST.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StoredNameFinder<'a> {
|
||||
/// A map from identifier to defining expression.
|
||||
pub names: FxHashMap<&'a str, &'a ast::ExprName>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for StoredNameFinder<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
if let Expr::Name(name) = expr {
|
||||
if name.ctx.is_store() {
|
||||
self.names.insert(&name.id, name);
|
||||
}
|
||||
}
|
||||
crate::visitor::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`StatementVisitor`] that collects all `return` statements in a function or method.
|
||||
#[derive(Default)]
|
||||
pub struct ReturnStatementVisitor<'a> {
|
||||
|
||||
@@ -79,3 +79,15 @@ pub fn next_sibling<'a>(stmt: &'a Stmt, suite: &'a Suite) -> Option<&'a Stmt> {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Given a [`Stmt`] and its containing [`Suite`], return the previous [`Stmt`] in the [`Suite`].
|
||||
pub fn prev_sibling<'a>(stmt: &'a Stmt, suite: &'a Suite) -> Option<&'a Stmt> {
|
||||
let mut prev = None;
|
||||
for sibling in suite {
|
||||
if sibling == stmt {
|
||||
return prev;
|
||||
}
|
||||
prev = Some(sibling);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use ruff_formatter::printer::{LineEnding, PrinterOptions, SourceMapGeneration};
|
||||
use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -241,6 +242,16 @@ pub enum QuoteStyle {
|
||||
Preserve,
|
||||
}
|
||||
|
||||
impl fmt::Display for QuoteStyle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Single => write!(f, "single"),
|
||||
Self::Double => write!(f, "double"),
|
||||
Self::Preserve => write!(f, "preserve"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for QuoteStyle {
|
||||
type Err = &'static str;
|
||||
|
||||
@@ -277,6 +288,15 @@ impl MagicTrailingComma {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MagicTrailingComma {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Respect => write!(f, "respect"),
|
||||
Self::Ignore => write!(f, "ignore"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MagicTrailingComma {
|
||||
type Err = &'static str;
|
||||
|
||||
@@ -306,6 +326,15 @@ impl PreviewMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PreviewMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Disabled => write!(f, "disabled"),
|
||||
Self::Enabled => write!(f, "enabled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
|
||||
@@ -323,6 +352,15 @@ impl DocstringCode {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DocstringCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Disabled => write!(f, "disabled"),
|
||||
Self::Enabled => write!(f, "enabled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Eq, PartialEq, CacheKey)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
|
||||
@@ -338,8 +376,8 @@ pub enum DocstringCodeLineWidth {
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DocstringCodeLineWidth {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl fmt::Debug for DocstringCodeLineWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
DocstringCodeLineWidth::Fixed(v) => v.value().fmt(f),
|
||||
DocstringCodeLineWidth::Dynamic => "dynamic".fmt(f),
|
||||
@@ -347,6 +385,15 @@ impl std::fmt::Debug for DocstringCodeLineWidth {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DocstringCodeLineWidth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Fixed(width) => width.fmt(f),
|
||||
Self::Dynamic => write!(f, "dynamic"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Responsible for deserializing the `DocstringCodeLineWidth::Dynamic`
|
||||
/// variant.
|
||||
fn deserialize_docstring_code_line_width_dynamic<'de, D>(d: D) -> Result<(), D::Error>
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
use std::iter::FusedIterator;
|
||||
use std::{char, cmp::Ordering, str::FromStr};
|
||||
|
||||
use unicode_ident::{is_xid_continue, is_xid_start};
|
||||
|
||||
use ruff_python_ast::{Int, IpyEscapeKind};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
@@ -1491,19 +1489,39 @@ const fn is_ascii_identifier_start(c: char) -> bool {
|
||||
|
||||
// Checks if the character c is a valid starting character as described
|
||||
// in https://docs.python.org/3/reference/lexical_analysis.html#identifiers
|
||||
#[cold]
|
||||
fn is_unicode_identifier_start(c: char) -> bool {
|
||||
is_xid_start(c)
|
||||
unicode_ident::is_xid_start(c)
|
||||
}
|
||||
|
||||
// Checks if the character c is a valid continuation character as described
|
||||
// in https://docs.python.org/3/reference/lexical_analysis.html#identifiers
|
||||
fn is_identifier_continuation(c: char) -> bool {
|
||||
match c {
|
||||
'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => true,
|
||||
c => is_xid_continue(c),
|
||||
if c.is_ascii() {
|
||||
ASCII_CONTINUE.0[c as usize]
|
||||
} else {
|
||||
is_unicode_identifier_continue(c)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn is_unicode_identifier_continue(c: char) -> bool {
|
||||
unicode_ident::is_xid_continue(c)
|
||||
}
|
||||
|
||||
const T: bool = true;
|
||||
const F: bool = false;
|
||||
|
||||
#[repr(C, align(64))]
|
||||
struct Align64<T>(pub(crate) T);
|
||||
|
||||
static ASCII_CONTINUE: Align64<[bool; 128]> = Align64([
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F,
|
||||
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, T, T, T, F, F, F, F, F, F,
|
||||
F, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, F, F, F, T,
|
||||
F, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, F, F, F, F,
|
||||
]);
|
||||
|
||||
/// Returns `true` for [whitespace](https://docs.python.org/3/reference/lexical_analysis.html#whitespace-between-tokens)
|
||||
/// characters.
|
||||
///
|
||||
|
||||
@@ -645,6 +645,24 @@ pub fn resolve_assignment<'a>(
|
||||
pub fn find_assigned_value<'a>(symbol: &str, semantic: &'a SemanticModel<'a>) -> Option<&'a Expr> {
|
||||
let binding_id = semantic.lookup_symbol(symbol)?;
|
||||
let binding = semantic.binding(binding_id);
|
||||
find_binding_value(symbol, binding, semantic)
|
||||
}
|
||||
|
||||
/// Find the assigned [`Expr`] for a given [`Binding`], if any.
|
||||
///
|
||||
/// For example given:
|
||||
/// ```python
|
||||
/// foo = 42
|
||||
/// (bar, bla) = 1, "str"
|
||||
/// ```
|
||||
///
|
||||
/// This function will return a `NumberLiteral` with value `Int(42)` when called with `foo` and a
|
||||
/// `StringLiteral` with value `"str"` when called with `bla`.
|
||||
pub fn find_binding_value<'a>(
|
||||
symbol: &str,
|
||||
binding: &Binding,
|
||||
semantic: &'a SemanticModel,
|
||||
) -> Option<&'a Expr> {
|
||||
match binding.kind {
|
||||
// Ex) `x := 1`
|
||||
BindingKind::NamedExprAssignment => {
|
||||
|
||||
@@ -129,6 +129,11 @@ impl<'a> Scope<'a> {
|
||||
self.shadowed_bindings.get(&id).copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all bindings that the given binding shadows, including itself.
|
||||
pub fn shadowed_bindings(&self, id: BindingId) -> impl Iterator<Item = BindingId> + '_ {
|
||||
std::iter::successors(Some(id), |id| self.shadowed_bindings.get(id).copied())
|
||||
}
|
||||
|
||||
/// Adds a reference to a star import (e.g., `from sys import *`) to this scope.
|
||||
pub fn add_star_import(&mut self, import: StarImport<'a>) {
|
||||
self.star_imports.push(import);
|
||||
|
||||
@@ -25,12 +25,10 @@ use ruff_linter::rule_selector::{PreviewOptions, Specificity};
|
||||
use ruff_linter::rules::pycodestyle;
|
||||
use ruff_linter::settings::rule_table::RuleTable;
|
||||
use ruff_linter::settings::types::{
|
||||
ExtensionMapping, FilePattern, FilePatternSet, PerFileIgnore, PreviewMode, PythonVersion,
|
||||
SerializationFormat, UnsafeFixes, Version,
|
||||
};
|
||||
use ruff_linter::settings::{
|
||||
resolve_per_file_ignores, LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS,
|
||||
ExtensionMapping, FilePattern, FilePatternSet, PerFileIgnore, PerFileIgnores, PreviewMode,
|
||||
PythonVersion, SerializationFormat, UnsafeFixes, Version,
|
||||
};
|
||||
use ruff_linter::settings::{LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS};
|
||||
use ruff_linter::{
|
||||
fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION,
|
||||
};
|
||||
@@ -117,6 +115,7 @@ pub struct Configuration {
|
||||
pub output_format: Option<SerializationFormat>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
pub required_version: Option<Version>,
|
||||
pub extension: Option<ExtensionMapping>,
|
||||
pub show_fixes: Option<bool>,
|
||||
pub show_source: Option<bool>,
|
||||
|
||||
@@ -174,6 +173,7 @@ impl Configuration {
|
||||
|
||||
let formatter = FormatterSettings {
|
||||
exclude: FilePatternSet::try_from_iter(format.exclude.unwrap_or_default())?,
|
||||
extension: self.extension.clone().unwrap_or_default(),
|
||||
preview: format_preview,
|
||||
target_version: match target_version {
|
||||
PythonVersion::Py37 => ruff_python_formatter::PythonVersion::Py37,
|
||||
@@ -241,7 +241,7 @@ impl Configuration {
|
||||
linter: LinterSettings {
|
||||
rules: lint.as_rule_table(lint_preview),
|
||||
exclude: FilePatternSet::try_from_iter(lint.exclude.unwrap_or_default())?,
|
||||
extension: lint.extension.unwrap_or_default(),
|
||||
extension: self.extension.unwrap_or_default(),
|
||||
preview: lint_preview,
|
||||
target_version,
|
||||
project_root: project_root.to_path_buf(),
|
||||
@@ -258,7 +258,7 @@ impl Configuration {
|
||||
line_length,
|
||||
tab_size: self.indent_width.unwrap_or_default(),
|
||||
namespace_packages: self.namespace_packages.unwrap_or_default(),
|
||||
per_file_ignores: resolve_per_file_ignores(
|
||||
per_file_ignores: PerFileIgnores::resolve(
|
||||
lint.per_file_ignores
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
@@ -496,6 +496,9 @@ impl Configuration {
|
||||
.map(|src| resolve_src(&src, project_root))
|
||||
.transpose()?,
|
||||
target_version: options.target_version,
|
||||
// `--extension` is a hidden command-line argument that isn't supported in configuration
|
||||
// files at present.
|
||||
extension: None,
|
||||
|
||||
lint: LintConfiguration::from_options(lint, project_root)?,
|
||||
format: FormatConfiguration::from_options(
|
||||
@@ -538,6 +541,7 @@ impl Configuration {
|
||||
src: self.src.or(config.src),
|
||||
target_version: self.target_version.or(config.target_version),
|
||||
preview: self.preview.or(config.preview),
|
||||
extension: self.extension.or(config.extension),
|
||||
|
||||
lint: self.lint.combine(config.lint),
|
||||
format: self.format.combine(config.format),
|
||||
@@ -549,7 +553,6 @@ impl Configuration {
|
||||
pub struct LintConfiguration {
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
pub extension: Option<ExtensionMapping>,
|
||||
|
||||
// Rule selection
|
||||
pub extend_per_file_ignores: Vec<PerFileIgnore>,
|
||||
@@ -616,9 +619,6 @@ impl LintConfiguration {
|
||||
.chain(options.common.extend_unfixable.into_iter().flatten())
|
||||
.collect();
|
||||
Ok(LintConfiguration {
|
||||
// `--extension` is a hidden command-line argument that isn't supported in configuration
|
||||
// files at present.
|
||||
extension: None,
|
||||
exclude: options.exclude.map(|paths| {
|
||||
paths
|
||||
.into_iter()
|
||||
@@ -954,7 +954,6 @@ impl LintConfiguration {
|
||||
Self {
|
||||
exclude: self.exclude.or(config.exclude),
|
||||
preview: self.preview.or(config.preview),
|
||||
extension: self.extension.or(config.extension),
|
||||
rule_selections: config
|
||||
.rule_selections
|
||||
.into_iter()
|
||||
@@ -1031,6 +1030,7 @@ impl LintConfiguration {
|
||||
pub struct FormatConfiguration {
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
pub extension: Option<ExtensionMapping>,
|
||||
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
pub quote_style: Option<QuoteStyle>,
|
||||
@@ -1044,6 +1044,9 @@ impl FormatConfiguration {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: FormatOptions, project_root: &Path) -> Result<Self> {
|
||||
Ok(Self {
|
||||
// `--extension` is a hidden command-line argument that isn't supported in configuration
|
||||
// files at present.
|
||||
extension: None,
|
||||
exclude: options.exclude.map(|paths| {
|
||||
paths
|
||||
.into_iter()
|
||||
@@ -1077,18 +1080,19 @@ impl FormatConfiguration {
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
pub fn combine(self, config: Self) -> Self {
|
||||
Self {
|
||||
exclude: self.exclude.or(other.exclude),
|
||||
preview: self.preview.or(other.preview),
|
||||
indent_style: self.indent_style.or(other.indent_style),
|
||||
quote_style: self.quote_style.or(other.quote_style),
|
||||
magic_trailing_comma: self.magic_trailing_comma.or(other.magic_trailing_comma),
|
||||
line_ending: self.line_ending.or(other.line_ending),
|
||||
docstring_code_format: self.docstring_code_format.or(other.docstring_code_format),
|
||||
exclude: self.exclude.or(config.exclude),
|
||||
preview: self.preview.or(config.preview),
|
||||
extension: self.extension.or(config.extension),
|
||||
indent_style: self.indent_style.or(config.indent_style),
|
||||
quote_style: self.quote_style.or(config.quote_style),
|
||||
magic_trailing_comma: self.magic_trailing_comma.or(config.magic_trailing_comma),
|
||||
line_ending: self.line_ending.or(config.line_ending),
|
||||
docstring_code_format: self.docstring_code_format.or(config.docstring_code_format),
|
||||
docstring_code_line_width: self
|
||||
.docstring_code_line_width
|
||||
.or(other.docstring_code_line_width),
|
||||
.or(config.docstring_code_line_width),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use path_absolutize::path_dedot;
|
||||
use ruff_cache::cache_dir;
|
||||
use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
|
||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFormat, UnsafeFixes};
|
||||
use ruff_linter::display_settings;
|
||||
use ruff_linter::settings::types::{
|
||||
ExtensionMapping, FilePattern, FilePatternSet, SerializationFormat, UnsafeFixes,
|
||||
};
|
||||
use ruff_linter::settings::LinterSettings;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PySourceType;
|
||||
@@ -10,6 +13,7 @@ use ruff_python_formatter::{
|
||||
QuoteStyle,
|
||||
};
|
||||
use ruff_source_file::find_newline;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
@@ -53,6 +57,30 @@ impl Default for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "\n# General Settings")?;
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
fields = [
|
||||
// We want the quotes and lossy UTF8 conversion for this path, so
|
||||
// using PathBuf's `Debug` formatter suffices.
|
||||
self.cache_dir | debug,
|
||||
self.fix,
|
||||
self.fix_only,
|
||||
self.output_format,
|
||||
self.show_fixes,
|
||||
self.show_source,
|
||||
self.unsafe_fixes,
|
||||
self.file_resolver | nested,
|
||||
self.linter | nested,
|
||||
self.formatter | nested
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
pub struct FileResolverSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
@@ -64,6 +92,26 @@ pub struct FileResolverSettings {
|
||||
pub project_root: PathBuf,
|
||||
}
|
||||
|
||||
impl fmt::Display for FileResolverSettings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "\n# File Resolver Settings")?;
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "file_resolver",
|
||||
fields = [
|
||||
self.exclude,
|
||||
self.extend_exclude,
|
||||
self.force_exclude,
|
||||
self.include,
|
||||
self.extend_include,
|
||||
self.respect_gitignore,
|
||||
self.project_root | debug,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static EXCLUDE: &[FilePattern] = &[
|
||||
FilePattern::Builtin(".bzr"),
|
||||
FilePattern::Builtin(".direnv"),
|
||||
@@ -116,6 +164,7 @@ impl FileResolverSettings {
|
||||
#[derive(CacheKey, Clone, Debug)]
|
||||
pub struct FormatterSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub extension: ExtensionMapping,
|
||||
pub preview: PreviewMode,
|
||||
pub target_version: ruff_python_formatter::PythonVersion,
|
||||
|
||||
@@ -177,6 +226,7 @@ impl Default for FormatterSettings {
|
||||
|
||||
Self {
|
||||
exclude: FilePatternSet::default(),
|
||||
extension: ExtensionMapping::default(),
|
||||
target_version: default_options.target_version(),
|
||||
preview: PreviewMode::Disabled,
|
||||
line_width: default_options.line_width(),
|
||||
@@ -191,6 +241,30 @@ impl Default for FormatterSettings {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FormatterSettings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "\n# Formatter Settings")?;
|
||||
display_settings! {
|
||||
formatter = f,
|
||||
namespace = "formatter",
|
||||
fields = [
|
||||
self.exclude,
|
||||
self.target_version | debug,
|
||||
self.preview,
|
||||
self.line_width,
|
||||
self.line_ending,
|
||||
self.indent_style,
|
||||
self.indent_width,
|
||||
self.quote_style,
|
||||
self.magic_trailing_comma,
|
||||
self.docstring_code_format,
|
||||
self.docstring_code_line_width,
|
||||
]
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
@@ -212,3 +286,14 @@ pub enum LineEnding {
|
||||
/// Line endings will be converted to `\n` on Unix and `\r\n` on Windows.
|
||||
Native,
|
||||
}
|
||||
|
||||
impl fmt::Display for LineEnding {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Auto => write!(f, "auto"),
|
||||
Self::Lf => write!(f, "lf"),
|
||||
Self::CrLf => write!(f, "crlf"),
|
||||
Self::Native => write!(f, "native"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,6 +528,8 @@ Options:
|
||||
Enable preview mode; checks will include unstable rules and fixes. Use `--no-preview` to disable
|
||||
--config <CONFIG>
|
||||
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
|
||||
--extension <EXTENSION>
|
||||
List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`
|
||||
--statistics
|
||||
Show counts for every rule with at least one violation
|
||||
--add-noqa
|
||||
@@ -604,6 +606,8 @@ Options:
|
||||
Avoid writing any formatted files back; instead, exit with a non-zero status code and the difference between the current file and how the formatted file would look like
|
||||
--config <CONFIG>
|
||||
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
|
||||
--extension <EXTENSION>
|
||||
List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported [possible values: py37, py38, py39, py310, py311, py312]
|
||||
--preview
|
||||
|
||||
@@ -334,7 +334,14 @@ Ruff is available as [`flymake-ruff`](https://melpa.org/#/flymake-ruff) on MELPA
|
||||
(add-hook 'python-mode-hook #'flymake-ruff-load)
|
||||
```
|
||||
|
||||
Ruff can be used as a formatter in Emacs using the [Apheleia](https://github.com/radian-software/apheleia) formatter library, by setting this configuration:
|
||||
Ruff is also available as [`emacs-ruff-format`](https://github.com/scop/emacs-ruff-format):
|
||||
|
||||
```elisp
|
||||
(require 'ruff-format)
|
||||
(add-hook 'python-mode-hook 'ruff-format-on-save-mode)
|
||||
```
|
||||
|
||||
Alternatively, it can be used via the [Apheleia](https://github.com/radian-software/apheleia) formatter library, by setting this configuration:
|
||||
|
||||
```emacs-lisp
|
||||
(add-to-list 'apheleia-mode-alist '(python-mode . ruff))
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
}
|
||||
|
||||
/* See: https://github.com/astral-sh/ruff/issues/9046 */
|
||||
[data-md-color-scheme="astral-dark"] div.admonition p a {
|
||||
color: var(--flare);
|
||||
[data-md-color-scheme="astral-dark"] div.admonition {
|
||||
color: var(--md-code-fg-color);
|
||||
background-color: var(--md-code-bg-color);
|
||||
}
|
||||
|
||||
1
ruff.schema.json
generated
1
ruff.schema.json
generated
@@ -3552,6 +3552,7 @@
|
||||
"SIM11",
|
||||
"SIM110",
|
||||
"SIM112",
|
||||
"SIM113",
|
||||
"SIM114",
|
||||
"SIM115",
|
||||
"SIM116",
|
||||
|
||||
Reference in New Issue
Block a user