Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: astral-sh/ruff
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 4385153115d462bf591fc95640e4b90ed2e7a036
Choose a base ref
..
head repository: astral-sh/ruff
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 7aca63a659fecc84fab83233ba760324aa731d5c
Choose a head ref
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ repos:
- id: mdformat
additional_dependencies:
- mdformat-mkdocs
- mdformat-admon

- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.33.0
20 changes: 20 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Breaking Changes

## 0.1.0

### Unsafe fixes are not applied by default ([#7769](https://github.com/astral-sh/ruff/pull/7769))

Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe
fixes, but the meaning could be changed when applying unsafe fixes. Previously, unsafe fixes were always displayed
and applied when fixing was enabled. Now, unsafe fixes are hidden by default and not applied. The `--unsafe-fixes`
flag or `unsafe-fixes` configuration option can be used to enable unsafe fixes.

See the [docs](https://docs.astral.sh/ruff/configuration/#fix-safety) for details.

### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))

Previously, Ruff enabled all implemented rules in Pycodestyle (`E`) by default. Ruff now only includes the
Pycodestyle prefixes `E4`, `E7`, and `E9` to exclude rules that conflict with automatic formatters. Consequently,
the stable rule set no longer includes `line-too-long` (`E501`) and `mixed-spaces-and-tabs` (`E101`). Other
excluded Pycodestyle rules include whitespace enforcement in `E1` and `E2`; these rules are currently in preview, and are already omitted by default.

This change only affects those using Ruff under its default rule set. Users that include `E` in their `select` will experience no change in behavior.

## 0.0.288

### Remove support for emoji identifiers ([#7212](https://github.com/astral-sh/ruff/pull/7212))
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@

Foo.objects.create(**{**bar}) # PIE804

foo(**{})


foo(**{**data, "foo": "buzz"})
foo(**buzz)
5 changes: 5 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pycodestyle/E25.py
Original file line number Diff line number Diff line change
@@ -54,3 +54,8 @@ def add(a: int = _default(name='f')):
f"{a:=1}"
f"{foo(a=1)}"
f"normal {f"{a=}"} normal"

# Okay as the `=` is used inside a f-string...
print(f"{foo = }")
# ...but then it creates false negatives for now
print(f"{foo(a = 1)}")
Original file line number Diff line number Diff line change
@@ -196,3 +196,9 @@ def g():

if sys.version_info <= (3,10000000):
print("py3")

if sys.version_info > (3,12):
print("py3")

if sys.version_info >= (3,12):
print("py3")
10 changes: 5 additions & 5 deletions crates/ruff_linter/src/message/json.rs
Original file line number Diff line number Diff line change
@@ -72,9 +72,9 @@ pub(crate) fn message_to_json_value(message: &Message, context: &EmitterContext)
.cell(start_location.row)
.unwrap_or(OneIndexed::MIN),
);
start_location = notebook_index.translated_location(&start_location);
end_location = notebook_index.translated_location(&end_location);
noqa_location = notebook_index.translated_location(&noqa_location);
start_location = notebook_index.translate_location(&start_location);
end_location = notebook_index.translate_location(&end_location);
noqa_location = notebook_index.translate_location(&noqa_location);
}

json!({
@@ -145,10 +145,10 @@ impl Serialize for ExpandedEdits<'_> {
};
}
_ => {
end_location = notebook_index.translated_location(&end_location);
end_location = notebook_index.translate_location(&end_location);
}
}
location = notebook_index.translated_location(&location);
location = notebook_index.translate_location(&location);
}

let value = json!({
Original file line number Diff line number Diff line change
@@ -88,16 +88,20 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs

let mut diagnostic = Diagnostic::new(UnnecessaryDictKwargs, expr.range());

diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
kwargs
.iter()
.zip(values.iter())
.map(|(kwarg, value)| {
format!("{}={}", kwarg.value, checker.locator().slice(value.range()))
})
.join(", "),
kw.range(),
)));
if values.is_empty() {
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(kw.start(), kw.end())));
} else {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
kwargs
.iter()
.zip(values.iter())
.map(|(kwarg, value)| {
format!("{}={}", kwarg.value, checker.locator().slice(value.range()))
})
.join(", "),
kw.range(),
)));
}

