From 4d8ccb612557f8b35bd19b69ad6e0a073510e69c Mon Sep 17 00:00:00 2001 From: Frazer McLean Date: Sun, 10 Aug 2025 22:35:27 +0200 Subject: [PATCH] RUF064: offer a safe fix for multi-digit zeros (#19847) Fixes #19010 ## Summary See #19010. `0` was not considered a violation, but `000` was. The latter will now be fixed to `0o000`. --- .../resources/test/fixtures/ruff/RUF064.py | 8 +++ .../rules/ruff/rules/non_octal_permissions.rs | 20 +++++- ..._rules__ruff__tests__RUF064_RUF064.py.snap | 71 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF064.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF064.py index 4f565ae15a..4fb6804d09 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF064.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF064.py @@ -51,3 +51,11 @@ dbm.ndbm.open("db", "r", 0o600) # OK os.fchmod(0, 256) # 0o400 os.fchmod(0, 493) # 0o755 + +# https://github.com/astral-sh/ruff/issues/19010 +os.chmod("foo", 000) # Error +os.chmod("foo", 0000) # Error + +os.chmod("foo", 0b0) # Error +os.chmod("foo", 0x0) # Error +os.chmod("foo", 0) # Ok diff --git a/crates/ruff_linter/src/rules/ruff/rules/non_octal_permissions.rs b/crates/ruff_linter/src/rules/ruff/rules/non_octal_permissions.rs index 90e70f9201..ed4c2aad65 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/non_octal_permissions.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/non_octal_permissions.rs @@ -57,6 +57,17 @@ use crate::{FixAvailability, Violation}; /// original code really intended to use `0o256` (`u=w,g=rx,o=rw`) instead of /// `256`, this fix should not be accepted. /// +/// As a special case, zero is allowed to omit the `0o` prefix unless it has +/// multiple digits: +/// +/// ```python +/// os.chmod("foo", 0) # Ok +/// os.chmod("foo", 0o000) # Ok +/// os.chmod("foo", 000) # Lint emitted and fix suggested +/// ``` +/// +/// Ruff will suggest a safe fix for multi-digit zeros to add the `0o` prefix. +/// /// ## Fix availability /// /// A fix is only available if the integer literal matches a set of common modes. @@ -102,7 +113,14 @@ pub(crate) fn non_octal_permissions(checker: &Checker, call: &ExprCall) { let mut diagnostic = checker.report_diagnostic(NonOctalPermissions, mode_arg.range()); // Don't suggest a fix for 0x or 0b literals. - if mode_literal.starts_with('0') { + if mode_literal.starts_with("0x") || mode_literal.starts_with("0b") { + return; + } + + if mode_literal.chars().all(|c| c == '0') { + // Fix e.g. 000 as 0o000 + let edit = Edit::range_replacement(format!("0o{mode_literal}"), mode_arg.range()); + diagnostic.set_fix(Fix::safe_edit(edit)); return; } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF064_RUF064.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF064_RUF064.py.snap index acdb6555d2..4aace6b857 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF064_RUF064.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF064_RUF064.py.snap @@ -348,6 +348,8 @@ help: Replace with octal literal 52 |-os.fchmod(0, 256) # 0o400 52 |+os.fchmod(0, 0o400) # 0o400 53 53 | os.fchmod(0, 493) # 0o755 +54 54 | +55 55 | # https://github.com/astral-sh/ruff/issues/19010 RUF064 [*] Non-octal mode --> RUF064.py:53:14 @@ -355,6 +357,8 @@ RUF064 [*] Non-octal mode 52 | os.fchmod(0, 256) # 0o400 53 | os.fchmod(0, 493) # 0o755 | ^^^ +54 | +55 | # https://github.com/astral-sh/ruff/issues/19010 | help: Replace with octal literal @@ -364,3 +368,70 @@ help: Replace with octal literal 52 52 | os.fchmod(0, 256) # 0o400 53 |-os.fchmod(0, 493) # 0o755 53 |+os.fchmod(0, 0o755) # 0o755 +54 54 | +55 55 | # https://github.com/astral-sh/ruff/issues/19010 +56 56 | os.chmod("foo", 000) # Error + +RUF064 [*] Non-octal mode + --> RUF064.py:56:17 + | +55 | # https://github.com/astral-sh/ruff/issues/19010 +56 | os.chmod("foo", 000) # Error + | ^^^ +57 | os.chmod("foo", 0000) # Error + | +help: Replace with octal literal + +ℹ Safe fix +53 53 | os.fchmod(0, 493) # 0o755 +54 54 | +55 55 | # https://github.com/astral-sh/ruff/issues/19010 +56 |-os.chmod("foo", 000) # Error + 56 |+os.chmod("foo", 0o000) # Error +57 57 | os.chmod("foo", 0000) # Error +58 58 | +59 59 | os.chmod("foo", 0b0) # Error + +RUF064 [*] Non-octal mode + --> RUF064.py:57:17 + | +55 | # https://github.com/astral-sh/ruff/issues/19010 +56 | os.chmod("foo", 000) # Error +57 | os.chmod("foo", 0000) # Error + | ^^^^ +58 | +59 | os.chmod("foo", 0b0) # Error + | +help: Replace with octal literal + +ℹ Safe fix +54 54 | +55 55 | # https://github.com/astral-sh/ruff/issues/19010 +56 56 | os.chmod("foo", 000) # Error +57 |-os.chmod("foo", 0000) # Error + 57 |+os.chmod("foo", 0o0000) # Error +58 58 | +59 59 | os.chmod("foo", 0b0) # Error +60 60 | os.chmod("foo", 0x0) # Error + +RUF064 Non-octal mode + --> RUF064.py:59:17 + | +57 | os.chmod("foo", 0000) # Error +58 | +59 | os.chmod("foo", 0b0) # Error + | ^^^ +60 | os.chmod("foo", 0x0) # Error +61 | os.chmod("foo", 0) # Ok + | +help: Replace with octal literal + +RUF064 Non-octal mode + --> RUF064.py:60:17 + | +59 | os.chmod("foo", 0b0) # Error +60 | os.chmod("foo", 0x0) # Error + | ^^^ +61 | os.chmod("foo", 0) # Ok + | +help: Replace with octal literal