Compare commits

..

1 Commits

Author SHA1 Message Date
konstin
d4cfe1f2a7 Break before slice colon
**Summary** Break slices at the colon first, since the colon is separator with the lowest precedence and we're in a parenthesized context.

**Input**
```python
section_header_data = byte_array[byte_begin_index + byte_step_index * event_index : byte_begin_index + byte_step_index * (event_index + 1)]
```
**Black**
```python
section_header_data = byte_array[
    byte_begin_index
    + byte_step_index * event_index : byte_begin_index
    + byte_step_index * (event_index + 1)
]
```
**Current formatting**
```python
section_header_data = byte_array[
    byte_begin_index + byte_step_index * event_index : byte_begin_index
    + byte_step_index * (event_index + 1)
]
```
**Proposed formatting**
```python
section_header_data = byte_array[
    byte_begin_index + byte_step_index * event_index
    : byte_begin_index + byte_step_index * (event_index + 1)
]
```

This is another intentional black deviation, but i find it a clear style improvement.

This is consistent with adding a step:
```python
section_header_data2 = byte_array[
    byte_begin_index + byte_step_index * event_index
    : byte_begin_index + byte_step_index
    : section_size
]
```

As-is, this regresses trailing colon comments:

**in**
```python
c1 = "c"[
    1:  # e
    # f
    2
]
```
**out**
```python
c1 = "c"[
    1
    :  # e
    # f
    2
]
```

Fixes #7316

**Test Plan** Added the fixtures above.
2023-09-13 12:11:53 +02:00
70 changed files with 1223 additions and 1539 deletions

View File