checker.diagnostics.push(diagnostic);
}
Original file line number Diff line number Diff line change
@@ -80,13 +80,15 @@ PIE804.py:7:1: PIE804 [*] Unnecessary `dict` kwargs
10 10 |

PIE804.py:9:1: PIE804 [*] Unnecessary `dict` kwargs
|
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
8 |
9 | Foo.objects.create(**{**bar}) # PIE804
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE804
|
= help: Remove unnecessary kwargs
|
7 | Foo.objects.create(**{"_id": some_id}) # PIE804
8 |
9 | Foo.objects.create(**{**bar}) # PIE804
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PIE804
10 |
11 | foo(**{})
|
= help: Remove unnecessary kwargs

Fix
6 6 |
@@ -95,7 +97,26 @@ PIE804.py:9:1: PIE804 [*] Unnecessary `dict` kwargs
9 |-Foo.objects.create(**{**bar}) # PIE804
9 |+Foo.objects.create(**bar) # PIE804
10 10 |
11 11 |
12 12 | foo(**{**data, "foo": "buzz"})
11 11 | foo(**{})
12 12 |

PIE804.py:11:1: PIE804 [*] Unnecessary `dict` kwargs
|
9 | Foo.objects.create(**{**bar}) # PIE804
10 |
11 | foo(**{})
| ^^^^^^^^^ PIE804
|
= help: Remove unnecessary kwargs

Fix
8 8 |
9 9 | Foo.objects.create(**{**bar}) # PIE804
10 10 |
11 |-foo(**{})
11 |+foo()
12 12 |
13 13 |
14 14 | foo(**{**data, "foo": "buzz"})


