Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0b9378e
Bump docker published images to alpine 3.23 and debian trixie (#21149)
samypr100 Jan 28, 2026
3f29e98
Drop PPC64 (big endian) builds (#22766)
konstin Jan 28, 2026
5754a1c
Stabilize `RUF037` (#22923)
ntBre Jan 29, 2026
a3f96f6
Stabilize `RUF102` (#22922)
ntBre Jan 29, 2026
7f93e76
Stabilize `UP042` (#22924)
ntBre Jan 29, 2026
9f58580
Stabilize `ASYNC250` (#22911)
ntBre Jan 29, 2026
fb2387e
Stabilize `RUF060` (#22919)
ntBre Jan 29, 2026
d65a99a
Stabilize `RUF064` (#22915)
ntBre Jan 29, 2026
43a11e6
Stabilize `RUF061` (#22917)
ntBre Jan 29, 2026
f11f3c0
Stabilize `--output-format` in `--watch` mode (#22908)
ntBre Jan 29, 2026
3bd8b46
Stabilize `ASYNC240` (#22909)
ntBre Jan 29, 2026
afa9608
Stabilize `ASYNC212` (#22914)
ntBre Jan 29, 2026
1ea7a6a
Stabilize `B912` (#22910)
ntBre Jan 29, 2026
74f51be
Stabilize `FURB110` (#22926)
ntBre Jan 29, 2026
6933e21
Stabilize `PLW0108` (#22927)
ntBre Jan 29, 2026
b2a1767
Stabilize `FURB171` (#22928)
ntBre Jan 29, 2026
d6b6dc6
Stabilize `PLC0207` (#22918)
ntBre Jan 29, 2026
85d6cb2
Stabilize safe fix for `UP008` (#22977)
ntBre Jan 30, 2026
db7b162
Stabilize considering `Optional` as a union in `PYI016` (#22974)
ntBre Jan 30, 2026
54593d8
Stabilize `RUF103` (#22979)
amyreese Jan 30, 2026
10518ab
Stabilize `RUF104` (#22980)
amyreese Jan 30, 2026
df23e9c
Stabilize the fix for `SIM905` when `maxsplit` is provided (#22975)
ntBre Jan 30, 2026
757679e
Stabilize expanded `A003` detection (#22973)
ntBre Jan 30, 2026
f0a4cee
Stabilize `UP043` for stub files on any Python version (#22978)
ntBre Jan 30, 2026
e88139c
Stabilize range suppressions (#22983)
ntBre Jan 30, 2026
532a4e4
Stabilize checking additional key expression types in `SIM910` (#22976)
ntBre Jan 30, 2026
6d0e243
fix: target-version fallback with extend (#21980)
denyszhak Jan 30, 2026
ffb226a
2026 Ruff Formatter Style (#22735)
dylwil3 Feb 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/build-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,6 @@ jobs:
manylinux: 2_17
# see https://github.com/astral-sh/ruff/issues/10073
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: powerpc64-unknown-linux-gnu
arch: ppc64
manylinux: 2_17
# see https://github.com/astral-sh/ruff/issues/10073
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: arm-unknown-linux-musleabihf
# Use the cross container, but tag as `linux_armv6l`
manylinux: auto
Expand Down Expand Up @@ -328,7 +323,7 @@ jobs:
docker-options: ${{ matrix.platform.maturin_docker_options }}
args: --release --locked --out dist --compatibility pypi
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
if: ${{ matrix.platform.arch != 'ppc64' && matrix.platform.arch != 'ppc64le'}}
if: ${{ matrix.platform.arch != 'ppc64le'}}
name: Test wheel
with:
arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/build-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ jobs:
# Mapping of base image followed by a comma followed by one or more base tags (comma separated)
# Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first)
image-mapping:
- alpine:3.21,alpine3.21,alpine
- debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian
- alpine:3.23,alpine3.23,alpine
- debian:trixie-slim,trixie-slim,debian-slim
- buildpack-deps:trixie,trixie,debian
steps:
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ Ruff is extremely actively developed and used in major open-source projects like
Ruff is backed by [Astral](https://astral.sh), the creators of
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).

Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
Read the [launch
post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
original [project
announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).

## Testimonials

Expand Down
7 changes: 1 addition & 6 deletions crates/ruff/src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,19 +408,14 @@ impl Printer {
}

let context = EmitterContext::new(&diagnostics.notebook_indexes);
let format = if preview {
self.format
} else {
OutputFormat::Concise
};
let config = DisplayDiagnosticConfig::default()
.preview(preview)
.hide_severity(true)
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_fix_applicability(self.unsafe_fixes.required_applicability())
.show_fix_diff(preview);
render_diagnostics(writer, format, config, &context, &diagnostics.inner)?;
render_diagnostics(writer, self.format, config, &context, &diagnostics.inner)?;
}
writer.flush()?;

Expand Down
20 changes: 2 additions & 18 deletions crates/ruff/tests/cli/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1539,22 +1539,6 @@ import sys
.args(["--config", "ruff.toml"])
.arg("noqa.py"),
@"
success: false
exit_code: 1
----- stdout -----
noqa.py:5:8: F401 [*] `sys` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.

----- stderr -----
");

assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.args(["--preview"]),
@"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -1563,12 +1547,12 @@ import sys
----- stderr -----
");

// with --ignore-noqa --preview
// with --ignore-noqa
assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.args(["--ignore-noqa", "--preview"]),
.args(["--ignore-noqa"]),
@"
success: false
exit_code: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ linter.rules.should_fix = [
linter.per_file_ignores = {}
linter.safety_table.forced_safe = []
linter.safety_table.forced_unsafe = []
linter.unresolved_target_version = 3.10
linter.unresolved_target_version = 3.11
linter.per_file_target_version = {}
linter.preview = disabled
linter.explicit_preview_rules = false
Expand Down Expand Up @@ -265,7 +265,7 @@ linter.ruff.strictly_empty_init_modules = false

# Formatter Settings
formatter.exclude = []
formatter.unresolved_target_version = 3.10
formatter.unresolved_target_version = 3.11
formatter.per_file_target_version = {}
formatter.preview = disabled
formatter.line_width = 88
Expand All @@ -280,7 +280,7 @@ formatter.docstring_code_line_width = dynamic
# Analyze Settings
analyze.exclude = []
analyze.preview = disabled
analyze.target_version = 3.10
analyze.target_version = 3.11
analyze.string_imports = disabled
analyze.extension = ExtensionMapping({})
analyze.include_dependencies = {}
Expand Down
18 changes: 4 additions & 14 deletions crates/ruff_linter/src/checkers/ast/analyze/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use ruff_python_semantic::analyze::typing;
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
use crate::preview::{
is_future_required_preview_generics_enabled, is_optional_as_none_in_union_enabled,
is_unnecessary_default_type_args_stubs_enabled,
};
use crate::preview::is_future_required_preview_generics_enabled;
use crate::registry::Rule;
use crate::rules::{
airflow, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear,
Expand Down Expand Up @@ -101,10 +98,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
}
if checker.is_rule_enabled(Rule::DuplicateUnionMember)
// Avoid duplicate checks inside `Optional`
&& !(
is_optional_as_none_in_union_enabled(checker.settings())
&& checker.semantic.inside_optional()
)
&& !checker.semantic.inside_optional()
{
flake8_pyi::rules::duplicate_union_member(checker, expr);
}
Expand Down Expand Up @@ -149,8 +143,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {

if checker.is_rule_enabled(Rule::UnnecessaryDefaultTypeArgs) {
if checker.target_version() >= PythonVersion::PY313
|| is_unnecessary_default_type_args_stubs_enabled(checker.settings())
&& checker.semantic().in_stub_file()
|| checker.semantic().in_stub_file()
{
pyupgrade::rules::unnecessary_default_type_args(checker, expr);
}
Expand Down Expand Up @@ -1542,10 +1535,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::DuplicateUnionMember)
&& checker.semantic.in_type_definition()
// Avoid duplicate checks inside `Optional`
&& !(
is_optional_as_none_in_union_enabled(checker.settings())
&& checker.semantic.inside_optional()
)
&& !checker.semantic.inside_optional()
{
flake8_pyi::rules::duplicate_union_member(checker, expr);
}
Expand Down
3 changes: 1 addition & 2 deletions crates/ruff_linter/src/checkers/noqa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use crate::fix::edits::delete_comment;
use crate::noqa::{
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
};
use crate::preview::is_range_suppressions_enabled;
use crate::registry::Rule;
use crate::rule_redirects::get_redirect_target;
use crate::rules::pygrep_hooks;
Expand Down Expand Up @@ -71,7 +70,7 @@ pub(crate) fn check_noqa(
}

// Apply ranged suppressions next
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
if suppressions.check_diagnostic(diagnostic) {
ignored_diagnostics.push(index);
continue;
}
Expand Down
12 changes: 4 additions & 8 deletions crates/ruff_linter/src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,7 @@ pub fn add_noqa_to_path(
);

// Parse range suppression comments
let suppressions =
Suppressions::from_tokens(settings, locator.contents(), parsed.tokens(), &indexer);
let suppressions = Suppressions::from_tokens(locator.contents(), parsed.tokens(), &indexer);

// Generate diagnostics, ignoring any existing `noqa` directives.
let diagnostics = check_path(
Expand Down Expand Up @@ -480,8 +479,7 @@ pub fn lint_only(
);

// Parse range suppression comments
let suppressions =
Suppressions::from_tokens(settings, locator.contents(), parsed.tokens(), &indexer);
let suppressions = Suppressions::from_tokens(locator.contents(), parsed.tokens(), &indexer);

// Generate diagnostics.
let diagnostics = check_path(
Expand Down Expand Up @@ -598,8 +596,7 @@ pub fn lint_fix<'a>(
);

// Parse range suppression comments
let suppressions =
Suppressions::from_tokens(settings, locator.contents(), parsed.tokens(), &indexer);
let suppressions = Suppressions::from_tokens(locator.contents(), parsed.tokens(), &indexer);

// Generate diagnostics.
let diagnostics = check_path(
Expand Down Expand Up @@ -981,8 +978,7 @@ mod tests {
&locator,
&indexer,
);
let suppressions =
Suppressions::from_tokens(settings, locator.contents(), parsed.tokens(), &indexer);
let suppressions = Suppressions::from_tokens(locator.contents(), parsed.tokens(), &indexer);
let mut diagnostics = check_path(
path,
None,
Expand Down
41 changes: 0 additions & 41 deletions crates/ruff_linter/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,52 +196,16 @@ pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) ->
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/18572
pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/20659
pub(crate) const fn is_future_required_preview_generics_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/18683
pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/19851
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/20027
pub(crate) const fn is_unnecessary_default_type_args_stubs_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/20343
pub(crate) const fn is_sim910_expanded_key_support_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/20169
pub(crate) const fn is_fix_builtin_open_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/20178
pub(crate) const fn is_a003_class_scope_shadowing_expansion_enabled(
settings: &LinterSettings,
) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/20200
pub(crate) const fn is_refined_submodule_import_match_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
Expand Down Expand Up @@ -292,11 +256,6 @@ pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/21623
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/22057
pub(crate) const fn is_ble001_exc_info_suppression_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::checkers::ast::Checker;
/// response = await client.get(...)
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.12.11")]
#[violation_metadata(stable_since = "0.15.0")]
pub(crate) struct BlockingHttpCallHttpxInAsyncFunction {
name: String,
call: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ use crate::checkers::ast::Checker;
/// username = await loop.run_in_executor(None, input, "Username:")
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.12.12")]
#[violation_metadata(stable_since = "0.15.0")]
pub(crate) struct BlockingInputInAsyncFunction;

impl Violation for BlockingInputInAsyncFunction {
#[derive_message_formats]
fn message(&self) -> String {
"Blocking call to input() in async context".to_string()
"Blocking call to `input()` in async context".to_string()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use ruff_text_size::Ranged;
/// new_path = os.path.join("/tmp/src/", path)
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.13.2")]
#[violation_metadata(stable_since = "0.15.0")]
pub(crate) struct BlockingPathMethodInAsyncFunction {
path_library: String,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
---
ASYNC250 Blocking call to input() in async context
ASYNC250 Blocking call to `input()` in async context
--> ASYNC250.py:7:9
|
6 | async def foo():
Expand All @@ -10,7 +10,7 @@ ASYNC250 Blocking call to input() in async context
8 | input("hello world") # ASYNC250
|

ASYNC250 Blocking call to input() in async context
ASYNC250 Blocking call to `input()` in async context
--> ASYNC250.py:8:5
|
6 | async def foo():
Expand All @@ -19,7 +19,7 @@ ASYNC250 Blocking call to input() in async context
| ^^^^^
|

ASYNC250 Blocking call to input() in async context
ASYNC250 Blocking call to `input()` in async context
--> ASYNC250.py:21:5
|
20 | async def foo():
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ mod tests {
#[test_case(Rule::LoopIteratorMutation, Path::new("B909.py"))]
#[test_case(Rule::MutableContextvarDefault, Path::new("B039.py"))]
#[test_case(Rule::BatchedWithoutExplicitStrict, Path::new("B911.py"))]
#[test_case(Rule::MapWithoutExplicitStrict, Path::new("B912.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Expand All @@ -83,7 +84,6 @@ mod tests {
Ok(())
}

#[test_case(Rule::MapWithoutExplicitStrict, Path::new("B912.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))]
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))]
Expand Down
Loading