@@ -7,15 +7,12 @@ on:
description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)."
type: string
sha:
description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used."
default: ""
description: "Optionally, the full sha of the commit to be released"
type: string
pull_request:
paths:
# When we change pyproject.toml, we want to ensure that the maturin builds still work
- pyproject.toml
# And when we change this workflow itself...
- .github/workflows/release.yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -34,8 +31,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -62,8 +57,6 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -102,8 +95,6 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -150,8 +141,6 @@ jobs:
arch: x64
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -198,8 +187,6 @@ jobs:
- i686-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -257,8 +244,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -312,8 +297,6 @@ jobs:
- i686-unknown-linux-musl
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -368,8 +351,6 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
@@ -418,8 +399,6 @@ jobs:
if: ${{ inputs.tag }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- name: Check tag consistency
run: |
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
@@ -431,15 +410,6 @@ jobs:
else
echo "Releasing ${version}"
fi
- name: Check main branch
if: ${{ inputs.sha }}
run: |
# Fetch the main branch since a shallow checkout is used by default
git fetch origin main --unshallow
if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then
echo "The specified sha is not on the main branch" >&2
exit 1
fi
- name: Check SHA consistency
if: ${{ inputs.sha }}
run: |
@@ -495,8 +465,6 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- name: git tag
run: |
git config user.email "hey@astral.sh"

132
Cargo.lock generated
View File

@@ -128,11 +128,10 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "argfile"
version = "0.1.6"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1287c4f82a41c5085e65ee337c7934d71ab43d5187740a81fb69129013f6a5f6"
checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638"
dependencies = [
"fs-err",
"os_str_bytes",
]
@@ -280,7 +279,8 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"time",
"serde",
"time 0.1.45",
"wasm-bindgen",
"windows-targets 0.48.5",
]
@@ -624,6 +624,15 @@ dependencies = [
"syn 2.0.29",
]
[[package]]
name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
dependencies = [
"serde",
]
[[package]]
name = "diff"
version = "0.1.13"
@@ -936,6 +945,12 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hexf-parse"
version = "0.2.1"
@@ -1024,6 +1039,17 @@ dependencies = [
"rust-stemmers",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.0.0"
@@ -1114,15 +1140,15 @@ dependencies = [
[[package]]
name = "is-macro"
version = "0.3.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4467ed1321b310c2625c5aa6c1b1ffc5de4d9e42668cf697a08fb033ee8265e"
checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20"
dependencies = [
"Inflector",
"pmutil 0.6.1",
"pmutil",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 1.0.109",
]
[[package]]
@@ -1330,9 +1356,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
version = "2.6.3"
version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
[[package]]
name = "memoffset"
@@ -1556,18 +1582,18 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "path-absolutize"
version = "3.1.1"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
checksum = "43eb3595c63a214e1b37b44f44b0a84900ef7ae0b4c5efce59e123d246d7a0de"
dependencies = [
"path-dedot",
]
[[package]]
name = "path-dedot"
version = "3.1.1"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
checksum = "9d55e486337acb9973cdea3ec5638c1b3bcb22e573b2b7b41969e0c744d5a15e"
dependencies = [
"once_cell",
]
@@ -1646,7 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap",
"indexmap 2.0.0",
]
[[package]]
@@ -1713,17 +1739,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "pmutil"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "portable-atomic"
version = "1.4.3"
@@ -1815,11 +1830,11 @@ dependencies = [
[[package]]
name = "pyproject-toml"
version = "0.7.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "569e259cd132eb8cec5df8b672d187c5260f82ad352156b5da9549d4472e64b0"
checksum = "ee79feaa9d31e1c417e34219e610b67db4e786ce9b49d77dda549640abb9dc5f"
dependencies = [
"indexmap",
"indexmap 1.9.3",
"pep440_rs",
"pep508_rs",
"serde",
@@ -1833,7 +1848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf780b59d590c25f8c59b44c124166a2a93587868b619fb8f5b47fb15e9ed6d"
dependencies = [
"chrono",
"indexmap",
"indexmap 2.0.0",
"nextest-workspace-hack",
"quick-xml",
"thiserror",
@@ -1998,7 +2013,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fabf0a2e54f711c68c50d49f648a1a8a37adcb57353f518ac4df374f0788f42"
dependencies = [
"pmutil 0.5.3",
"pmutil",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -2707,9 +2722,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.106"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
"itoa",
"ryu",
@@ -2740,8 +2755,15 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.0.0",
"serde",
"serde_json",
"serde_with_macros",
"time 0.3.28",
]
[[package]]
@@ -2831,24 +2853,24 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.25.0"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.25.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.29",
"syn 1.0.109",
]
[[package]]
@@ -3030,6 +3052,34 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [
"deranged",
"itoa",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
dependencies = [
"time-core",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -3091,7 +3141,7 @@ version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap",
"indexmap 2.0.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3381,9 +3431,9 @@ dependencies = [
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",

View File

@@ -22,27 +22,27 @@ glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.31.0", feature = ["filters", "glob"] }
is-macro = { version = "0.3.0" }
is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
log = { version = "0.4.17" }
memchr = "2.6.3"
memchr = "2.5.0"
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
once_cell = { version = "1.17.1" }
path-absolutize = { version = "3.1.1" }
path-absolutize = { version = "3.0.14" }
proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.106" }
serde_json = { version = "1.0.93" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1", features = ["inline"] }
smallvec = { version = "1.10.0" }
static_assertions = "1.1.0"
strum = { version = "0.25.0", features = ["strum_macros"] }
strum_macros = { version = "0.25.2" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
thiserror = { version = "1.0.43" }

View File

@@ -56,7 +56,7 @@ path-absolutize = { workspace = true, features = [
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.1", features = ["serde"] }
pyproject-toml = { version = "0.7.0" }
pyproject-toml = { version = "0.6.0" }
quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
@@ -89,5 +89,3 @@ default = []
schemars = ["dep:schemars"]
# Enables the UnreachableCode rule
unreachable-code = []
# Enables rules for internal integration tests
test-rules = []

View File

@@ -8,11 +8,6 @@ reversed(sorted(x, key=lambda e: e, reverse=True))
reversed(sorted(x, reverse=True, key=lambda e: e))
reversed(sorted(x, reverse=False))
# Regression test for: https://github.com/astral-sh/ruff/issues/7289
reversed(sorted(i for i in range(42)))
reversed(sorted((i for i in range(42)), reverse=True))
def reversed(*args, **kwargs):
return None

View File

@@ -529,16 +529,3 @@ def replace_equals_with_dash2():
Parameters
===========
"""
@expect(_D213)
def non_empty_blank_line_before_section(): # noqa: D416
"""Toggle the gizmo.
The function's description.
Returns
-------
A value of some sort.
"""

View File

@@ -74,8 +74,6 @@ from typing import Collection
from typing import AsyncGenerator
from typing import Reversible
from typing import Generator
from typing import Callable
from typing import cast
# OK
from a import b

View File

@@ -1,21 +0,0 @@
l = [1, 2, 3, 4, 5]
# Errors.
a = l[:]
b, c = 1, l[:]
d, e = l[:], 1
m = l[::]
l[:]
print(l[:])
# False negatives.
aa = a[:] # Type inference.
# OK.
t = (1, 2, 3, 4, 5)
f = t[:] # t.copy() is not supported.
g = l[1:3]
h = l[1:]
i = l[:3]
j = l[1:3:2]
k = l[::2]

View File

@@ -16,7 +16,7 @@ use crate::rules::{
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format,
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify,
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle,
pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
};
use crate::settings::types::PythonVersion;
@@ -113,12 +113,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnnecessaryIterableAllocationForFirstElement) {
ruff::rules::unnecessary_iterable_allocation_for_first_element(checker, subscript);
}
if checker.enabled(Rule::InvalidIndexType) {
ruff::rules::invalid_index_type(checker, subscript);
}
if checker.settings.rules.enabled(Rule::SliceCopy) {
refurb::rules::slice_copy(checker, subscript);
}
pandas_vet::rules::subscript(checker, value, expr);
}

View File

@@ -865,13 +865,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
#[cfg(feature = "test-rules")]
(Ruff, "900") => (RuleGroup::Unspecified, rules::ruff::rules::StableTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "911") => (RuleGroup::Preview, rules::ruff::rules::PreviewTestRule),
#[cfg(feature = "test-rules")]
#[allow(deprecated)]
(Ruff, "912") => (RuleGroup::Nursery, rules::ruff::rules::NurseryTestRule),
// flake8-django
(Flake8Django, "001") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoNullableModelStringField),
@@ -923,7 +916,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Refurb, "131") => (RuleGroup::Nursery, rules::refurb::rules::DeleteFullSlice),
#[allow(deprecated)]
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
_ => return None,
})

View File

@@ -8,6 +8,16 @@ use itertools::Itertools;
use log::error;
use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::{AsMode, ParseError};
use ruff_source_file::{Locator, SourceFileBuilder};
use ruff_text_size::Ranged;
use crate::autofix::{fix_file, FixResult};
use crate::checkers::ast::check_ast;
use crate::checkers::filesystem::check_file_path;
@@ -25,15 +35,6 @@ use crate::rules::pycodestyle;
use crate::settings::{flags, Settings};
use crate::source_kind::SourceKind;
use crate::{directives, fs};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::{AsMode, ParseError};
use ruff_source_file::{Locator, SourceFileBuilder};
use ruff_text_size::Ranged;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
@@ -212,31 +213,6 @@ pub fn check_path(
));
}
// Raise violations for internal test rules
#[cfg(feature = "test-rules")]
{
if settings.rules.enabled(Rule::StableTestRule) {
diagnostics.push(Diagnostic::new(
crate::rules::ruff::rules::StableTestRule,
ruff_text_size::TextRange::default(),
));
}
if settings.rules.enabled(Rule::PreviewTestRule) {
diagnostics.push(Diagnostic::new(
crate::rules::ruff::rules::PreviewTestRule,
ruff_text_size::TextRange::default(),
));
}
if settings.rules.enabled(Rule::NurseryTestRule) {
diagnostics.push(Diagnostic::new(
crate::rules::ruff::rules::NurseryTestRule,
ruff_text_size::TextRange::default(),
));
}
}
// Ignore diagnostics based on per-file-ignores.
if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
let ignores = fs::ignores_from_path(path, &settings.per_file_ignores);

View File

@@ -212,7 +212,7 @@ impl RuleSelector {
// Always include rules that are not in preview or the nursery
!(rule.is_preview() || rule.is_nursery())
// Backwards compatibility allows selection of nursery rules by exact code or dedicated group
|| ((matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. })) && rule.is_nursery())
|| (matches!(self, RuleSelector::Rule { .. }) || matches!(self, RuleSelector::Nursery { .. }) && rule.is_nursery())
// Enabling preview includes all preview or nursery rules
|| preview.is_enabled()
})

View File

@@ -718,7 +718,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
if outer_name.value == "list" {
tree = Expression::Call(Box::new((*inner_call).clone()));
} else {
// If the `reverse` argument is used...
// If the `reverse` argument is used
let args = if inner_call.args.iter().any(|arg| {
matches!(
arg.keyword,
@@ -770,28 +770,6 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
.collect_vec()
} else {
let mut args = inner_call.args.clone();
// If necessary, parenthesize a generator expression, as a generator expression must
// be parenthesized if it's not a solitary argument. For example, given:
// ```python
// reversed(sorted(i for i in range(42)))
// ```
// Rewrite as:
// ```python
// sorted((i for i in range(42)), reverse=True)
// ```
if let [arg] = args.as_mut_slice() {
if matches!(arg.value, Expression::GeneratorExp(_)) {
if arg.value.lpar().is_empty() && arg.value.rpar().is_empty() {
arg.value = arg
.value
.clone()
.with_parens(LeftParen::default(), RightParen::default());
}
}
}
// Add the `reverse=True` argument.
args.push(Arg {
value: Expression::Name(Box::new(Name {
value: "True",

View File

@@ -123,7 +123,7 @@ C413.py:8:1: C413 [*] Unnecessary `reversed` call around `sorted()`
8 |+sorted(x, reverse=False, key=lambda e: e)
9 9 | reversed(sorted(x, reverse=False))
10 10 |
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
11 11 | def reversed(*args, **kwargs):
C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
@@ -132,7 +132,7 @@ C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
9 | reversed(sorted(x, reverse=False))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
10 |
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
11 | def reversed(*args, **kwargs):
|
= help: Remove unnecessary `reversed` call
@@ -143,45 +143,7 @@ C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
9 |-reversed(sorted(x, reverse=False))
9 |+sorted(x, reverse=True)
10 10 |
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
12 12 | reversed(sorted(i for i in range(42)))
C413.py:12:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
12 | reversed(sorted(i for i in range(42)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
13 | reversed(sorted((i for i in range(42)), reverse=True))
|
= help: Remove unnecessary `reversed` call
Suggested fix
9 9 | reversed(sorted(x, reverse=False))
10 10 |
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
12 |-reversed(sorted(i for i in range(42)))
12 |+sorted((i for i in range(42)), reverse=True)
13 13 | reversed(sorted((i for i in range(42)), reverse=True))
14 14 |
15 15 |
C413.py:13:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
12 | reversed(sorted(i for i in range(42)))
13 | reversed(sorted((i for i in range(42)), reverse=True))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
= help: Remove unnecessary `reversed` call
Suggested fix
10 10 |
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
12 12 | reversed(sorted(i for i in range(42)))
13 |-reversed(sorted((i for i in range(42)), reverse=True))
13 |+sorted((i for i in range(42)), reverse=False)
14 14 |
15 15 |
16 16 | def reversed(*args, **kwargs):
11 11 | def reversed(*args, **kwargs):
12 12 | return None

View File

@@ -1691,10 +1691,7 @@ fn common_section(
}
if checker.enabled(Rule::NoBlankLineBeforeSection) {
if !context
.previous_line()
.is_some_and(|line| line.trim().is_empty())
{
if !context.previous_line().is_some_and(str::is_empty) {
let mut diagnostic = Diagnostic::new(
NoBlankLineBeforeSection {
name: context.section_name().to_string(),

View File

@@ -496,6 +496,5 @@ sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters"
530 |+ ----------
530 531 | ===========
531 532 | """
532 533 |

View File

@@ -138,52 +138,16 @@ const PIPES_TO_SHLEX: &[&str] = &["quote"];
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING: &[&str] = &[
"AbstractSet",
"AnyStr",
"AsyncIterable",
"AsyncIterator",
"Awaitable",
"BinaryIO",
"Callable",
"ClassVar",
"Collection",
"Container",
"ContextManager",
"Coroutine",
"DefaultDict",
"Dict",
"FrozenSet",
"Generator",
"Generic",
"Hashable",
"IO",
"ItemsView",
"Iterable",
"Iterator",
"KeysView",
"List",
"Mapping",
"MappingView",
"Match",
"MutableMapping",
"MutableSequence",
"MutableSet",
"Optional",
"Pattern",
"Reversible",
"Sequence",
"Set",
"Sized",
"TYPE_CHECKING",
"Text",
"TextIO",
"Tuple",
"Type",
"Union",
"ValuesView",
"cast",
"no_type_check",
"no_type_check_decorator",
// Introduced in Python 3.5.2, but `typing_extensions` contains backported bugfixes and
// optimizations,
// "NewType",
@@ -201,7 +165,6 @@ const TYPING_EXTENSIONS_TO_TYPING_37: &[&str] = &[
"ChainMap",
"Counter",
"Deque",
"ForwardRef",
"NoReturn",
];
@@ -324,18 +287,6 @@ const TYPING_EXTENSIONS_TO_TYPING_311: &[&str] = &[
// Members of `typing_extensions` that were moved to `typing`.
const TYPING_EXTENSIONS_TO_TYPING_312: &[&str] = &[
"NamedTuple",
// Introduced in Python 3.8, but `typing_extensions` backports a ton of optimizations that were
// added in Python 3.12.
"Protocol",
"SupportsAbs",
"SupportsBytes",
"SupportsComplex",
"SupportsFloat",
"SupportsInt",
"SupportsRound",
"TypedDict",
"Unpack",
// Introduced in Python 3.11, but `typing_extensions` backports the `frozen_default` argument,
// which was introduced in Python 3.12.
"dataclass_transform",

View File

@@ -931,7 +931,7 @@ UP035.py:74:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
74 |+from collections.abc import AsyncGenerator
75 75 | from typing import Reversible
76 76 | from typing import Generator
77 77 | from typing import Callable
77 77 |
UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
|
@@ -940,7 +940,6 @@ UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
75 | from typing import Reversible
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
76 | from typing import Generator
77 | from typing import Callable
|
= help: Import from `collections.abc`
@@ -951,8 +950,8 @@ UP035.py:75:1: UP035 [*] Import from `collections.abc` instead: `Reversible`
75 |-from typing import Reversible
75 |+from collections.abc import Reversible
76 76 | from typing import Generator
77 77 | from typing import Callable
78 78 | from typing import cast
77 77 |
78 78 | # OK
UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
|
@@ -960,8 +959,8 @@ UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
75 | from typing import Reversible
76 | from typing import Generator
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
77 | from typing import Callable
78 | from typing import cast
77 |
78 | # OK
|
= help: Import from `collections.abc`
@@ -971,63 +970,23 @@ UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
75 75 | from typing import Reversible
76 |-from typing import Generator
76 |+from collections.abc import Generator
77 77 | from typing import Callable
78 78 | from typing import cast
79 79 |
77 77 |
78 78 | # OK
79 79 | from a import b
UP035.py:77:1: UP035 [*] Import from `collections.abc` instead: `Callable`
UP035.py:88:1: UP035 [*] Import from `typing` instead: `dataclass_transform`
|
75 | from typing import Reversible
76 | from typing import Generator
77 | from typing import Callable
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
78 | from typing import cast
|
= help: Import from `collections.abc`
Suggested fix
74 74 | from typing import AsyncGenerator
75 75 | from typing import Reversible
76 76 | from typing import Generator
77 |-from typing import Callable
77 |+from collections.abc import Callable
78 78 | from typing import cast
79 79 |
80 80 | # OK
UP035.py:87:1: UP035 [*] Import from `typing` instead: `NamedTuple`
|
86 | # Ok: `typing_extensions` contains backported improvements.
87 | from typing_extensions import NamedTuple
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
88 |
89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
= help: Import from `typing`
Suggested fix
84 84 | from typing_extensions import SupportsIndex
85 85 |
86 86 | # Ok: `typing_extensions` contains backported improvements.
87 |-from typing_extensions import NamedTuple
87 |+from typing import NamedTuple
88 88 |
89 89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
90 90 | from typing_extensions import dataclass_transform
UP035.py:90:1: UP035 [*] Import from `typing` instead: `dataclass_transform`
|
89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
90 | from typing_extensions import dataclass_transform
87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
88 | from typing_extensions import dataclass_transform
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
|
= help: Import from `typing`
Suggested fix
87 87 | from typing_extensions import NamedTuple
88 88 |
89 89 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
90 |-from typing_extensions import dataclass_transform
90 |+from typing import dataclass_transform
85 85 | from typing_extensions import NamedTuple
86 86 |
87 87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
88 |-from typing_extensions import dataclass_transform
88 |+from typing import dataclass_transform

View File

@@ -1,36 +0,0 @@
use ruff_python_ast as ast;
use ruff_python_codegen::Generator;
use ruff_text_size::TextRange;
/// Format a code snippet to call `name.method()`.
pub(super) fn generate_method_call(name: &str, method: &str, generator: Generator) -> String {
// Construct `name`.
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Construct `name.method`.
let attr = ast::ExprAttribute {
value: Box::new(var.into()),
attr: ast::Identifier::new(method.to_string(), TextRange::default()),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Make it into a call `name.method()`
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
// And finally, turn it into a statement.
let stmt = ast::StmtExpr {
value: Box::new(call.into()),
range: TextRange::default(),
};
generator.stmt(&stmt.into())
}

View File

@@ -1,6 +1,5 @@
//! Rules from [refurb](https://pypi.org/project/refurb/)/
mod helpers;
pub(crate) mod rules;
#[cfg(test)]
@@ -17,7 +16,6 @@ mod tests {
#[test_case(Rule::RepeatedAppend, Path::new("FURB113.py"))]
#[test_case(Rule::DeleteFullSlice, Path::new("FURB131.py"))]
#[test_case(Rule::CheckAndRemoveFromSet, Path::new("FURB132.py"))]
#[test_case(Rule::SliceCopy, Path::new("FURB145.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -18,11 +18,6 @@ use crate::registry::AsRule;
/// If an element should be removed from a set if it is present, it is more
/// succinct and idiomatic to use `discard`.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect sets that are instantiated as literals or annotated
/// with a type annotation.
///
/// ## Example
/// ```python
/// nums = {123, 456}

View File

@@ -1,13 +1,13 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_codegen::Generator;
use ruff_python_semantic::analyze::typing::{is_dict, is_list};
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_text_size::Ranged;
use ruff_text_size::{Ranged, TextRange};
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::refurb::helpers::generate_method_call;
/// ## What it does
/// Checks for `del` statements that delete the entire slice of a list or
@@ -17,11 +17,6 @@ use crate::rules::refurb::helpers::generate_method_call;
/// It's is faster and more succinct to remove all items via the `clear()`
/// method.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect lists and dictionaries that are instantiated as
/// literals or annotated with a type annotation.
///
/// ## Example
/// ```python
/// names = {"key": "value"}
@@ -70,7 +65,7 @@ pub(crate) fn delete_full_slice(checker: &mut Checker, delete: &ast::StmtDelete)
// Fix is only supported for single-target deletions.
if checker.patch(diagnostic.kind.rule()) && delete.targets.len() == 1 {
let replacement = generate_method_call(name, "clear", checker.generator());
let replacement = make_suggestion(name, checker.generator());
diagnostic.set_fix(Fix::suggested(Edit::replacement(
replacement,
delete.start(),
@@ -123,3 +118,38 @@ fn match_full_slice<'a>(expr: &'a Expr, semantic: &SemanticModel) -> Option<&'a
// Name is needed for the fix suggestion.
Some(name)
}
/// Make fix suggestion for the given name, ie `name.clear()`.
fn make_suggestion(name: &str, generator: Generator) -> String {
// Here we construct `var.clear()`
//
// And start with construction of `var`
let var = ast::ExprName {
id: name.to_string(),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Make `var.clear`.
let attr = ast::ExprAttribute {
value: Box::new(var.into()),
attr: ast::Identifier::new("clear".to_string(), TextRange::default()),
ctx: ast::ExprContext::Load,
range: TextRange::default(),
};
// Make it into a call `var.clear()`
let call = ast::ExprCall {
func: Box::new(attr.into()),
arguments: ast::Arguments {
args: vec![],
keywords: vec![],
range: TextRange::default(),
},
range: TextRange::default(),
};
// And finally, turn it into a statement.
let stmt = ast::StmtExpr {
value: Box::new(call.into()),
range: TextRange::default(),
};
generator.stmt(&stmt.into())
}

View File

@@ -1,9 +1,7 @@
pub(crate) use check_and_remove_from_set::*;
pub(crate) use delete_full_slice::*;
pub(crate) use repeated_append::*;
pub(crate) use slice_copy::*;
mod check_and_remove_from_set;
mod delete_full_slice;
mod repeated_append;
mod slice_copy;

View File

@@ -21,11 +21,6 @@ use crate::registry::AsRule;
/// a single `extend`. Each `append` resizes the list individually, whereas an
/// `extend` can resize the list once for all elements.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect lists that are instantiated as literals or annotated
/// with a type annotation.
///
/// ## Example
/// ```python
/// nums = [1, 2, 3]

View File

@@ -1,109 +0,0 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::typing::is_list;
use ruff_python_semantic::{Binding, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::refurb::helpers::generate_method_call;
/// ## What it does
/// Checks for unbounded slice expressions to copy a list.
///
/// ## Why is this bad?
/// The `list#copy` method is more readable and consistent with copying other
/// types.
///
/// ## Known problems
/// This rule is prone to false negatives due to type inference limitations,
/// as it will only detect lists that are instantiated as literals or annotated
/// with a type annotation.
///
/// ## Example
/// ```python
/// a = [1, 2, 3]
/// b = a[:]
/// ```
///
/// Use instead:
/// ```python
/// a = [1, 2, 3]
/// b = a.copy()
/// ```
///
/// ## References
/// - [Python documentation: Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types)
#[violation]
pub struct SliceCopy;
impl Violation for SliceCopy {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Prefer `copy` method over slicing")
}
fn autofix_title(&self) -> Option<String> {
Some("Replace with `copy()`".to_string())
}
}
/// FURB145
pub(crate) fn slice_copy(checker: &mut Checker, subscript: &ast::ExprSubscript) {
if subscript.ctx.is_store() || subscript.ctx.is_del() {
return;
}
let Some(name) = match_list_full_slice(subscript, checker.semantic()) else {
return;
};
let mut diagnostic = Diagnostic::new(SliceCopy, subscript.range());
if checker.patch(diagnostic.kind.rule()) {
let replacement = generate_method_call(name, "copy", checker.generator());
diagnostic.set_fix(Fix::suggested(Edit::replacement(
replacement,
subscript.start(),
subscript.end(),
)));
}
checker.diagnostics.push(diagnostic);
}
/// Matches `obj[:]` where `obj` is a list.
fn match_list_full_slice<'a>(
subscript: &'a ast::ExprSubscript,
semantic: &SemanticModel,
) -> Option<&'a str> {
// Check that it is `obj[:]`.
if !matches!(
subscript.slice.as_ref(),
Expr::Slice(ast::ExprSlice {
lower: None,
upper: None,
step: None,
range: _,
})
) {
return None;
}
let ast::ExprName { id, .. } = subscript.value.as_name_expr()?;
// Check that `obj` is a list.
let scope = semantic.current_scope();
let bindings: Vec<&Binding> = scope
.get_all(id)
.map(|binding_id| semantic.binding(binding_id))
.collect();
let [binding] = bindings.as_slice() else {
return None;
};
if !is_list(binding, semantic) {
return None;
}
Some(id)
}

View File

@@ -1,128 +0,0 @@
---
source: crates/ruff/src/rules/refurb/mod.rs
---
FURB145.py:4:5: FURB145 [*] Prefer `copy` method over slicing
|
3 | # Errors.
4 | a = l[:]
| ^^^^ FURB145
5 | b, c = 1, l[:]
6 | d, e = l[:], 1
|
= help: Replace with `copy()`
Suggested fix
1 1 | l = [1, 2, 3, 4, 5]
2 2 |
3 3 | # Errors.
4 |-a = l[:]
4 |+a = l.copy()
5 5 | b, c = 1, l[:]
6 6 | d, e = l[:], 1
7 7 | m = l[::]
FURB145.py:5:11: FURB145 [*] Prefer `copy` method over slicing
|
3 | # Errors.
4 | a = l[:]
5 | b, c = 1, l[:]
| ^^^^ FURB145
6 | d, e = l[:], 1
7 | m = l[::]
|
= help: Replace with `copy()`
Suggested fix
2 2 |
3 3 | # Errors.
4 4 | a = l[:]
5 |-b, c = 1, l[:]
5 |+b, c = 1, l.copy()
6 6 | d, e = l[:], 1
7 7 | m = l[::]
8 8 | l[:]
FURB145.py:6:8: FURB145 [*] Prefer `copy` method over slicing
|
4 | a = l[:]
5 | b, c = 1, l[:]
6 | d, e = l[:], 1
| ^^^^ FURB145
7 | m = l[::]
8 | l[:]
|
= help: Replace with `copy()`
Suggested fix
3 3 | # Errors.
4 4 | a = l[:]
5 5 | b, c = 1, l[:]
6 |-d, e = l[:], 1
6 |+d, e = l.copy(), 1
7 7 | m = l[::]
8 8 | l[:]
9 9 | print(l[:])
FURB145.py:7:5: FURB145 [*] Prefer `copy` method over slicing
|
5 | b, c = 1, l[:]
6 | d, e = l[:], 1
7 | m = l[::]
| ^^^^^ FURB145
8 | l[:]
9 | print(l[:])
|
= help: Replace with `copy()`
Suggested fix
4 4 | a = l[:]
5 5 | b, c = 1, l[:]
6 6 | d, e = l[:], 1
7 |-m = l[::]
7 |+m = l.copy()
8 8 | l[:]
9 9 | print(l[:])
10 10 |
FURB145.py:8:1: FURB145 [*] Prefer `copy` method over slicing
|
6 | d, e = l[:], 1
7 | m = l[::]
8 | l[:]
| ^^^^ FURB145
9 | print(l[:])
|
= help: Replace with `copy()`
Suggested fix
5 5 | b, c = 1, l[:]
6 6 | d, e = l[:], 1
7 7 | m = l[::]
8 |-l[:]
8 |+l.copy()
9 9 | print(l[:])
10 10 |
11 11 | # False negatives.
FURB145.py:9:7: FURB145 [*] Prefer `copy` method over slicing
|
7 | m = l[::]
8 | l[:]
9 | print(l[:])
| ^^^^ FURB145
10 |
11 | # False negatives.
|
= help: Replace with `copy()`
Suggested fix
6 6 | d, e = l[:], 1
7 7 | m = l[::]
8 8 | l[:]
9 |-print(l[:])
9 |+print(l.copy())
10 10 |
11 11 | # False negatives.
12 12 | aa = a[:] # Type inference.

View File

@@ -10,8 +10,6 @@ pub(crate) use mutable_class_default::*;
pub(crate) use mutable_dataclass_default::*;
pub(crate) use pairwise_over_zipped::*;
pub(crate) use static_key_dict_comprehension::*;
#[cfg(feature = "test-rules")]
pub(crate) use test_rules::*;
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
#[cfg(feature = "unreachable-code")]
pub(crate) use unreachable::*;
@@ -31,8 +29,6 @@ mod mutable_class_default;
mod mutable_dataclass_default;
mod pairwise_over_zipped;
mod static_key_dict_comprehension;
#[cfg(feature = "test-rules")]
mod test_rules;
mod unnecessary_iterable_allocation_for_first_element;
#[cfg(feature = "unreachable-code")]
pub(crate) mod unreachable;

View File

@@ -1,83 +0,0 @@
use ruff_diagnostics::{AutofixKind, Violation};
use ruff_macros::{derive_message_formats, violation};
/// ## What it does
/// Fake rule for testing.
///
/// ## Why is this bad?
/// Tests must pass!
///
/// ## Example
/// ```python
/// foo
/// ```
///
/// Use instead:
/// ```python
/// bar
/// ```
#[violation]
pub struct StableTestRule;
impl Violation for StableTestRule {
const AUTOFIX: AutofixKind = AutofixKind::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Hey this is a stable test rule.")
}
}
/// ## What it does
/// Fake rule for testing.
///
/// ## Why is this bad?
/// Tests must pass!
///
/// ## Example
/// ```python
/// foo
/// ```
///
/// Use instead:
/// ```python
/// bar
/// ```
#[violation]
pub struct PreviewTestRule;
impl Violation for PreviewTestRule {
const AUTOFIX: AutofixKind = AutofixKind::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Hey this is a preview test rule.")
}
}
/// ## What it does
/// Fake rule for testing.
///
/// ## Why is this bad?
/// Tests must pass!
///
/// ## Example
/// ```python
/// foo
/// ```
///
/// Use instead:
/// ```python
/// bar
/// ```
#[violation]
pub struct NurseryTestRule;
impl Violation for NurseryTestRule {
const AUTOFIX: AutofixKind = AutofixKind::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Hey this is a nursery test rule.")
}
}

View File

@@ -36,7 +36,7 @@ ruff_text_size = { path = "../ruff_text_size" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
argfile = { version = "0.1.6" }
argfile = { version = "0.1.5" }
bincode = { version = "1.3.3" }
bitflags = { workspace = true }
cachedir = { version = "0.3.0" }
@@ -69,7 +69,6 @@ walkdir = { version = "2.3.2" }
wild = { version = "2" }
[dev-dependencies]
ruff = { path = "../ruff", features = ["clap", "test-rules"] }
assert_cmd = { version = "2.0.8" }
# Avoid writing colored snapshots when running tests from the terminal
colored = { workspace = true, features = ["no-color"]}

View File

@@ -249,16 +249,16 @@ fn show_statistics() {
#[test]
fn nursery_prefix() {
// Should only detect RUF900, but not the unstable test rules
let args = ["--select", "RUF9"];
// `--select E` should detect E741, but not E225, which is in the nursery.
let args = ["--select", "E"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: E741 Ambiguous variable name: `I`
Found 1 error.
----- stderr -----
@@ -267,17 +267,17 @@ fn nursery_prefix() {
#[test]
fn nursery_all() {
// Should detect RUF900, but not the unstable test rules
// `--select ALL` should detect E741, but not E225, which is in the nursery.
let args = ["--select", "ALL"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:1: D100 Missing docstring in public module
-:1:1: RUF900 Hey this is a stable test rule.
Found 2 errors.
----- stderr -----
@@ -288,210 +288,18 @@ fn nursery_all() {
#[test]
fn nursery_direct() {
let args = ["--select", "RUF912"];
// `--select E225` should detect E225.
let args = ["--select", "E225"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF912 Hey this is a nursery test rule.
-:1:2: E225 Missing whitespace around operator
Found 1 error.
----- stderr -----
warning: Selection of nursery rule `RUF912` without the `--preview` flag is deprecated.
"###);
}
#[test]
fn nursery_group_selector() {
// Only nursery rules should be detected e.g. RUF912
let args = ["--select", "NURSERY"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:1: RUF912 Hey this is a nursery test rule.
Found 2 errors.
----- stderr -----
warning: The `NURSERY` selector has been deprecated. Use the `--preview` flag instead.
"###);
}
#[test]
fn nursery_group_selector_preview_enabled() {
// A warning should be displayed due to deprecated selector usage
let args = ["--select", "NURSERY", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:1: RUF912 Hey this is a nursery test rule.
Found 2 errors.
----- stderr -----
warning: The `NURSERY` selector has been deprecated. Use the `PREVIEW` selector instead.
"###);
}
#[test]
fn preview_enabled_prefix() {
// All the RUF9XX test rules should be triggered
let args = ["--select", "RUF9", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 3 errors.
----- stderr -----
"###);
}
#[test]
fn preview_enabled_all() {
let args = ["--select", "ALL", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: D100 Missing docstring in public module
-:1:1: CPY001 Missing copyright notice at top of file
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 5 errors.
----- stderr -----
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
"###);
}
#[test]
fn preview_enabled_direct() {
// Should be enabled without warning
let args = ["--select", "RUF911", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF911 Hey this is a preview test rule.
Found 1 error.
----- stderr -----
"###);
}
#[test]
fn preview_disabled_direct() {
// RUF911 is preview not nursery so selecting should be empty
let args = ["--select", "RUF911"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `RUF911` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_disabled_prefix_empty() {
// Warns that the selection is empty since all of the RUF91 rules are in preview
let args = ["--select", "RUF91"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `RUF91` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_disabled_group_selector() {
// `--select PREVIEW` should warn without the `--preview` flag
let args = ["--select", "PREVIEW"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `PREVIEW` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_enabled_group_selector() {
// `--select PREVIEW` is okay with the `--preview` flag and shouldn't warn
let args = ["--select", "PREVIEW", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 3 errors.
----- stderr -----
"###);
}
#[test]
fn preview_enabled_group_ignore() {
// Should detect stable and unstable rules, RUF9 is more specific than PREVIEW so ignore has no effect
let args = ["--select", "RUF9", "--ignore", "PREVIEW", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 3 errors.
----- stderr -----
"###);
}

View File

@@ -25,7 +25,7 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
}
AutofixKind::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
};
let preview_token = if rule.is_preview() || rule.is_nursery() {
let preview_token = if rule.is_preview() {
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
} else {
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")

View File

@@ -361,7 +361,7 @@ where
f.write_element(FormatElement::Text {
text: self.text.to_string().into_boxed_str(),
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
text_width: TextWidth::from_text(self.text, f.options().tab_width()),
});
Ok(())
@@ -393,10 +393,8 @@ where
let slice = source_code.slice(self.range);
debug_assert_no_newlines(slice.text(source_code));
let text_width = TextWidth::from_text(
slice.text(source_code),
f.context().options().indent_width(),
);
let text_width =
TextWidth::from_text(slice.text(source_code), f.context().options().tab_width());
f.write_element(FormatElement::SourceCodeSlice { slice, text_width });
@@ -919,10 +917,8 @@ where
/// use ruff_formatter::prelude::*;
///
/// # fn main() -> FormatResult<()> {
/// use ruff_formatter::IndentWidth;
/// let context = SimpleFormatContext::new(SimpleFormatOptions {
/// indent_style: IndentStyle::Space,
/// indent_width: IndentWidth::try_from(4).unwrap(),
/// indent_style: IndentStyle::Space(4),
/// ..SimpleFormatOptions::default()
/// });
///

View File

@@ -10,7 +10,7 @@ use unicode_width::UnicodeWidthChar;
use crate::format_element::tag::{GroupMode, LabelId, Tag};
use crate::source_code::SourceCodeSlice;
use crate::{IndentWidth, TagKind};
use crate::{TabWidth, TagKind};
use ruff_text_size::TextSize;
/// Language agnostic IR for formatting source code.
@@ -432,12 +432,12 @@ pub enum TextWidth {
}
impl TextWidth {
pub fn from_text(text: &str, indent_width: IndentWidth) -> TextWidth {
pub fn from_text(text: &str, tab_width: TabWidth) -> TextWidth {
let mut width = 0u32;
for c in text.chars() {
let char_width = match c {
'\t' => indent_width.value(),
'\t' => tab_width.value(),
'\n' => return TextWidth::Multiline,
#[allow(clippy::cast_possible_truncation)]
c => c.width().unwrap_or(0) as u32,

View File

@@ -9,7 +9,7 @@ use crate::prelude::*;
use crate::source_code::SourceCode;
use crate::{
format, write, BufferExtensions, Format, FormatContext, FormatElement, FormatOptions,
FormatResult, Formatter, IndentStyle, IndentWidth, LineWidth, PrinterOptions,
FormatResult, Formatter, IndentStyle, LineWidth, PrinterOptions, TabWidth,
};
use super::tag::Tag;
@@ -213,11 +213,11 @@ struct IrFormatOptions;
impl FormatOptions for IrFormatOptions {
fn indent_style(&self) -> IndentStyle {
IndentStyle::Space
IndentStyle::Space(2)
}
fn indent_width(&self) -> IndentWidth {
IndentWidth::default()
fn tab_width(&self) -> TabWidth {
TabWidth::default()
}
fn line_width(&self) -> LineWidth {
@@ -227,7 +227,7 @@ impl FormatOptions for IrFormatOptions {
fn as_print_options(&self) -> PrinterOptions {
PrinterOptions {
line_width: self.line_width(),
indent_style: IndentStyle::Space,
indent_style: IndentStyle::Space(2),
..PrinterOptions::default()
}
}

View File

@@ -52,20 +52,23 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
pub use group_id::GroupId;
use ruff_text_size::{TextRange, TextSize};
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum IndentStyle {
/// Use tabs to indent code.
/// Tab
#[default]
Tab,
/// Use [`IndentWidth`] spaces to indent code.
Space,
/// Space, with its quantity
Space(u8),
}
impl IndentStyle {
pub const DEFAULT_SPACES: u8 = 2;
/// Returns `true` if this is an [`IndentStyle::Tab`].
pub const fn is_tab(&self) -> bool {
matches!(self, IndentStyle::Tab)
@@ -73,42 +76,58 @@ impl IndentStyle {
/// Returns `true` if this is an [`IndentStyle::Space`].
pub const fn is_space(&self) -> bool {
matches!(self, IndentStyle::Space)
matches!(self, IndentStyle::Space(_))
}
}
impl FromStr for IndentStyle {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"tab" | "Tabs" => Ok(Self::Tab),
"space" | "Spaces" => Ok(Self::Space(IndentStyle::DEFAULT_SPACES)),
// TODO: replace this error with a diagnostic
v => {
let v = v.strip_prefix("Spaces, size: ").unwrap_or(v);
u8::from_str(v)
.map(Self::Space)
.map_err(|_| "Value not supported for IndentStyle")
}
}
}
}
impl std::fmt::Display for IndentStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IndentStyle::Tab => std::write!(f, "tab"),
IndentStyle::Space => std::write!(f, "space"),
IndentStyle::Tab => std::write!(f, "Tab"),
IndentStyle::Space(size) => std::write!(f, "Spaces, size: {size}"),
}
}
}
/// The visual width of a indentation.
///
/// Determines the visual width of a tab character (`\t`) and the number of
/// spaces per indent when using [`IndentStyle::Space`].
/// The visual width of a `\t` character.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndentWidth(NonZeroU8);
pub struct TabWidth(NonZeroU8);
impl IndentWidth {
impl TabWidth {
/// Return the numeric value for this [`LineWidth`]
pub const fn value(&self) -> u32 {
self.0.get() as u32
}
}
impl Default for IndentWidth {
impl Default for TabWidth {
fn default() -> Self {
Self(NonZeroU8::new(2).unwrap())
}
}
impl TryFrom<u8> for IndentWidth {
impl TryFrom<u8> for TabWidth {
type Error = TryFromIntError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
@@ -177,8 +196,16 @@ pub trait FormatOptions {
/// The indent style.
fn indent_style(&self) -> IndentStyle;
/// The visual width of a tab character.
fn tab_width(&self) -> TabWidth;
/// The visual width of an indent
fn indent_width(&self) -> IndentWidth;
fn indent_width(&self) -> u32 {
match self.indent_style() {
IndentStyle::Tab => self.tab_width().value(),
IndentStyle::Space(spaces) => u32::from(spaces),
}
}
/// What's the max width of a line. Defaults to 80.
fn line_width(&self) -> LineWidth;
@@ -223,7 +250,6 @@ impl FormatContext for SimpleFormatContext {
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct SimpleFormatOptions {
pub indent_style: IndentStyle,
pub indent_width: IndentWidth,
pub line_width: LineWidth,
}
@@ -232,8 +258,8 @@ impl FormatOptions for SimpleFormatOptions {
self.indent_style
}
fn indent_width(&self) -> IndentWidth {
self.indent_width
fn tab_width(&self) -> TabWidth {
TabWidth::default()
}
fn line_width(&self) -> LineWidth {
@@ -244,7 +270,6 @@ impl FormatOptions for SimpleFormatOptions {
PrinterOptions {
line_width: self.line_width,
indent_style: self.indent_style,
indent_width: self.indent_width,
source_map_generation: SourceMapGeneration::Enabled,
..PrinterOptions::default()
}

View File

@@ -367,7 +367,7 @@ impl<'a> Printer<'a> {
if !self.state.pending_indent.is_empty() {
let (indent_char, repeat_count) = match self.options.indent_style() {
IndentStyle::Tab => ('\t', 1),
IndentStyle::Space => (' ', self.options.indent_width()),
IndentStyle::Space(count) => (' ', count),
};
let indent = std::mem::take(&mut self.state.pending_indent);
@@ -764,7 +764,7 @@ impl<'a> Printer<'a> {
#[allow(clippy::cast_possible_truncation)]
let char_width = if char == '\t' {
self.options.indent_width.value()
self.options.tab_width.value()
} else {
// SAFETY: A u32 is sufficient to represent the width of a file <= 4GB
char.width().unwrap_or(0) as u32
@@ -1347,7 +1347,7 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {
} else {
for c in text.chars() {
let char_width = match c {
'\t' => self.options().indent_width.value(),
'\t' => self.options().tab_width.value(),
'\n' => {
if self.must_be_flat {
return Fits::No;
@@ -1501,7 +1501,7 @@ mod tests {
use crate::printer::{LineEnding, Printer, PrinterOptions};
use crate::source_code::SourceCode;
use crate::{
format_args, write, Document, FormatState, IndentStyle, IndentWidth, LineWidth, Printed,
format_args, write, Document, FormatState, IndentStyle, LineWidth, Printed, TabWidth,
VecBuffer,
};
@@ -1509,7 +1509,7 @@ mod tests {
format_with_options(
root,
PrinterOptions {
indent_style: IndentStyle::Space,
indent_style: IndentStyle::Space(2),
..PrinterOptions::default()
},
)
@@ -1653,7 +1653,7 @@ two lines`,
fn it_use_the_indent_character_specified_in_the_options() {
let options = PrinterOptions {
indent_style: IndentStyle::Tab,
indent_width: IndentWidth::try_from(4).unwrap(),
tab_width: TabWidth::try_from(4).unwrap(),
line_width: LineWidth::try_from(19).unwrap(),
..PrinterOptions::default()
};

View File

@@ -1,13 +1,10 @@
use crate::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
use crate::{FormatOptions, IndentStyle, LineWidth, TabWidth};
/// Options that affect how the [`crate::Printer`] prints the format tokens
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct PrinterOptions {
/// Width of a single tab character (does it equal 2, 4, ... spaces?)
pub indent_width: IndentWidth,
/// Whether the printer should use tabs or spaces to indent code.
pub indent_style: IndentStyle,
pub tab_width: TabWidth,
/// What's the max width of a line. Defaults to 80
pub line_width: LineWidth,
@@ -15,6 +12,9 @@ pub struct PrinterOptions {
/// The type of line ending to apply to the printed input
pub line_ending: LineEnding,
/// Whether the printer should use tabs or spaces to indent code and if spaces, by how many.
pub indent_style: IndentStyle,
/// Whether the printer should build a source map that allows mapping positions in the source document
/// to positions in the formatted document.
pub source_map_generation: SourceMapGeneration,
@@ -46,8 +46,8 @@ impl PrinterOptions {
}
#[must_use]
pub fn with_tab_width(mut self, width: IndentWidth) -> Self {
self.indent_width = width;
pub fn with_tab_width(mut self, width: TabWidth) -> Self {
self.tab_width = width;
self
}
@@ -58,7 +58,10 @@ impl PrinterOptions {
/// Width of an indent in characters.
pub(super) const fn indent_width(&self) -> u32 {
self.indent_width.value()
match self.indent_style {
IndentStyle::Tab => self.tab_width.value(),
IndentStyle::Space(count) => count as u32,
}
}
}

View File

@@ -22,7 +22,7 @@ itertools = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { version = "3.0.0", default-features = false, features = ["macros"] }
serde_with = { version = "3.0.0" }
thiserror = { workspace = true }
uuid = { workspace = true }

View File

@@ -1,3 +1,3 @@
{
"indent_width": 4
"tab_width": 8
}

View File

@@ -1,18 +1,22 @@
[
{
"indent_style": "Space",
"indent_width": 4
"indent_style": {
"Space": 4
},
"tab_width": 8
},
{
"indent_style": "Space",
"indent_width": 2
"indent_style": {
"Space": 2
},
"tab_width": 8
},
{
"indent_style": "Tab",
"indent_width": 8
"tab_width": 8
},
{
"indent_style": "Tab",
"indent_width": 4
"tab_width": 4
}
]

View File

@@ -91,3 +91,18 @@ f = "f"[:,]
g1 = "g"[(1):(2)]
g2 = "g"[(1):(2):(3)]
# https://github.com/astral-sh/ruff/issues/7316
section_header_data = byte_array[
byte_begin_index
+ byte_step_index * event_index : byte_begin_index
+ byte_step_index * (event_index + 1)
]
section_header_data2 = byte_array[
byte_begin_index
+ byte_step_index * event_index : byte_begin_index
+ byte_step_index : section_size
]

View File

@@ -1,10 +1,8 @@
[
{
"indent_style": "Space",
"indent_width": 4
"indent_style": { "Space": 4 }
},
{
"indent_style": "Space",
"indent_width": 2
"indent_style": { "Space": 2 }
}
]

View File

@@ -1,11 +1,9 @@
[
{
"indent_style": "Space",
"indent_width": 4
"indent_style": { "Space": 4 }
},
{
"indent_style": "Space",
"indent_width": 1
"indent_style": { "Space": 1 }
},
{
"indent_style": "Tab"

View File

@@ -1,11 +1,9 @@
[
{
"indent_style": "Space",
"indent_width": 4
"indent_style": { "Space": 4 }
},
{
"indent_style": "Space",
"indent_width": 2
"indent_style": { "Space": 2 }
},
{
"indent_style": "Tab"

View File

@@ -1,11 +1,8 @@
[
{
"indent_width": 2
"tab_width": 2
},
{
"indent_width": 4
},
{
"indent_width": 8
"tab_width": 4
}
]

View File

@@ -1,8 +1,8 @@
# Fits with tab width 2
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567")
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567890"
# Fits with tab width 4
(1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345")
1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345678"
# Fits with tab width 8
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901")
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234"

View File

@@ -353,7 +353,7 @@ impl Format<PyFormatContext<'_>> for FormatTrailingEndOfLineComment<'_> {
} else {
// Start with 2 because of the two leading spaces.
let width = 2u32.saturating_add(
TextWidth::from_text(&normalized_comment, f.options().indent_width())
TextWidth::from_text(&normalized_comment, f.options().tab_width())
.width()
.expect("Expected comment not to contain any newlines")
.value(),

View File

@@ -91,7 +91,7 @@ impl FormatNodeRule<ExprSlice> for FormatExprSlice {
if !all_simple && lower.is_some() {
space().fmt(f)?;
}
token(":").fmt(f)?;
write!(f, [soft_line_break(), token(":")])?;
// No upper node, no need for a space, e.g. `x[a() :]`
if !all_simple && upper.is_some() {
space().fmt(f)?;
@@ -125,7 +125,7 @@ impl FormatNodeRule<ExprSlice> for FormatExprSlice {
if !all_simple && (upper.is_some() || step.is_none()) {
space().fmt(f)?;
}
token(":").fmt(f)?;
write!(f, [soft_line_break(), token(":")])?;
// No step node, no need for a space
if !all_simple && step.is_some() {
space().fmt(f)?;

View File

@@ -47,7 +47,7 @@ where
text_len > 5
&& text_len
<= context.options().line_width().value() as usize
- context.options().indent_width().value() as usize
- context.options().indent_width() as usize
}
pub(crate) trait NeedsParentheses {

View File

@@ -2,7 +2,7 @@ use std::borrow::Cow;
use bitflags::bitflags;
use ruff_formatter::{format_args, write, FormatError};
use ruff_formatter::{format_args, write, FormatError, FormatOptions, TabWidth};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{self as ast, Constant, ExprConstant, ExprFString, ExpressionRef};
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
@@ -727,20 +727,22 @@ fn normalize_string(input: &str, quotes: StringQuotes, is_raw: bool) -> Cow<str>
/// For docstring indentation, black counts spaces as 1 and tabs by increasing the indentation up
/// to the next multiple of 8. This is effectively a port of
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs),
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61).
fn indentation_length(line: &str) -> TextSize {
let mut indentation = 0u32;
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61)
fn count_indentation_like_black(line: &str, tab_width: TabWidth) -> TextSize {
let mut indentation = TextSize::default();
for char in line.chars() {
if char == '\t' {
// Pad to the next multiple of tab_width
indentation += 8 - (indentation.rem_euclid(8));
indentation += TextSize::from(
tab_width.value() - (indentation.to_u32().rem_euclid(tab_width.value())),
);
} else if char.is_whitespace() {
indentation += u32::from(char.text_len());
indentation += char.text_len();
} else {
break;
return indentation;
}
}
TextSize::new(indentation)
indentation
}
/// Format a docstring by trimming whitespace and adjusting the indentation.
@@ -908,7 +910,7 @@ fn format_docstring(normalized: &NormalizedString, f: &mut PyFormatter) -> Forma
.clone()
// We don't want to count whitespace-only lines as miss-indented
.filter(|line| !line.trim().is_empty())
.map(indentation_length)
.map(|line| count_indentation_like_black(line, f.options().tab_width()))
.min()
.unwrap_or_default();
@@ -950,7 +952,7 @@ fn format_docstring_line(
line: &str,
is_last: bool,
offset: TextSize,
stripped_indentation_length: TextSize,
stripped_indentation: TextSize,
already_normalized: bool,
f: &mut PyFormatter,
) -> FormatResult<()> {
@@ -977,20 +979,21 @@ fn format_docstring_line(
// overindented, in which case we strip the additional whitespace (see example in
// [`format_docstring`] doc comment). We then prepend the in-docstring indentation to the
// string.
let indent_len = indentation_length(trim_end) - stripped_indentation_length;
let in_docstring_indent = " ".repeat(usize::from(indent_len)) + trim_end.trim_start();
let indent_len =
count_indentation_like_black(trim_end, f.options().tab_width()) - stripped_indentation;
let in_docstring_indent = " ".repeat(indent_len.to_usize()) + trim_end.trim_start();
text(&in_docstring_indent, Some(offset)).fmt(f)?;
} else {
// Take the string with the trailing whitespace removed, then also skip the leading
// whitespace
let trimmed_line_range =
TextRange::at(offset, trim_end.text_len()).add_start(stripped_indentation_length);
TextRange::at(offset, trim_end.text_len()).add_start(stripped_indentation);
if already_normalized {
source_text_slice(trimmed_line_range).fmt(f)?;
} else {
// All indents are ascii spaces, so the slicing is correct
text(
&trim_end[usize::from(stripped_indentation_length)..],
&trim_end[stripped_indentation.to_usize()..],
Some(trimmed_line_range.start()),
)
.fmt(f)?;
@@ -1009,14 +1012,25 @@ fn format_docstring_line(
#[cfg(test)]
mod tests {
use crate::expression::string::indentation_length;
use ruff_text_size::TextSize;
use ruff_formatter::TabWidth;
use crate::expression::string::count_indentation_like_black;
#[test]
fn test_indentation_like_black() {
assert_eq!(indentation_length("\t \t \t"), TextSize::new(24));
assert_eq!(indentation_length("\t \t"), TextSize::new(24));
assert_eq!(indentation_length("\t\t\t"), TextSize::new(24));
assert_eq!(indentation_length(" "), TextSize::new(4));
let tab_width = TabWidth::try_from(8).unwrap();
assert_eq!(
count_indentation_like_black("\t \t \t", tab_width).to_u32(),
24
);
assert_eq!(
count_indentation_like_black("\t \t", tab_width).to_u32(),
24
);
assert_eq!(
count_indentation_like_black("\t\t\t", tab_width).to_u32(),
24
);
assert_eq!(count_indentation_like_black(" ", tab_width).to_u32(), 4);
}
}

View File

@@ -1,5 +1,5 @@
use ruff_formatter::printer::{LineEnding, PrinterOptions, SourceMapGeneration};
use ruff_formatter::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
use ruff_formatter::{FormatOptions, IndentStyle, LineWidth, TabWidth};
use ruff_python_ast::PySourceType;
use std::path::Path;
use std::str::FromStr;
@@ -25,8 +25,8 @@ pub struct PyFormatOptions {
line_width: LineWidth,
/// The visual width of a tab character.
#[cfg_attr(feature = "serde", serde(default = "default_indent_width"))]
indent_width: IndentWidth,
#[cfg_attr(feature = "serde", serde(default = "default_tab_width"))]
tab_width: TabWidth,
line_ending: LineEnding,
@@ -49,11 +49,11 @@ fn default_line_width() -> LineWidth {
}
fn default_indent_style() -> IndentStyle {
IndentStyle::Space
IndentStyle::Space(4)
}
fn default_indent_width() -> IndentWidth {
IndentWidth::try_from(4).unwrap()
fn default_tab_width() -> TabWidth {
TabWidth::try_from(4).unwrap()
}
impl Default for PyFormatOptions {
@@ -62,7 +62,7 @@ impl Default for PyFormatOptions {
source_type: PySourceType::default(),
indent_style: default_indent_style(),
line_width: default_line_width(),
indent_width: default_indent_width(),
tab_width: default_tab_width(),
quote_style: QuoteStyle::default(),
line_ending: LineEnding::default(),
magic_trailing_comma: MagicTrailingComma::default(),
@@ -110,8 +110,8 @@ impl PyFormatOptions {
}
#[must_use]
pub fn with_indent_width(mut self, indent_width: IndentWidth) -> Self {
self.indent_width = indent_width;
pub fn with_tab_width(mut self, tab_width: TabWidth) -> Self {
self.tab_width = tab_width;
self
}
@@ -157,8 +157,8 @@ impl FormatOptions for PyFormatOptions {
self.indent_style
}
fn indent_width(&self) -> IndentWidth {
self.indent_width
fn tab_width(&self) -> TabWidth {
self.tab_width
}
fn line_width(&self) -> LineWidth {
@@ -167,7 +167,7 @@ impl FormatOptions for PyFormatOptions {
fn as_print_options(&self) -> PrinterOptions {
PrinterOptions {
indent_width: self.indent_width,
tab_width: self.tab_width,
line_width: self.line_width,
line_ending: self.line_ending,
indent_style: self.indent_style,

View File

@@ -253,11 +253,11 @@ impl fmt::Display for DisplayPyOptions<'_> {
f,
r#"indent-style = {indent_style}
line-width = {line_width}
indent-width = {indent_width}
tab-width = {tab_width}
quote-style = {quote_style:?}
magic-trailing-comma = {magic_trailing_comma:?}"#,
indent_style = self.0.indent_style(),
indent_width = self.0.indent_width().value(),
tab_width = self.0.tab_width().value(),
line_width = self.0.line_width().value(),
quote_style = self.0.quote_style(),
magic_trailing_comma = self.0.magic_trailing_comma()

View File

@@ -111,9 +111,9 @@ class TabbedIndent:
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 8
quote-style = Double
magic-trailing-comma = Respect
```
@@ -224,9 +224,9 @@ class TabbedIndent:
### Output 2
```
indent-style = space
indent-style = Spaces, size: 2
line-width = 88
indent-width = 2
tab-width = 8
quote-style = Double
magic-trailing-comma = Respect
```
@@ -337,9 +337,9 @@ class TabbedIndent:
### Output 3
```
indent-style = tab
indent-style = Tab
line-width = 88
indent-width = 8
tab-width = 8
quote-style = Double
magic-trailing-comma = Respect
```
@@ -450,9 +450,9 @@ class TabbedIndent:
### Output 4
```
indent-style = tab
indent-style = Tab
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -556,7 +556,7 @@ class TabbedIndent:
"""check for correct tabbed formatting
^^^^^^^^^^
Normal indented line
- autor
- autor
"""
```

View File

@@ -129,9 +129,9 @@ test_particular = [
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -277,9 +277,9 @@ test_particular = [
### Output 2
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Single
magic-trailing-comma = Respect
```

View File

@@ -97,6 +97,21 @@ f = "f"[:,]
g1 = "g"[(1):(2)]
g2 = "g"[(1):(2):(3)]
# https://github.com/astral-sh/ruff/issues/7316
section_header_data = byte_array[
byte_begin_index
+ byte_step_index * event_index : byte_begin_index
+ byte_step_index * (event_index + 1)
]
section_header_data2 = byte_array[
byte_begin_index
+ byte_step_index * event_index : byte_begin_index
+ byte_step_index : section_size
]
```
## Output
@@ -132,21 +147,25 @@ b1 = "b"[ # a
# Handle the spacing from the colon correctly with upper leading comments
c1 = "c"[
1: # e
1
: # e
# f
2
]
c2 = "c"[
1: # e
1
: # e
2
]
c3 = "c"[
1:
1
:
# f
2
]
c4 = "c"[
1: # f
1
: # f
2
]
@@ -155,7 +174,8 @@ d1 = "d"[ # comment
:
]
d2 = "d"[ # comment
1:
1
:
]
d3 = "d"[
1 # comment
@@ -191,6 +211,18 @@ f = "f"[:,]
# Regression test for https://github.com/astral-sh/ruff/issues/5733
g1 = "g"[(1):(2)]
g2 = "g"[(1):(2):(3)]
# https://github.com/astral-sh/ruff/issues/7316
section_header_data = byte_array[
byte_begin_index + byte_step_index * event_index
: byte_begin_index + byte_step_index * (event_index + 1)
]
section_header_data2 = byte_array[
byte_begin_index + byte_step_index * event_index
: byte_begin_index + byte_step_index
: section_size
]
```

View File

@@ -141,9 +141,9 @@ x = (b"""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa""" b"""bbbbbbbbbbbbbbbbbbbbbbbbbbb
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -310,9 +310,9 @@ x = (
### Output 2
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Single
magic-trailing-comma = Respect
```

View File

@@ -28,9 +28,9 @@ def test():
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -60,9 +60,9 @@ def test():
### Output 2
```
indent-style = space
indent-style = Spaces, size: 2
line-width = 88
indent-width = 2
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```

View File

@@ -9,9 +9,9 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_on_off
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -22,9 +22,9 @@ magic-trailing-comma = Respect
### Output 2
```
indent-style = space
indent-style = Spaces, size: 1
line-width = 88
indent-width = 1
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -35,9 +35,9 @@ magic-trailing-comma = Respect
### Output 3
```
indent-style = tab
indent-style = Tab
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```

View File

@@ -24,9 +24,9 @@ not_fixed
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -53,9 +53,9 @@ not_fixed
### Output 2
```
indent-style = space
indent-style = Spaces, size: 2
line-width = 88
indent-width = 2
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -82,9 +82,9 @@ not_fixed
### Output 3
```
indent-style = tab
indent-style = Tab
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```

View File

@@ -42,9 +42,9 @@ with (a,): # magic trailing comma
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -94,9 +94,9 @@ with (
### Output 2
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 4
tab-width = 4
quote-style = Double
magic-trailing-comma = Ignore
```

View File

@@ -175,7 +175,8 @@ raise ( # hey 2
# some comment
raise aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa[
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:bbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
:bbbbbbbbbbbbbbbbbbbbbbbbbb
]
raise (

View File

@@ -5,42 +5,21 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/tab_width.
## Input
```py
# Fits with tab width 2
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567")
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567890"
# Fits with tab width 4
(1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345")
1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345678"
# Fits with tab width 8
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901")
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234"
```
## Outputs
### Output 1
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 2
quote-style = Double
magic-trailing-comma = Respect
```
```py
# Fits with tab width 2
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567")
# Fits with tab width 4
(1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345")
# Fits with tab width 8
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901")
```
### Output 2
```
indent-style = space
line-width = 88
indent-width = 4
tab-width = 2
quote-style = Double
magic-trailing-comma = Respect
```
@@ -49,22 +28,22 @@ magic-trailing-comma = Respect
# Fits with tab width 2
(
1
+ " 012345678901234567890123456789012345678901234567890123456789012345678901234567"
+ " 012345678901234567890123456789012345678901234567890123456789012345678901234567890"
)
# Fits with tab width 4
(1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345")
1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345678"
# Fits with tab width 8
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901")
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234"
```
### Output 3
### Output 2
```
indent-style = space
indent-style = Spaces, size: 4
line-width = 88
indent-width = 8
tab-width = 4
quote-style = Double
magic-trailing-comma = Respect
```
@@ -72,18 +51,18 @@ magic-trailing-comma = Respect
```py
# Fits with tab width 2
(
1
+ " 012345678901234567890123456789012345678901234567890123456789012345678901234567"
1
+ " 012345678901234567890123456789012345678901234567890123456789012345678901234567890"
)
# Fits with tab width 4
(
1
+ " 0123456789012345678901234567890123456789012345678901234567890123456789012345"
1
+ " 0123456789012345678901234567890123456789012345678901234567890123456789012345678"
)
# Fits with tab width 8
(1 + " 012345678901234567890123456789012345678901234567890123456789012345678901")
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234"
```

View File

@@ -1,4 +1,3 @@
use std::num::NonZeroU16;
use std::path::Path;
use js_sys::Error;
@@ -9,10 +8,10 @@ use ruff::directives;
use ruff::line_width::{LineLength, TabSize};
use ruff::linter::{check_path, LinterResult};
use ruff::registry::AsRule;
use ruff::settings::types::{PreviewMode, PythonVersion};
use ruff::settings::types::PythonVersion;
use ruff::settings::{defaults, flags, Settings};
use ruff::source_kind::SourceKind;
use ruff_formatter::{FormatResult, Formatted, LineWidth};
use ruff_formatter::{FormatResult, Formatted};
use ruff_python_ast::{Mod, PySourceType};
use ruff_python_codegen::Stylist;
use ruff_python_formatter::{format_node, pretty_comments, PyFormatContext, PyFormatOptions};
@@ -238,7 +237,7 @@ impl Workspace {
pub fn format(&self, contents: &str) -> Result<String, Error> {
let parsed = ParsedModule::from_source(contents)?;
let formatted = parsed.format(&self.settings).map_err(into_error)?;
let formatted = parsed.format().map_err(into_error)?;
let printed = formatted.print().map_err(into_error)?;
Ok(printed.into_code())
@@ -246,7 +245,7 @@ impl Workspace {
pub fn format_ir(&self, contents: &str) -> Result<String, Error> {
let parsed = ParsedModule::from_source(contents)?;
let formatted = parsed.format(&self.settings).map_err(into_error)?;
let formatted = parsed.format().map_err(into_error)?;
Ok(format!("{formatted}"))
}
@@ -299,14 +298,9 @@ impl<'a> ParsedModule<'a> {
})
}
fn format(&self, settings: &Settings) -> FormatResult<Formatted<PyFormatContext>> {
fn format(&self) -> FormatResult<Formatted<PyFormatContext>> {
// TODO(konstin): Add an options for py/pyi to the UI (2/2)
let options = PyFormatOptions::from_source_type(PySourceType::default())
.with_preview(match settings.preview {
PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled,
PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled,
})
.with_line_width(LineWidth::from(NonZeroU16::from(settings.line_length)));
let options = PyFormatOptions::from_source_type(PySourceType::default());
format_node(
&self.module,

View File

@@ -27,7 +27,7 @@ use ruff::settings::types::{
Version,
};
use ruff::settings::{defaults, resolve_per_file_ignores, AllSettings, CliSettings, Settings};
use ruff::{fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION};
use ruff::{fs, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION};
use ruff_cache::cache_dir;
use rustc_hash::{FxHashMap, FxHashSet};
use shellexpand;
@@ -460,10 +460,7 @@ impl Configuration {
let mut carryover_ignores: Option<&[RuleSelector]> = None;
let mut carryover_unfixables: Option<&[RuleSelector]> = None;
// Store selectors for displaying warnings
let mut redirects = FxHashMap::default();
let mut deprecated_nursery_selectors = FxHashSet::default();
let mut ignored_preview_selectors = FxHashSet::default();
for selection in &self.rule_selections {
// If a selection only specifies extend-select we cannot directly
@@ -574,7 +571,8 @@ impl Configuration {
}
}
// Check for selections that require a warning
// We insert redirects into the hashmap so that we
// can warn the users about remapped rule codes.
for selector in selection
.select
.iter()
@@ -585,29 +583,6 @@ impl Configuration {
.chain(selection.unfixable.iter())
.chain(selection.extend_fixable.iter())
{
#[allow(deprecated)]
if matches!(selector, RuleSelector::Nursery) {
let suggestion = if preview.is_disabled() {
"Use the `--preview` flag instead."
} else {
"Use the `PREVIEW` selector instead."
};
warn_user_once!("The `NURSERY` selector has been deprecated. {suggestion}");
}
if preview.is_disabled() {
if let RuleSelector::Rule { prefix, .. } = selector {
if prefix.rules().any(|rule| rule.is_nursery()) {
deprecated_nursery_selectors.insert(selector);
}
}
// Check if the selector is empty because preview mode is disabled
if selector.rules(PreviewMode::Disabled).next().is_none() {
ignored_preview_selectors.insert(selector);
}
}
if let RuleSelector::Prefix {
prefix,
redirected_from: Some(redirect_from),
@@ -628,18 +603,6 @@ impl Configuration {
);
}
for selection in deprecated_nursery_selectors {
let (prefix, code) = selection.prefix_and_code();
warn_user!("Selection of nursery rule `{prefix}{code}` without the `--preview` flag is deprecated.",);
}
for selection in ignored_preview_selectors {
let (prefix, code) = selection.prefix_and_code();
warn_user!(
"Selection `{prefix}{code}` has no effect because the `--preview` flag was not included.",
);
}
let mut rules = RuleTable::empty();
for rule in select_set {
@@ -801,7 +764,7 @@ pub fn resolve_src(src: &[String], project_root: &Path) -> Result<Vec<PathBuf>>
#[cfg(test)]
mod tests {
use crate::configuration::{Configuration, RuleSelection};
use ruff::codes::{Flake8Copyright, Pycodestyle, Refurb};
use ruff::codes::{Flake8Copyright, Pycodestyle};
use ruff::registry::{Linter, Rule, RuleSet};
use ruff::settings::types::PreviewMode;
use ruff::RuleSelector;
@@ -851,8 +814,6 @@ mod tests {
Rule::QuadraticListSummation,
];
const PREVIEW_RULES: &[Rule] = &[Rule::SliceCopy];
#[allow(clippy::needless_pass_by_value)]
fn resolve_rules(
selections: impl IntoIterator<Item = RuleSelection>,
@@ -1138,29 +1099,6 @@ mod tests {
assert_eq!(actual, expected);
}
#[test]
fn select_rule_preview() {
let actual = resolve_rules(
[RuleSelection {
select: Some(vec![Refurb::_145.into()]),
..RuleSelection::default()
}],
Some(PreviewMode::Disabled),
);
let expected = RuleSet::empty();
assert_eq!(actual, expected);
let actual = resolve_rules(
[RuleSelection {
select: Some(vec![Refurb::_145.into()]),
..RuleSelection::default()
}],
Some(PreviewMode::Enabled),
);
let expected = RuleSet::from_rule(Rule::SliceCopy);
assert_eq!(actual, expected);
}
#[test]
fn select_preview() {
let actual = resolve_rules(
@@ -1180,9 +1118,7 @@ mod tests {
}],
Some(PreviewMode::Enabled),
);
let expected =
RuleSet::from_rules(NURSERY_RULES).union(&RuleSet::from_rules(PREVIEW_RULES));
let expected = RuleSet::from_rules(NURSERY_RULES);
assert_eq!(actual, expected);
}

2
ruff.schema.json generated
View File

@@ -2079,8 +2079,6 @@
"FURB13",
"FURB131",
"FURB132",
"FURB14",
"FURB145",
"G",
"G0",
"G00",

1
rust-toolchain Normal file
View File

@@ -0,0 +1 @@
1.72

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "1.72"

File diff suppressed because it is too large Load Diff

View File

@@ -6,31 +6,70 @@ authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
[tool.poetry.dependencies]
python = ">=3.10,<3.12"
autoflake = "*"
autopep8 = "*"
black = "*"
flake8 = "*"
isort = "*"
pycodestyle = "*"
pyflakes = "*"
pylint = "*"
yapf = "*"
autoflake = "^2.0.0"
flake8 = "^6.0.0"
pycodestyle = "^2.10.0"
pyflakes = "^3.0.1"
pylint = "^2.15.10"
black = "^22.12.0"
isort = "^5.11.4"
flake8-2020 = { version = "*", optional = true }
flake8-annotations = { version = "*", optional = true }
flake8-bandit = { version = "*", optional = true }
flake8-blind-except = { version = "*", optional = true }
# flake8-boolean-trap = { version = "*", optional = true }
flake8-bugbear = { version = "*", optional = true }
flake8-builtins = { version = "*", optional = true }
flake8-commas = { version = "*", optional = true }
flake8-comprehensions = { version = "*", optional = true }
flake8-datetimez = { version = "*", optional = true }
flake8-debugger = { version = "*", optional = true }
flake8-docstrings = { version = "*", optional = true }
# flake8-eradicate = { version = "*", optional = true }
flake8-errmsg = { version = "*", optional = true }
flake8-implicit-str-concat = { version = "*", optional = true }
# flake8-import-conventions = { version = "*", optional = true }
flake8-isort = { version = "*", optional = true }
flake8-pie = { version = "*", optional = true }
flake8-print = { version = "*", optional = true }
flake8-quotes = { version = "*", optional = true }
flake8-return = { version = "*", optional = true }
flake8-simplify = { version = "*", optional = true }
flake8-super = { version = "*", optional = true }
flake8-tidy-imports = { version = "*", optional = true }
pandas-vet = { version = "*", optional = true }
pep8-naming = { version = "*", optional = true }
[tool.poetry.dev-dependencies]
[tool.poetry.extras]
formatter = [
"black",
"yapf",
"autopep8",
]
linter = [
"autoflake",
"flake8",
"pycodestyle",
"pyflakes",
"pylint",
"isort",
plugins = [
"flake8-2020",
"flake8-annotations",
"flake8-bandit",
"flake8-blind-except",
# "flake8-boolean-trap",
"flake8-bugbear",
"flake8-builtins",
"flake8-commas",
"flake8-comprehensions",
"flake8-datetimez",
"flake8-debugger",
"flake8-docstrings",
# "flake8-eradicate",
"flake8-errmsg",
"flake8-implicit-str-concat",
# "flake8-import-conventions",
"flake8-isort",
"flake8-pie",
"flake8-print",
"flake8-quotes",
"flake8-return",
"flake8-simplify",
"flake8-super",
"flake8-tidy-imports",
"pandas-vet",
"pep8-naming",
]
[build-system]

View File

@@ -1,74 +0,0 @@
#!/usr/bin/env sh
###
# Benchmark the Ruff formatter's performance against a variety of similar tools.
#
# Expects to be run from the repo root after invoking `cargo build --release`,
# in an environment with access to `black`, `autopep8`, and `yapf` (most recently:
# `black` v23.9.1, `autopep8` v2.0.4, and `yapf` v0.40.1).
#
# Example usage:
#
# ./scripts/benchmarks/run_formatter.sh ~/workspace/zulip
###
TARGET_DIR=${1}
# In each case, ensure that we format the code in-place before invoking a given tool. This ensures
# a fair comparison across tools, since every tool is then running on a repository that already
# matches that tool's desired formatting.
#
# For example, if we're benchmarking Black's preview style, we first run `black --preview` over the
# target directory, thus ensuring that we're benchmarking preview style against a codebase that
# already conforms to it. The same goes for yapf, autoepp8, etc.
# Benchmark 1: Write to disk.
hyperfine --ignore-failure \
--prepare "./target/release/ruff format ${TARGET_DIR}" \
"./target/release/ruff format ${TARGET_DIR}" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe --preview" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe --preview" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast --preview" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast --preview" \
--prepare "autopep8 ${TARGET_DIR} --recursive --in-place" \
"autopep8 ${TARGET_DIR} --recursive --in-place" \
--prepare "yapf ${TARGET_DIR} --parallel --recursive --in-place" \
"yapf ${TARGET_DIR} --parallel --recursive --in-place"
# Benchmark 2: Write to disk, but only use one thread.
hyperfine --ignore-failure \
--prepare "./target/release/ruff format ${TARGET_DIR}" \
"RAYON_NUM_THREADS=1 ./target/release/ruff format ${TARGET_DIR}" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --workers=1 --safe" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --workers=1 --fast" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe --preview" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --workers=1 --safe --preview" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast --preview" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --workers=1 --fast --preview" \
--prepare "autopep8 ${TARGET_DIR} --recursive --in-place" \
"autopep8 ${TARGET_DIR} --in-place --recursive --jobs=1" \
--prepare "yapf ${TARGET_DIR} --parallel --recursive --in-place" \
"yapf ${TARGET_DIR} --recursive --in-place"
# Benchmark 3: Check formatting, but don't write to disk.
hyperfine --ignore-failure \
--prepare "./target/release/ruff format ${TARGET_DIR}" \
"./target/release/ruff format ${TARGET_DIR} --check" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --check --safe" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --check --fast" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --safe --preview" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --check --safe --preview" \
--prepare "BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --fast --preview" \
"BLACK_CACHE_DIR=/dev/null black ${TARGET_DIR} --check --fast --preview" \
--prepare "autopep8 ${TARGET_DIR} --recursive --in-place" \
"autopep8 ${TARGET_DIR} --recursive --diff" \
--prepare "yapf ${TARGET_DIR} --parallel --recursive --in-place" \
"yapf ${TARGET_DIR} --parallel --recursive --quiet"