Original file line number Diff line number Diff line change
@@ -94,6 +94,7 @@ pub(crate) fn whitespace_around_named_parameter_equals(
context: &mut LogicalLinesContext,
) {
let mut parens = 0u32;
let mut fstrings = 0u32;
let mut annotated_func_arg = false;
let mut prev_end = TextSize::default();

@@ -108,6 +109,8 @@ pub(crate) fn whitespace_around_named_parameter_equals(
}

match kind {
TokenKind::FStringStart => fstrings += 1,
TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1),
TokenKind::Lpar | TokenKind::Lsqb => {
parens = parens.saturating_add(1);
}
@@ -124,7 +127,7 @@ pub(crate) fn whitespace_around_named_parameter_equals(
TokenKind::Comma if parens == 1 => {
annotated_func_arg = false;
}
TokenKind::Equal if parens > 0 => {
TokenKind::Equal if parens > 0 && fstrings == 0 => {
if annotated_func_arg && parens == 1 {
let start = token.start();
if start == prev_end && prev_end != TextSize::new(0) {
Original file line number Diff line number Diff line change
@@ -104,12 +104,18 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {

match comparison {
Expr::Tuple(ast::ExprTuple { elts, .. }) => match op {
CmpOp::Lt | CmpOp::LtE => {
CmpOp::Lt | CmpOp::LtE | CmpOp::Gt | CmpOp::GtE => {
let Some(version) = extract_version(elts) else {
return;
};
let target = checker.settings.target_version;
match compare_version(&version, target, op == &CmpOp::LtE) {
match version_always_less_than(
&version,
target,
// `x <= y` and `x > y` are cases where `x == y` will not stop the comparison
// from always evaluating to true or false respectively
op.is_lt_e() || op.is_gt(),
) {
Ok(false) => {}
Ok(true) => {
let mut diagnostic = Diagnostic::new(
@@ -118,36 +124,11 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
},
branch.test.range(),
);
if let Some(fix) = fix_always_false_branch(checker, stmt_if, &branch) {
diagnostic.set_fix(fix);
}
checker.diagnostics.push(diagnostic);
}
Err(_) => {
checker.diagnostics.push(Diagnostic::new(
OutdatedVersionBlock {
reason: Reason::Invalid,
},
comparison.range(),
));
}
}
}
CmpOp::Gt | CmpOp::GtE => {
let Some(version) = extract_version(elts) else {
return;
};
let target = checker.settings.target_version;
match compare_version(&version, target, op == &CmpOp::GtE) {
Ok(false) => {}
Ok(true) => {
let mut diagnostic = Diagnostic::new(
OutdatedVersionBlock {
reason: Reason::Outdated,
},
branch.test.range(),
);
if let Some(fix) = fix_always_true_branch(checker, stmt_if, &branch) {
if let Some(fix) = if op.is_lt() || op.is_lt_e() {
fix_always_false_branch(checker, stmt_if, &branch)
} else {
fix_always_true_branch(checker, stmt_if, &branch)
} {
diagnostic.set_fix(fix);
}
checker.diagnostics.push(diagnostic);
@@ -211,15 +192,15 @@ pub(crate) fn outdated_version_block(checker: &mut Checker, stmt_if: &StmtIf) {
}
}

/// Returns true if the `target_version` is always less than the [`PythonVersion`].
fn compare_version(
target_version: &[Int],
/// Returns true if the `check_version` is always less than the [`PythonVersion`].
fn version_always_less_than(
check_version: &[Int],
py_version: PythonVersion,
or_equal: bool,
) -> Result<bool> {
let mut target_version_iter = target_version.iter();
let mut check_version_iter = check_version.iter();

let Some(if_major) = target_version_iter.next() else {
let Some(if_major) = check_version_iter.next() else {
return Ok(false);
};
let Some(if_major) = if_major.as_u8() else {
@@ -232,7 +213,7 @@ fn compare_version(
Ordering::Less => Ok(true),
Ordering::Greater => Ok(false),
Ordering::Equal => {
let Some(if_minor) = target_version_iter.next() else {
let Some(if_minor) = check_version_iter.next() else {
return Ok(true);
};
let Some(if_minor) = if_minor.as_u8() else {
@@ -462,7 +443,7 @@ mod tests {
expected: bool,
) -> Result<()> {
let target_versions: Vec<_> = target_versions.iter().map(|int| Int::from(*int)).collect();
let actual = compare_version(&target_versions, version, or_equal)?;
let actual = version_always_less_than(&target_versions, version, or_equal)?;
assert_eq!(actual, expected);
Ok(())
}
Original file line number Diff line number Diff line change
@@ -712,4 +712,22 @@ UP036_0.py:197:24: UP036 Version specifier is invalid
198 | print("py3")
|

UP036_0.py:203:4: UP036 [*] Version block is outdated for minimum Python version
|
201 | print("py3")
202 |
203 | if sys.version_info >= (3,12):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
204 | print("py3")
|
= help: Remove outdated version block

Suggested fix
200 200 | if sys.version_info > (3,12):
201 201 | print("py3")
202 202 |
203 |-if sys.version_info >= (3,12):
204 |- print("py3")
203 |+print("py3")


2 changes: 1 addition & 1 deletion crates/ruff_notebook/src/index.rs
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ impl NotebookIndex {
///
/// This will translate the row/column in the concatenated source code
/// to the row/column in the Jupyter Notebook.
pub fn translated_location(&self, source_location: &SourceLocation) -> SourceLocation {
pub fn translate_location(&self, source_location: &SourceLocation) -> SourceLocation {
SourceLocation {
row: self
.cell_row(source_location.row)
Original file line number Diff line number Diff line change
@@ -384,3 +384,29 @@ def test():
#comment
except ImportError:
pass

# https://github.com/astral-sh/ruff/issues/7603
def default_arg_comments(
a: str = #a
"a",
b: str =
#b
"b",
c: str =( #c
"c"
),
d: str =(
#d
"d"
)
):
print(a, b, c, d)

def default_arg_comments2(#
x: int#=
= #
#
123#
#
):
print(x)
Loading