Compare commits

...

11 Commits

Author SHA1 Message Date
Charlie Marsh
a332f078db Checkout repo to support release tag validation (#5238)
## Summary

The
[release](https://github.com/astral-sh/ruff/actions/runs/5329340068/jobs/9655224008)
failed due to an inability to find `pyproject.toml`. This PR moves that
validation into its own step (so we can fail fast) and ensures we clone
the repo.
2023-06-21 03:16:35 +00:00
Charlie Marsh
e0339b538b Bump version to 0.0.274 (#5230) 2023-06-20 22:12:32 -04:00
Charlie Marsh
07b6b7401f Move copyright rules to flake8_copyright module (#5236)
## Summary

I initially wanted this category to be more general and decoupled from
the plugin, but I got some feedback that the titling felt inconsistent
with others.
2023-06-21 01:56:40 +00:00
Charlie Marsh
1db7d9e759 Avoid erroneous RUF013 violations for quoted annotations (#5234)
## Summary

Temporary fix for #5231: if we can't flag and fix these properly, just
disabling them for now.

\cc @dhruvmanila 

## Test Plan

`cargo test`
2023-06-21 01:29:12 +00:00
Charlie Marsh
621e9ace88 Use package roots rather than package members for cache initialization (#5233)
## Summary

This is a proper fix for the issue patched-over in
https://github.com/astral-sh/ruff/pull/5229, thanks to an extremely
helpful repro from @tlambert03 in that thread. It looks like we were
using the keys of `package_roots` rather than the values to initialize
the cache -- but it's a map from package to package root.

## Test Plan

Reverted #5229, then ran through the plan that @tlambert03 included in
https://github.com/astral-sh/ruff/pull/5229#issuecomment-1599723226.
Verified the panic before but not after this change.
2023-06-20 21:21:45 -04:00
Charlie Marsh
f9f77cf617 Revert change to RUF010 to remove unnecessary str calls (#5232)
## Summary

This PR reverts #4971 (aba073a791). It
turns out that `f"{str(x)}"` and `f"{x}"` are often but not exactly
equivalent, and performing that conversion automatically can lead to
subtle bugs, See the discussion in
https://github.com/astral-sh/ruff/issues/4958.
2023-06-20 21:15:17 -04:00
Charlie Marsh
1a2bd984f2 Avoid .unwrap() on cache access (#5229)
## Summary

I haven't been able to determine why / when this is happening, but in
some cases, users are reporting that this `unwrap()` is causing a panic.
It's fine to just return `None` here and fallback to "No cache",
certainly better than panicking (while we figure out the edge case).

Closes #5225.

Closes #5228.
2023-06-20 19:01:21 -04:00
Tom Kuson
4717d0779f Complete flake8-debugger documentation (#5223)
## Summary

Completes the documentation for the `flake8-debugger` ruleset. Related
to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-20 21:04:32 +00:00
Florian Stasse
07409ce201 Fixed typo in numpy deprecated type alias rule documentation (#5224)
## Summary

It is a very simple typo fix in the "numy deprecated type alias"
documentation.
2023-06-20 16:51:51 -04:00
Addison Crump
2c0ec97782 Use cpython with fuzzer corpus (#5183)
Following #5055, add cpython as a member of the fuzzer corpus
unconditionally.
2023-06-20 16:51:06 -04:00
Micha Reiser
e520a3a721 Fix ArgWithDefault comments handling (#5204) 2023-06-20 20:48:07 +00:00
64 changed files with 334 additions and 519 deletions

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
tag:
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run skipping uploading artifact."
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
type: string
sha:
description: "Optionally, the full sha of the commit to be released"
@@ -392,28 +392,14 @@ jobs:
*.tar.gz
*.sha256
release:
name: Release
validate-tag:
name: Validate tag
runs-on: ubuntu-latest
needs:
- macos-universal
- macos-x86_64
- windows
- linux
- linux-cross
- musllinux
- musllinux-cross
# If you don't set an input it's a dry run skipping uploading artifact
# If you don't set an input tag, it's a dry run (no uploads).
if: ${{ inputs.tag }}
environment:
name: release
permissions:
# For pypi trusted publishing
id-token: write
# For GitHub release publishing
contents: write
steps:
- name: Consistency check tag
- uses: actions/checkout@v3
- name: Check tag consistency
run: |
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
if [ "${{ inputs.tag }}" != "${version}" ]; then
@@ -424,7 +410,7 @@ jobs:
else
echo "Releasing ${version}"
fi
- name: Consistency check sha
- name: Check SHA consistency
if: ${{ inputs.sha }}
run: |
git_sha=$(git rev-parse HEAD)
@@ -436,6 +422,29 @@ jobs:
else
echo "Releasing ${git_sha}"
fi
release:
name: Release
runs-on: ubuntu-latest
needs:
- macos-universal
- macos-x86_64
- windows
- linux
- linux-cross
- musllinux
- musllinux-cross
- validate-tag
# If you don't set an input tag, it's a dry run (no uploads).
if: ${{ inputs.tag }}
environment:
name: release
permissions:
# For pypi trusted publishing
id-token: write
# For GitHub release publishing
contents: write
steps:
- uses: actions/download-artifact@v3
with:
name: wheels

18
Cargo.lock generated
View File

@@ -733,7 +733,7 @@ dependencies = [
[[package]]
name = "flake8-to-ruff"
version = "0.0.273"
version = "0.0.274"
dependencies = [
"anyhow",
"clap",
@@ -1793,7 +1793,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.273"
version = "0.0.274"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1889,7 +1889,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.273"
version = "0.0.274"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2105,7 +2105,7 @@ dependencies = [
[[package]]
name = "ruff_text_size"
version = "0.0.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
dependencies = [
"schemars",
"serde",
@@ -2183,7 +2183,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
dependencies = [
"is-macro",
"num-bigint",
@@ -2194,7 +2194,7 @@ dependencies = [
[[package]]
name = "rustpython-format"
version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
dependencies = [
"bitflags 2.3.1",
"itertools",
@@ -2206,7 +2206,7 @@ dependencies = [
[[package]]
name = "rustpython-literal"
version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
dependencies = [
"hexf-parse",
"is-macro",
@@ -2218,7 +2218,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
dependencies = [
"anyhow",
"is-macro",
@@ -2241,7 +2241,7 @@ dependencies = [
[[package]]
name = "rustpython-parser-core"
version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=ed3b4eb72b6e497bbdb4d19dec6621074d724130#ed3b4eb72b6e497bbdb4d19dec6621074d724130"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=08ebbe40d7776cac6e3ba66277d435056f2b8dca#08ebbe40d7776cac6e3ba66277d435056f2b8dca"
dependencies = [
"is-macro",
"memchr",

View File

@@ -50,15 +50,15 @@ toml = { version = "0.7.2" }
# v0.0.1
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
# v0.0.3
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130" }
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" }
# v0.0.3
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
# v0.0.3
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130", default-features = false, features = ["num-bigint"] }
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false, features = ["num-bigint"] }
# v0.0.3
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130", default-features = false }
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false }
# v0.0.3
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "ed3b4eb72b6e497bbdb4d19dec6621074d724130" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
[profile.release]
lto = "fat"

View File

@@ -139,7 +139,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.273
rev: v0.0.274
hooks:
- id: ruff
```

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.273"
version = "0.0.274"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.273"
version = "0.0.274"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -34,12 +34,3 @@ f"{ascii(bla)}" # OK
" intermediary content "
f" that flows {repr(obj)} of type {type(obj)}.{additional_message}" # RUF010
)
f"{str(bla)}" # RUF010
f"{str(bla):20}" # RUF010
f"{bla!s}" # RUF010
f"{bla!s:20}" # OK

View File

@@ -185,3 +185,18 @@ def f(arg: Union[Annotated[int, ...], Annotated[Optional[float], ...]] = None):
def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
pass
# Quoted
def f(arg: "int" = None):
pass
def f(arg: "str" = None):
pass
def f(arg: "Optional[int]" = None):
pass

View File

@@ -8,7 +8,7 @@ use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_whitespace::UniversalNewlines;
use crate::registry::Rule;
use crate::rules::copyright::rules::missing_copyright_notice;
use crate::rules::flake8_copyright::rules::missing_copyright_notice;
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,

View File

@@ -375,7 +375,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
// copyright
(Copyright, "001") => (RuleGroup::Nursery, rules::copyright::rules::MissingCopyrightNotice),
(Copyright, "001") => (RuleGroup::Nursery, rules::flake8_copyright::rules::MissingCopyrightNotice),
// pyupgrade
(Pyupgrade, "001") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UselessMetaclassType),

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View File

@@ -75,7 +75,7 @@ import os
"#
.trim(),
&settings::Settings {
copyright: super::settings::Settings {
flake8_copyright: super::settings::Settings {
author: Some("Ruff".to_string()),
..super::settings::Settings::default()
},
@@ -95,7 +95,7 @@ import os
"#
.trim(),
&settings::Settings {
copyright: super::settings::Settings {
flake8_copyright: super::settings::Settings {
author: Some("Ruff".to_string()),
..super::settings::Settings::default()
},
@@ -113,7 +113,7 @@ import os
"#
.trim(),
&settings::Settings {
copyright: super::settings::Settings {
flake8_copyright: super::settings::Settings {
min_file_size: 256,
..super::settings::Settings::default()
},

View File

@@ -28,7 +28,7 @@ pub(crate) fn missing_copyright_notice(
settings: &Settings,
) -> Option<Diagnostic> {
// Ignore files that are too small to contain a copyright notice.
if locator.len() < settings.copyright.min_file_size {
if locator.len() < settings.flake8_copyright.min_file_size {
return None;
}
@@ -40,8 +40,8 @@ pub(crate) fn missing_copyright_notice(
};
// Locate the copyright notice.
if let Some(match_) = settings.copyright.notice_rgx.find(contents) {
match settings.copyright.author {
if let Some(match_) = settings.flake8_copyright.notice_rgx.find(contents) {
match settings.flake8_copyright.author {
Some(ref author) => {
// Ensure that it's immediately followed by the author.
if contents[match_.end()..].trim_start().starts_with(author) {

View File

@@ -1,4 +1,4 @@
//! Settings for the `copyright` plugin.
//! Settings for the `flake8-copyright` plugin.
use once_cell::sync::Lazy;
use regex::Regex;
@@ -12,7 +12,7 @@ use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "CopyrightOptions"
rename = "Flake8CopyrightOptions"
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Options {

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---
<filename>:1:1: CPY001 Missing copyright notice at top of file
|

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---
<filename>:1:1: CPY001 Missing copyright notice at top of file
|

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---

View File

@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---

View File

@@ -7,6 +7,28 @@ use ruff_python_ast::call_path::{format_call_path, from_unqualified_name, CallPa
use crate::checkers::ast::Checker;
use crate::rules::flake8_debugger::types::DebuggerUsingType;
/// ## What it does
/// Checks for the presence of debugger calls and imports.
///
/// ## Why is this bad?
/// Debugger calls and imports should be used for debugging purposes only. The
/// presence of a debugger call or import in production code is likely a
/// mistake and may cause unintended behavior, such as exposing sensitive
/// information or causing the program to hang.
///
/// Instead, consider using a logging library to log information about the
/// program's state, and writing tests to verify that the program behaves
/// as expected.
///
/// ## Example
/// ```python
/// def foo():
/// breakpoint()
/// ```
///
/// ## References
/// - [Python documentation: `pdb` — The Python Debugger](https://docs.python.org/3/library/pdb.html)
/// - [Python documentation: `logging` — Logging facility for Python](https://docs.python.org/3/library/logging.html)
#[violation]
pub struct Debugger {
using_type: DebuggerUsingType,

View File

@@ -1,6 +1,5 @@
#![allow(clippy::useless_format)]
pub mod airflow;
pub mod copyright;
pub mod eradicate;
pub mod flake8_2020;
pub mod flake8_annotations;
@@ -12,6 +11,7 @@ pub mod flake8_bugbear;
pub mod flake8_builtins;
pub mod flake8_commas;
pub mod flake8_comprehensions;
pub mod flake8_copyright;
pub mod flake8_datetimez;
pub mod flake8_debugger;
pub mod flake8_django;

View File

@@ -12,7 +12,7 @@ use crate::registry::AsRule;
/// ## Why is this bad?
/// NumPy's `np.int` has long been an alias of the builtin `int`. The same
/// goes for `np.float`, `np.bool`, and others. These aliases exist
/// primarily primarily for historic reasons, and have been a cause of
/// primarily for historic reasons, and have been a cause of
/// frequent confusion for newcomers.
///
/// These aliases were been deprecated in 1.20, and removed in 1.24.

View File

@@ -6,7 +6,6 @@ use rustpython_parser::ast::{self, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::prelude::ConversionFlag;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::autofix::codemods::CodegenStylist;
@@ -22,62 +21,36 @@ use crate::registry::AsRule;
/// f-strings support dedicated conversion flags for these types, which are
/// more succinct and idiomatic.
///
/// In the case of `str()`, it's also redundant, since `str()` is the default
/// conversion.
/// Note that, in many cases, calling `str()` within an f-string is
/// unnecessary and can be removed entirely, as the value will be converted
/// to a string automatically, the notable exception being for classes that
/// implement a custom `__format__` method.
///
/// ## Example
/// ```python
/// a = "some string"
/// f"{str(a)}"
/// f"{repr(a)}"
/// ```
///
/// Use instead:
/// ```python
/// a = "some string"
/// f"{a}"
/// f"{a!r}"
/// ```
#[violation]
pub struct ExplicitFStringTypeConversion {
operation: Operation,
}
pub struct ExplicitFStringTypeConversion;
impl AlwaysAutofixableViolation for ExplicitFStringTypeConversion {
#[derive_message_formats]
fn message(&self) -> String {
let ExplicitFStringTypeConversion { operation } = self;
match operation {
Operation::ConvertCallToConversionFlag => {
format!("Use explicit conversion flag")
}
Operation::RemoveCall => format!("Remove unnecessary `str` conversion"),
Operation::RemoveConversionFlag => format!("Remove unnecessary conversion flag"),
}
format!("Use explicit conversion flag")
}
fn autofix_title(&self) -> String {
let ExplicitFStringTypeConversion { operation } = self;
match operation {
Operation::ConvertCallToConversionFlag => {
format!("Replace with conversion flag")
}
Operation::RemoveCall => format!("Remove `str` call"),
Operation::RemoveConversionFlag => format!("Remove conversion flag"),
}
"Replace with conversion flag".to_string()
}
}
#[derive(Debug, PartialEq, Eq)]
enum Operation {
/// Ex) Convert `f"{repr(bla)}"` to `f"{bla!r}"`
ConvertCallToConversionFlag,
/// Ex) Convert `f"{bla!s}"` to `f"{bla}"`
RemoveConversionFlag,
/// Ex) Convert `f"{str(bla)}"` to `f"{bla}"`
RemoveCall,
}
/// RUF010
pub(crate) fn explicit_f_string_type_conversion(
checker: &mut Checker,
@@ -96,156 +69,50 @@ pub(crate) fn explicit_f_string_type_conversion(
.enumerate()
{
let ast::ExprFormattedValue {
value,
conversion,
format_spec,
range: _,
value, conversion, ..
} = formatted_value;
match conversion {
ConversionFlag::Ascii | ConversionFlag::Repr => {
// Nothing to do.
continue;
}
ConversionFlag::Str => {
// Skip if there's a format spec.
if format_spec.is_some() {
continue;
}
// Remove the conversion flag entirely.
// Ex) `f"{bla!s}"`
let mut diagnostic = Diagnostic::new(
ExplicitFStringTypeConversion {
operation: Operation::RemoveConversionFlag,
},
value.range(),
);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
remove_conversion_flag(expr, index, checker.locator, checker.stylist)
});
}
checker.diagnostics.push(diagnostic);
}
ConversionFlag::None => {
// Replace with the appropriate conversion flag.
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
..
}) = value.as_ref() else {
continue;
};
// Can't be a conversion otherwise.
if args.len() != 1 || !keywords.is_empty() {
continue;
}
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
continue;
};
if !matches!(id.as_str(), "str" | "repr" | "ascii") {
continue;
};
if !checker.semantic().is_builtin(id) {
continue;
}
if id == "str" && format_spec.is_none() {
// Ex) `f"{str(bla)}"`
let mut diagnostic = Diagnostic::new(
ExplicitFStringTypeConversion {
operation: Operation::RemoveCall,
},
value.range(),
);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
remove_conversion_call(expr, index, checker.locator, checker.stylist)
});
}
checker.diagnostics.push(diagnostic);
} else {
// Ex) `f"{repr(bla)}"`
let mut diagnostic = Diagnostic::new(
ExplicitFStringTypeConversion {
operation: Operation::ConvertCallToConversionFlag,
},
value.range(),
);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
convert_call_to_conversion_flag(
expr,
index,
checker.locator,
checker.stylist,
)
});
}
checker.diagnostics.push(diagnostic);
}
}
// Skip if there's already a conversion flag.
if !conversion.is_none() {
continue;
}
let Expr::Call(ast::ExprCall {
func,
args,
keywords,
..
}) = value.as_ref() else {
continue;
};
// Can't be a conversion otherwise.
if args.len() != 1 || !keywords.is_empty() {
continue;
}
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
continue;
};
if !matches!(id.as_str(), "str" | "repr" | "ascii") {
continue;
};
if !checker.semantic().is_builtin(id) {
continue;
}
let mut diagnostic = Diagnostic::new(ExplicitFStringTypeConversion, value.range());
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
convert_call_to_conversion_flag(expr, index, checker.locator, checker.stylist)
});
}
checker.diagnostics.push(diagnostic);
}
}
/// Generate a [`Fix`] to remove a conversion flag from a formatted expression.
fn remove_conversion_flag(
expr: &Expr,
index: usize,
locator: &Locator,
stylist: &Stylist,
) -> Result<Fix> {
// Parenthesize the expression, to support implicit concatenation.
let range = expr.range();
let content = locator.slice(range);
let parenthesized_content = format!("({content})");
let mut expression = match_expression(&parenthesized_content)?;
// Replace the formatted call expression at `index` with a conversion flag.
let formatted_string_expression = match_part(index, &mut expression)?;
formatted_string_expression.conversion = None;
// Remove the parentheses (first and last characters).
let mut content = expression.codegen_stylist(stylist);
content.remove(0);
content.pop();
Ok(Fix::automatic(Edit::range_replacement(content, range)))
}
/// Generate a [`Fix`] to remove a call from a formatted expression.
fn remove_conversion_call(
expr: &Expr,
index: usize,
locator: &Locator,
stylist: &Stylist,
) -> Result<Fix> {
// Parenthesize the expression, to support implicit concatenation.
let range = expr.range();
let content = locator.slice(range);
let parenthesized_content = format!("({content})");
let mut expression = match_expression(&parenthesized_content)?;
// Replace the formatted call expression at `index` with a conversion flag.
let formatted_string_expression = match_part(index, &mut expression)?;
let call = match_call_mut(&mut formatted_string_expression.expression)?;
formatted_string_expression.expression = call.args[0].value.clone();
// Remove the parentheses (first and last characters).
let mut content = expression.codegen_stylist(stylist);
content.remove(0);
content.pop();
Ok(Fix::automatic(Edit::range_replacement(content, range)))
}
/// Generate a [`Fix`] to replace an explicit type conversion with a conversion flag.
fn convert_call_to_conversion_flag(
expr: &Expr,

View File

@@ -142,6 +142,7 @@ enum TypingTarget<'a> {
None,
Any,
Object,
ForwardReference,
Optional,
Union(Vec<&'a Expr>),
Literal(Vec<&'a Expr>),
@@ -175,6 +176,10 @@ impl<'a> TypingTarget<'a> {
value: Constant::None,
..
}) => Some(TypingTarget::None),
Expr::Constant(ast::ExprConstant {
value: Constant::Str(_),
..
}) => Some(TypingTarget::ForwardReference),
_ => semantic.resolve_call_path(expr).and_then(|call_path| {
if semantic.match_typing_call_path(&call_path, "Any") {
Some(TypingTarget::Any)
@@ -196,8 +201,8 @@ impl<'a> TypingTarget<'a> {
| TypingTarget::Object => true,
TypingTarget::Literal(elements) => elements.iter().any(|element| {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic) else {
return false;
};
return false;
};
// Literal can only contain `None`, a literal value, other `Literal`
// or an enum value.
match new_target {
@@ -208,8 +213,8 @@ impl<'a> TypingTarget<'a> {
}),
TypingTarget::Union(elements) => elements.iter().any(|element| {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic) else {
return false;
};
return false;
};
match new_target {
TypingTarget::None => true,
_ => new_target.contains_none(semantic),
@@ -217,13 +222,15 @@ impl<'a> TypingTarget<'a> {
}),
TypingTarget::Annotated(element) => {
let Some(new_target) = TypingTarget::try_from_expr(element, semantic) else {
return false;
};
return false;
};
match new_target {
TypingTarget::None => true,
_ => new_target.contains_none(semantic),
}
}
// TODO(charlie): Add support for forward references (quoted annotations).
TypingTarget::ForwardReference => true,
}
}
}
@@ -305,7 +312,7 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
}
}
/// RUF011
/// RUF013
pub(crate) fn implicit_optional(checker: &mut Checker, arguments: &Arguments) {
for ArgWithDefault {
def,

View File

@@ -312,5 +312,7 @@ RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
186 |+def f(arg: Optional[Union[Annotated[int, ...], Union[str, bytes]]] = None): # RUF011
187 187 | pass
188 188 |
189 189 |

View File

@@ -1,21 +1,21 @@
---
source: crates/ruff/src/rules/ruff/mod.rs
---
RUF010.py:9:4: RUF010 [*] Remove unnecessary `str` conversion
RUF010.py:9:4: RUF010 [*] Use explicit conversion flag
|
9 | f"{str(bla)}, {repr(bla)}, {ascii(bla)}" # RUF010
| ^^^^^^^^ RUF010
10 |
11 | f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
|
= help: Remove `str` call
= help: Replace with conversion flag
Fix
6 6 | pass
7 7 |
8 8 |
9 |-f"{str(bla)}, {repr(bla)}, {ascii(bla)}" # RUF010
9 |+f"{bla}, {repr(bla)}, {ascii(bla)}" # RUF010
9 |+f"{bla!s}, {repr(bla)}, {ascii(bla)}" # RUF010
10 10 |
11 11 | f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
12 12 |
@@ -58,7 +58,7 @@ RUF010.py:9:29: RUF010 [*] Use explicit conversion flag
11 11 | f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
12 12 |
RUF010.py:11:4: RUF010 [*] Remove unnecessary `str` conversion
RUF010.py:11:4: RUF010 [*] Use explicit conversion flag
|
9 | f"{str(bla)}, {repr(bla)}, {ascii(bla)}" # RUF010
10 |
@@ -67,14 +67,14 @@ RUF010.py:11:4: RUF010 [*] Remove unnecessary `str` conversion
12 |
13 | f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
|
= help: Remove `str` call
= help: Replace with conversion flag
Fix
8 8 |
9 9 | f"{str(bla)}, {repr(bla)}, {ascii(bla)}" # RUF010
10 10 |
11 |-f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
11 |+f"{d['a']}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
11 |+f"{d['a']!s}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
12 12 |
13 13 | f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
14 14 |
@@ -121,7 +121,7 @@ RUF010.py:11:35: RUF010 [*] Use explicit conversion flag
13 13 | f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
14 14 |
RUF010.py:13:5: RUF010 [*] Remove unnecessary `str` conversion
RUF010.py:13:5: RUF010 [*] Use explicit conversion flag
|
11 | f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
12 |
@@ -130,14 +130,14 @@ RUF010.py:13:5: RUF010 [*] Remove unnecessary `str` conversion
14 |
15 | f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
|
= help: Remove `str` call
= help: Replace with conversion flag
Fix
10 10 |
11 11 | f"{str(d['a'])}, {repr(d['b'])}, {ascii(d['c'])}" # RUF010
12 12 |
13 |-f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
13 |+f"{bla}, {(repr(bla))}, {(ascii(bla))}" # RUF010
13 |+f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
14 14 |
15 15 | f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
16 16 |
@@ -184,27 +184,6 @@ RUF010.py:13:34: RUF010 [*] Use explicit conversion flag
15 15 | f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
16 16 |
RUF010.py:15:4: RUF010 [*] Remove unnecessary conversion flag
|
13 | f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
14 |
15 | f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
| ^^^ RUF010
16 |
17 | f"{foo(bla)}" # OK
|
= help: Remove conversion flag
Fix
12 12 |
13 13 | f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
14 14 |
15 |-f"{bla!s}, {(repr(bla))}, {(ascii(bla))}" # RUF010
15 |+f"{bla}, {(repr(bla))}, {(ascii(bla))}" # RUF010
16 16 |
17 17 | f"{foo(bla)}" # OK
18 18 |
RUF010.py:15:14: RUF010 [*] Use explicit conversion flag
|
13 | f"{(str(bla))}, {(repr(bla))}, {(ascii(bla))}" # RUF010
@@ -247,27 +226,6 @@ RUF010.py:15:29: RUF010 [*] Use explicit conversion flag
17 17 | f"{foo(bla)}" # OK
18 18 |
RUF010.py:21:4: RUF010 [*] Remove unnecessary conversion flag
|
19 | f"{str(bla, 'ascii')}, {str(bla, encoding='cp1255')}" # OK
20 |
21 | f"{bla!s} {[]!r} {'bar'!a}" # OK
| ^^^ RUF010
22 |
23 | "Not an f-string {str(bla)}, {repr(bla)}, {ascii(bla)}" # OK
|
= help: Remove conversion flag
Fix
18 18 |
19 19 | f"{str(bla, 'ascii')}, {str(bla, encoding='cp1255')}" # OK
20 20 |
21 |-f"{bla!s} {[]!r} {'bar'!a}" # OK
21 |+f"{bla} {[]!r} {'bar'!a}" # OK
22 22 |
23 23 | "Not an f-string {str(bla)}, {repr(bla)}, {ascii(bla)}" # OK
24 24 |
RUF010.py:35:20: RUF010 [*] Use explicit conversion flag
|
33 | f"Member of tuple mismatches type at index {i}. Expected {of_shape_i}. Got "
@@ -285,67 +243,5 @@ RUF010.py:35:20: RUF010 [*] Use explicit conversion flag
35 |- f" that flows {repr(obj)} of type {type(obj)}.{additional_message}" # RUF010
35 |+ f" that flows {obj!r} of type {type(obj)}.{additional_message}" # RUF010
36 36 | )
37 37 |
38 38 |
RUF010.py:39:4: RUF010 [*] Remove unnecessary `str` conversion
|
39 | f"{str(bla)}" # RUF010
| ^^^^^^^^ RUF010
40 |
41 | f"{str(bla):20}" # RUF010
|
= help: Remove `str` call
Fix
36 36 | )
37 37 |
38 38 |
39 |-f"{str(bla)}" # RUF010
39 |+f"{bla}" # RUF010
40 40 |
41 41 | f"{str(bla):20}" # RUF010
42 42 |
RUF010.py:41:4: RUF010 [*] Use explicit conversion flag
|
39 | f"{str(bla)}" # RUF010
40 |
41 | f"{str(bla):20}" # RUF010
| ^^^^^^^^ RUF010
42 |
43 | f"{bla!s}" # RUF010
|
= help: Replace with conversion flag
Fix
38 38 |
39 39 | f"{str(bla)}" # RUF010
40 40 |
41 |-f"{str(bla):20}" # RUF010
41 |+f"{bla!s:20}" # RUF010
42 42 |
43 43 | f"{bla!s}" # RUF010
44 44 |
RUF010.py:43:4: RUF010 [*] Remove unnecessary conversion flag
|
41 | f"{str(bla):20}" # RUF010
42 |
43 | f"{bla!s}" # RUF010
| ^^^ RUF010
44 |
45 | f"{bla!s:20}" # OK
|
= help: Remove conversion flag
Fix
40 40 |
41 41 | f"{str(bla):20}" # RUF010
42 42 |
43 |-f"{bla!s}" # RUF010
43 |+f"{bla}" # RUF010
44 44 |
45 45 | f"{bla!s:20}" # OK

View File

@@ -312,5 +312,7 @@ RUF013_0.py:186:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
186 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF011
186 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF011
187 187 | pass
188 188 |
189 189 |

View File

@@ -16,8 +16,8 @@ use crate::fs;
use crate::line_width::{LineLength, TabSize};
use crate::rule_selector::RuleSelector;
use crate::rules::{
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
@@ -75,14 +75,14 @@ pub struct Configuration {
pub flake8_bugbear: Option<flake8_bugbear::settings::Options>,
pub flake8_builtins: Option<flake8_builtins::settings::Options>,
pub flake8_comprehensions: Option<flake8_comprehensions::settings::Options>,
pub copyright: Option<copyright::settings::Options>,
pub flake8_copyright: Option<flake8_copyright::settings::Options>,
pub flake8_errmsg: Option<flake8_errmsg::settings::Options>,
pub flake8_gettext: Option<flake8_gettext::settings::Options>,
pub flake8_implicit_str_concat: Option<flake8_implicit_str_concat::settings::Options>,
pub flake8_import_conventions: Option<flake8_import_conventions::settings::Options>,
pub flake8_pytest_style: Option<flake8_pytest_style::settings::Options>,
pub flake8_quotes: Option<flake8_quotes::settings::Options>,
pub flake8_self: Option<flake8_self::settings::Options>,
pub flake8_gettext: Option<flake8_gettext::settings::Options>,
pub flake8_tidy_imports: Option<flake8_tidy_imports::options::Options>,
pub flake8_type_checking: Option<flake8_type_checking::settings::Options>,
pub flake8_unused_arguments: Option<flake8_unused_arguments::settings::Options>,
@@ -229,7 +229,7 @@ impl Configuration {
flake8_bugbear: options.flake8_bugbear,
flake8_builtins: options.flake8_builtins,
flake8_comprehensions: options.flake8_comprehensions,
copyright: options.copyright,
flake8_copyright: options.flake8_copyright,
flake8_errmsg: options.flake8_errmsg,
flake8_gettext: options.flake8_gettext,
flake8_implicit_str_concat: options.flake8_implicit_str_concat,
@@ -308,7 +308,7 @@ impl Configuration {
flake8_comprehensions: self
.flake8_comprehensions
.combine(config.flake8_comprehensions),
copyright: self.copyright.combine(config.copyright),
flake8_copyright: self.flake8_copyright.combine(config.flake8_copyright),
flake8_errmsg: self.flake8_errmsg.combine(config.flake8_errmsg),
flake8_gettext: self.flake8_gettext.combine(config.flake8_gettext),
flake8_implicit_str_concat: self

View File

@@ -10,8 +10,8 @@ use crate::line_width::{LineLength, TabSize};
use crate::registry::Linter;
use crate::rule_selector::{prefix_to_selector, RuleSelector};
use crate::rules::{
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
@@ -96,7 +96,7 @@ impl Default for Settings {
flake8_bugbear: flake8_bugbear::settings::Settings::default(),
flake8_builtins: flake8_builtins::settings::Settings::default(),
flake8_comprehensions: flake8_comprehensions::settings::Settings::default(),
copyright: copyright::settings::Settings::default(),
flake8_copyright: flake8_copyright::settings::Settings::default(),
flake8_errmsg: flake8_errmsg::settings::Settings::default(),
flake8_implicit_str_concat: flake8_implicit_str_concat::settings::Settings::default(),
flake8_import_conventions: flake8_import_conventions::settings::Settings::default(),

View File

@@ -16,8 +16,8 @@ use ruff_macros::CacheKey;
use crate::registry::{Rule, RuleNamespace, RuleSet, INCOMPATIBLE_CODES};
use crate::rule_selector::{RuleSelector, Specificity};
use crate::rules::{
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
@@ -112,11 +112,11 @@ pub struct Settings {
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_builtins: flake8_builtins::settings::Settings,
pub flake8_comprehensions: flake8_comprehensions::settings::Settings,
pub copyright: copyright::settings::Settings,
pub flake8_copyright: flake8_copyright::settings::Settings,
pub flake8_errmsg: flake8_errmsg::settings::Settings,
pub flake8_gettext: flake8_gettext::settings::Settings,
pub flake8_implicit_str_concat: flake8_implicit_str_concat::settings::Settings,
pub flake8_import_conventions: flake8_import_conventions::settings::Settings,
pub flake8_gettext: flake8_gettext::settings::Settings,
pub flake8_pytest_style: flake8_pytest_style::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_self: flake8_self::settings::Settings,
@@ -210,9 +210,9 @@ impl Settings {
.flake8_comprehensions
.map(flake8_comprehensions::settings::Settings::from)
.unwrap_or_default(),
copyright: config
.copyright
.map(copyright::settings::Settings::try_from)
flake8_copyright: config
.flake8_copyright
.map(flake8_copyright::settings::Settings::try_from)
.transpose()?
.unwrap_or_default(),
flake8_errmsg: config

View File

@@ -8,8 +8,8 @@ use ruff_macros::ConfigurationOptions;
use crate::line_width::{LineLength, TabSize};
use crate::rule_selector::RuleSelector;
use crate::rules::{
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
@@ -499,7 +499,7 @@ pub struct Options {
pub flake8_comprehensions: Option<flake8_comprehensions::settings::Options>,
#[option_group]
/// Options for the `copyright` plugin.
pub copyright: Option<copyright::settings::Options>,
pub flake8_copyright: Option<flake8_copyright::settings::Options>,
#[option_group]
/// Options for the `flake8-errmsg` plugin.
pub flake8_errmsg: Option<flake8_errmsg::settings::Options>,

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.273"
version = "0.0.274"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -7,6 +7,7 @@ use std::time::Instant;
use anyhow::Result;
use colored::Colorize;
use ignore::Error;
use itertools::Itertools;
use log::{debug, error, warn};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
@@ -80,8 +81,10 @@ pub(crate) fn run(
// Load the caches.
let caches = bool::from(cache).then(|| {
package_roots
.par_iter()
.map(|(package_root, _)| {
.values()
.flatten()
.dedup()
.map(|package_root| {
let settings = resolver.resolve_all(package_root, pyproject_config);
let cache = Cache::open(
&settings.cli.cache_dir,
@@ -107,9 +110,7 @@ pub(crate) fn run(
let settings = resolver.resolve_all(path, pyproject_config);
let package_root = package.unwrap_or_else(|| path.parent().unwrap_or(path));
let cache = caches
.as_ref()
.map(|caches| caches.get(&package_root).unwrap());
let cache = caches.as_ref().and_then(|caches| caches.get(&package_root));
lint_path(path, package, settings, cache, noqa, autofix).map_err(|e| {
(Some(path.to_owned()), {

View File

@@ -3680,7 +3680,7 @@ impl AnyNodeRef<'_> {
/// Compares two any node refs by their pointers (referential equality).
pub fn ptr_eq(self, other: AnyNodeRef) -> bool {
self.as_ptr().eq(&other.as_ptr())
self.as_ptr().eq(&other.as_ptr()) && self.kind() == other.kind()
}
/// Returns the node's [`kind`](NodeKind) that has no data associated and is [`Copy`].

View File

@@ -90,3 +90,10 @@ else:
# Regression test for https://github.com/python/cpython/blob/7199584ac8632eab57612f595a7162ab8d2ebbc0/Lib/warnings.py#L513
def f(arg1=1, *, kwonlyarg1, kwonlyarg2=2):
pass
# Regression test for https://github.com/astral-sh/ruff/issues/5176#issuecomment-1598171989
def foo(
b=3 + 2 # comment
):
...

View File

@@ -63,7 +63,7 @@ pub fn format_and_debug_print(input: &str, cli: &Cli) -> Result<String> {
}
if cli.print_comments {
println!(
"{:?}",
"{:#?}",
formatted.context().comments().debug(SourceCode::new(input))
);
}

View File

@@ -627,17 +627,7 @@ fn handle_positional_only_arguments_separator_comment<'a>(
};
let is_last_positional_argument =
// If the preceding node is the identifier for the last positional argument (`a`).
// ```python
// def test(a, /, b): pass
// ```
are_same_optional(last_argument_or_default, arguments.posonlyargs.last().map(|arg| &arg.def))
// If the preceding node is the default for the last positional argument (`10`).
// ```python
// def test(a=10, /, b): pass
// ```
|| are_same_optional(last_argument_or_default, arguments
.posonlyargs.last().and_then(|arg| arg.default.as_deref()));
are_same_optional(last_argument_or_default, arguments.posonlyargs.last());
if !is_last_positional_argument {
return CommentPlacement::Default(comment);

View File

@@ -19,9 +19,9 @@ expression: comments.debug(test_case.source_code)
"trailing": [],
},
Node {
kind: Arg,
range: 90..91,
source: `b`,
kind: ArgWithDefault,
range: 90..94,
source: `b=20`,
}: {
"leading": [
SourceComment {

View File

@@ -24,9 +24,9 @@ expression: comments.debug(test_case.source_code)
"trailing": [],
},
Node {
kind: ExprConstant,
range: 17..19,
source: `10`,
kind: ArgWithDefault,
range: 15..19,
source: `a=10`,
}: {
"leading": [],
"dangling": [],
@@ -39,9 +39,9 @@ expression: comments.debug(test_case.source_code)
],
},
Node {
kind: Arg,
range: 173..174,
source: `b`,
kind: ArgWithDefault,
range: 173..177,
source: `b=20`,
}: {
"leading": [
SourceComment {

View File

@@ -24,7 +24,7 @@ expression: comments.debug(test_case.source_code)
"trailing": [],
},
Node {
kind: Arg,
kind: ArgWithDefault,
range: 15..16,
source: `a`,
}: {
@@ -39,7 +39,7 @@ expression: comments.debug(test_case.source_code)
],
},
Node {
kind: Arg,
kind: ArgWithDefault,
range: 166..167,
source: `b`,
}: {

View File

@@ -24,7 +24,7 @@ expression: comments.debug(test_case.source_code)
"trailing": [],
},
Node {
kind: Arg,
kind: ArgWithDefault,
range: 15..16,
source: `a`,
}: {

View File

@@ -24,7 +24,7 @@ expression: comments.debug(test_case.source_code)
"trailing": [],
},
Node {
kind: Arg,
kind: ArgWithDefault,
range: 15..16,
source: `a`,
}: {
@@ -39,7 +39,7 @@ expression: comments.debug(test_case.source_code)
],
},
Node {
kind: Arg,
kind: ArgWithDefault,
range: 166..167,
source: `b`,
}: {

View File

@@ -4,7 +4,7 @@ expression: comments.debug(test_case.source_code)
---
{
Node {
kind: Arg,
kind: ArgWithDefault,
range: 15..16,
source: `a`,
}: {

View File

@@ -236,6 +236,13 @@ impl<'ast> PreorderVisitor<'ast> for CommentsVisitor<'ast> {
self.finish_node(arg);
}
fn visit_arg_with_default(&mut self, arg_with_default: &'ast ArgWithDefault) {
if self.start_node(arg_with_default).is_traverse() {
walk_arg_with_default(self, arg_with_default);
}
self.finish_node(arg_with_default);
}
fn visit_keyword(&mut self, keyword: &'ast Keyword) {
if self.start_node(keyword).is_traverse() {
walk_keyword(self, keyword);

View File

@@ -411,10 +411,12 @@ Formatted twice:
#[test]
fn quick_test() {
let src = r#"
def test(): ...
# Comment
def with_leading_comment(): ...
def foo(
b=3
+ 2 # comment
):
...
"#;
// Tokenize once
let mut tokens = Vec::new();
@@ -437,10 +439,10 @@ def with_leading_comment(): ...
// Use `dbg_write!(f, []) instead of `write!(f, [])` in your formatting code to print some IR
// inside of a `Format` implementation
// use ruff_formatter::FormatContext;
// formatted
// dbg!(formatted
// .document()
// .display(formatted.context().source_code());
// .display(formatted.context().source_code()));
//
// dbg!(formatted
// .context()
// .comments()

View File

@@ -1,6 +1,5 @@
use rustpython_parser::ast::ArgWithDefault;
use ruff_formatter::write;
use rustpython_parser::ast::ArgWithDefault;
use crate::prelude::*;
use crate::FormatNodeRule;
@@ -20,7 +19,7 @@ impl FormatNodeRule<ArgWithDefault> for FormatArgWithDefault {
if let Some(default) = default {
let space = def.annotation.is_some().then_some(space());
write!(f, [space, text("="), space, default.format()])?;
write!(f, [space, text("="), space, group(&default.format())])?;
}
Ok(())

View File

@@ -34,14 +34,9 @@ impl FormatNodeRule<Arguments> for FormatArguments {
let mut last_node: Option<AnyNodeRef> = None;
for arg_with_default in posonlyargs {
joiner.entry(&arg_with_default.into_format());
joiner.entry(&arg_with_default.format());
last_node = Some(
arg_with_default
.default
.as_deref()
.map_or_else(|| (&arg_with_default.def).into(), AnyNodeRef::from),
);
last_node = Some(arg_with_default.into());
}
if !posonlyargs.is_empty() {
@@ -49,14 +44,9 @@ impl FormatNodeRule<Arguments> for FormatArguments {
}
for arg_with_default in args {
joiner.entry(&arg_with_default.into_format());
joiner.entry(&arg_with_default.format());
last_node = Some(
arg_with_default
.default
.as_deref()
.map_or_else(|| (&arg_with_default.def).into(), AnyNodeRef::from),
);
last_node = Some(arg_with_default.into());
}
// kw only args need either a `*args` ahead of them capturing all var args or a `*`
@@ -74,14 +64,9 @@ impl FormatNodeRule<Arguments> for FormatArguments {
}
for arg_with_default in kwonlyargs {
joiner.entry(&arg_with_default.into_format());
joiner.entry(&arg_with_default.format());
last_node = Some(
arg_with_default
.default
.as_deref()
.map_or_else(|| (&arg_with_default.def).into(), AnyNodeRef::from),
);
last_node = Some(arg_with_default.into());
}
if let Some(kwarg) = kwarg {

View File

@@ -96,6 +96,13 @@ else:
# Regression test for https://github.com/python/cpython/blob/7199584ac8632eab57612f595a7162ab8d2ebbc0/Lib/warnings.py#L513
def f(arg1=1, *, kwonlyarg1, kwonlyarg2=2):
pass
# Regression test for https://github.com/astral-sh/ruff/issues/5176#issuecomment-1598171989
def foo(
b=3 + 2 # comment
):
...
```
@@ -223,6 +230,13 @@ else:
# Regression test for https://github.com/python/cpython/blob/7199584ac8632eab57612f595a7162ab8d2ebbc0/Lib/warnings.py#L513
def f(arg1=1, *, kwonlyarg1, kwonlyarg2=2):
pass
# Regression test for https://github.com/astral-sh/ruff/issues/5176#issuecomment-1598171989
def foo(
b=3 + 2, # comment
):
...
```

View File

@@ -9,8 +9,8 @@ use ruff::line_width::{LineLength, TabSize};
use ruff::linter::{check_path, LinterResult};
use ruff::registry::AsRule;
use ruff::rules::{
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
@@ -138,11 +138,8 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
flake8_bugbear: Some(flake8_bugbear::settings::Settings::default().into()),
flake8_builtins: Some(flake8_builtins::settings::Settings::default().into()),
flake8_comprehensions: Some(flake8_comprehensions::settings::Settings::default().into()),
copyright: Some(copyright::settings::Settings::default().into()),
flake8_copyright: Some(flake8_copyright::settings::Settings::default().into()),
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
flake8_self: Some(flake8_self::settings::Settings::default().into()),
flake8_gettext: Some(flake8_gettext::settings::Settings::default().into()),
flake8_implicit_str_concat: Some(
flake8_implicit_str_concat::settings::Settings::default().into(),
@@ -150,6 +147,9 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
flake8_import_conventions: Some(
flake8_import_conventions::settings::Settings::default().into(),
),
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
flake8_self: Some(flake8_self::settings::Settings::default().into()),
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Settings::default().into()),
flake8_type_checking: Some(flake8_type_checking::settings::Settings::default().into()),
flake8_unused_arguments: Some(

View File

@@ -242,7 +242,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.273
rev: v0.0.274
hooks:
- id: ruff
```

View File

@@ -22,7 +22,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.273
rev: v0.0.274
hooks:
- id: ruff
```
@@ -32,7 +32,7 @@ Or, to enable autofix:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.273
rev: v0.0.274
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]

View File

@@ -17,6 +17,7 @@ if [ ! -d corpus/ruff_fix_validity ]; then
if [[ $REPLY =~ ^[Yy]$ ]]; then
curl -L 'https://zenodo.org/record/3628784/files/python-corpus.tar.gz?download=1' | tar xz
fi
curl -L 'https://github.com/python/cpython/archive/refs/tags/v3.12.0b2.tar.gz' | tar xz
cp -r "../../../crates/ruff/resources/test" .
cd -
cargo fuzz cmin -s none ruff_fix_validity

View File

@@ -6,9 +6,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd "$SCRIPT_DIR"
cd corpus/ruff_fix_validity
if [[ $REPLY =~ ^[Yy]$ ]]; then
curl -L 'https://zenodo.org/record/3628784/files/python-corpus.tar.gz?download=1' | tar xz
fi
curl -L 'https://github.com/python/cpython/archive/refs/tags/v3.12.0b2.tar.gz' | tar xz
cp -r "../../../crates/ruff/resources/test" .
cd -
cargo fuzz cmin -s none ruff_fix_validity

View File

@@ -5,7 +5,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.273"
version = "0.0.274"
description = "An extremely fast Python linter, written in Rust."
authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]

80
ruff.schema.json generated
View File

@@ -32,17 +32,6 @@
"null"
]
},
"copyright": {
"description": "Options for the `copyright` plugin.",
"anyOf": [
{
"$ref": "#/definitions/CopyrightOptions"
},
{
"type": "null"
}
]
},
"dummy-variable-rgx": {
"description": "A regular expression used to identify \"dummy\" variables, or those which should be ignored when enforcing (e.g.) unused-variable rules. The default expression matches `_`, `__`, and `_var`, but not `_var_`.",
"type": [
@@ -209,6 +198,17 @@
}
]
},
"flake8-copyright": {
"description": "Options for the `copyright` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8CopyrightOptions"
},
{
"type": "null"
}
]
},
"flake8-errmsg": {
"description": "Options for the `flake8-errmsg` plugin.",
"anyOf": [
@@ -631,35 +631,6 @@
}
]
},
"CopyrightOptions": {
"type": "object",
"properties": {
"author": {
"description": "Author to enforce within the copyright notice. If provided, the author must be present immediately following the copyright notice.",
"type": [
"string",
"null"
]
},
"min-file-size": {
"description": "A minimum file size (in bytes) required for a copyright notice to be enforced. By default, all files are validated.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
},
"notice-rgx": {
"description": "The regular expression used to match the copyright notice, compiled with the [`regex`](https://docs.rs/regex/latest/regex/) crate.\n\nDefaults to `(?i)Copyright\\s+(\\(C\\)\\s+)?\\d{4}(-\\d{4})*`, which matches the following: - `Copyright 2023` - `Copyright (C) 2023` - `Copyright 2021-2023` - `Copyright (C) 2021-2023`",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"Flake8AnnotationsOptions": {
"type": "object",
"properties": {
@@ -779,6 +750,35 @@
},
"additionalProperties": false
},
"Flake8CopyrightOptions": {
"type": "object",
"properties": {
"author": {
"description": "Author to enforce within the copyright notice. If provided, the author must be present immediately following the copyright notice.",
"type": [
"string",
"null"
]
},
"min-file-size": {
"description": "A minimum file size (in bytes) required for a copyright notice to be enforced. By default, all files are validated.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
},
"notice-rgx": {
"description": "The regular expression used to match the copyright notice, compiled with the [`regex`](https://docs.rs/regex/latest/regex/) crate.\n\nDefaults to `(?i)Copyright\\s+(\\(C\\)\\s+)?\\d{4}(-\\d{4})*`, which matches the following: - `Copyright 2023` - `Copyright (C) 2023` - `Copyright 2021-2023` - `Copyright (C) 2021-2023`",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"Flake8ErrMsgOptions": {
"type": "object",
"properties": {