diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py index 943f5c4d93..54362a5070 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B028.py @@ -25,3 +25,11 @@ warnings.warn( # some comments here source = None # no trailing comma ) + +# https://github.com/astral-sh/ruff/issues/18011 +warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) +# trigger diagnostic if `skip_file_prefixes` is present and set to the default value +warnings.warn("test", skip_file_prefixes=()) + +_my_prefixes = ("this","that") +warnings.warn("test", skip_file_prefixes = _my_prefixes) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs index 73c41f4709..0e4ffd91f7 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/no_explicit_stacklevel.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast}; +use ruff_python_ast::{self as ast, Expr}; use ruff_text_size::Ranged; use crate::{checkers::ast::Checker, fix::edits::add_argument}; @@ -60,10 +60,18 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) { return; } + // When prefixes are supplied, stacklevel is implicitly overridden to be `max(2, stacklevel)`. + // + // Signature as of Python 3.13 (https://docs.python.org/3/library/warnings.html#warnings.warn) + // ```text + // 0 1 2 3 4 + // warnings.warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=()) + // ``` if call .arguments .find_argument_value("stacklevel", 2) .is_some() + || is_skip_file_prefixes_param_set(&call.arguments) || call .arguments .args @@ -90,3 +98,14 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) { checker.report_diagnostic(diagnostic); } + +/// Returns `true` if `skip_file_prefixes` is set to its non-default value. +/// The default value of `skip_file_prefixes` is an empty tuple. +fn is_skip_file_prefixes_param_set(arguments: &ast::Arguments) -> bool { + arguments + .find_keyword("skip_file_prefixes") + .is_some_and(|keyword| match &keyword.value { + Expr::Tuple(tuple) => !tuple.elts.is_empty(), + _ => true, + }) +} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap index cf6ec73378..db323efe66 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B028_B028.py.snap @@ -61,3 +61,26 @@ B028.py:22:1: B028 [*] No explicit `stacklevel` keyword argument found 26 |- source = None # no trailing comma 26 |+ source = None, stacklevel=2 # no trailing comma 27 27 | ) +28 28 | +29 29 | # https://github.com/astral-sh/ruff/issues/18011 + +B028.py:32:1: B028 [*] No explicit `stacklevel` keyword argument found + | +30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) +31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value +32 | warnings.warn("test", skip_file_prefixes=()) + | ^^^^^^^^^^^^^ B028 +33 | +34 | _my_prefixes = ("this","that") + | + = help: Set `stacklevel=2` + +ℹ Unsafe fix +29 29 | # https://github.com/astral-sh/ruff/issues/18011 +30 30 | warnings.warn("test", skip_file_prefixes=(os.path.dirname(__file__),)) +31 31 | # trigger diagnostic if `skip_file_prefixes` is present and set to the default value +32 |-warnings.warn("test", skip_file_prefixes=()) + 32 |+warnings.warn("test", skip_file_prefixes=(), stacklevel=2) +33 33 | +34 34 | _my_prefixes = ("this","that") +35 35 | warnings.warn("test", skip_file_prefixes = _my_prefixes)