Visitar URL original
[flake8-tidy-imports] Add `extend-banned-api` (#28644) · astral-sh/ruff@fe9f065 · GitHub
Skip to content

Commit fe9f065

Browse files
[flake8-tidy-imports] Add extend-banned-api (#28644)
## Summary Add `lint.flake8-tidy-imports.extend-banned-api` so we can add banned APIs without repeating an inherited `banned-api` table. Entries in `extend-banned-api` override entries with the same key in `banned-api`, including their diagnostic messages. For example, a child `ruff.toml` can add a ban while retaining the bans from its base configuration: ```toml extend = "../ruff.toml" [lint.flake8-tidy-imports.extend-banned-api] "typing.TypedDict".msg = "Use typing_extensions.TypedDict instead." ``` This follows the existing `extend-aliases` behavior: the base and extension tables inherit independently, and we merge them after resolving configuration inheritance. A child can replace either table, or clear it with `{}`. The change includes option documentation, the generated JSON schema, and CLI coverage for standalone bans, inheritance, message overrides, and clearing either table.
1 parent 31131db commit fe9f065

4 files changed

Lines changed: 170 additions & 2 deletions

File tree

‎crates/ruff/tests/cli/lint.rs‎

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,141 @@ extend = "ruff3.toml"
606606
Ok(())
607607
}
608608

609+
#[test]
610+
fn extend_banned_api() -> Result<()> {
611+
let fixture = CliTest::new()?;
612+
fixture.write_file(
613+
"ruff.toml",
614+
r#"
615+
[lint]
616+
select = ["TID251"]
617+
618+
[lint.flake8-tidy-imports.extend-banned-api]
619+
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
620+
"cgi".msg = "Use a supported library instead."
621+
"typing.Any".msg = "Use a precise type instead."
622+
"#,
623+
)?;
624+
fixture.write_file(
625+
"test.py",
626+
"
627+
import cgi
628+
from typing import TypedDict
629+
import typing
630+
x: typing.Any
631+
",
632+
)?;
633+
634+
assert_cmd_snapshot!(fixture.check_command(), @"
635+
success: false
636+
exit_code: 1
637+
----- stdout -----
638+
test.py:2:8: TID251 `cgi` is banned: Use a supported library instead.
639+
test.py:3:20: TID251 `typing.TypedDict` is banned: Use typing_extensions.TypedDict instead.
640+
test.py:5:4: TID251 `typing.Any` is banned: Use a precise type instead.
641+
Found 3 errors.
642+
643+
----- stderr -----
644+
");
645+
Ok(())
646+
}
647+
648+
#[test]
649+
fn extend_banned_api_inherited() -> Result<()> {
650+
let fixture = CliTest::new()?;
651+
fixture.write_file(
652+
"ruff.toml",
653+
r#"
654+
[lint]
655+
select = ["TID251"]
656+
657+
[lint.flake8-tidy-imports.banned-api]
658+
"cgi".msg = "The cgi module is deprecated."
659+
"pipes".msg = "Use shlex instead."
660+
661+
[lint.flake8-tidy-imports.extend-banned-api]
662+
"typing.Any".msg = "Use a precise type instead."
663+
"#,
664+
)?;
665+
fixture.write_file(
666+
"child/ruff.toml",
667+
r#"
668+
extend = "../ruff.toml"
669+
670+
[lint.flake8-tidy-imports.extend-banned-api]
671+
"cgi".msg = "Use a supported library instead."
672+
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
673+
"#,
674+
)?;
675+
fixture.write_file(
676+
"child/grandchild/ruff.toml",
677+
r#"
678+
extend = "../ruff.toml"
679+
"#,
680+
)?;
681+
fixture.write_file(
682+
"child/grandchild/test.py",
683+
"
684+
import cgi
685+
import pipes
686+
from typing import Any, TypedDict
687+
",
688+
)?;
689+
690+
assert_cmd_snapshot!(fixture.check_command(), @"
691+
success: false
692+
exit_code: 1
693+
----- stdout -----
694+
child/grandchild/test.py:2:8: TID251 `cgi` is banned: Use a supported library instead.
695+
child/grandchild/test.py:3:8: TID251 `pipes` is banned: Use shlex instead.
696+
child/grandchild/test.py:4:25: TID251 `typing.TypedDict` is banned: Use typing_extensions.TypedDict instead.
697+
Found 3 errors.
698+
699+
----- stderr -----
700+
");
701+
702+
// Clear inherited extensions while preserving the inherited base bans.
703+
fixture.write_file(
704+
"child/grandchild/ruff.toml",
705+
r#"
706+
extend = "../ruff.toml"
707+
[lint.flake8-tidy-imports]
708+
extend-banned-api = {}
709+
"#,
710+
)?;
711+
assert_cmd_snapshot!(fixture.check_command(), @"
712+
success: false
713+
exit_code: 1
714+
----- stdout -----
715+
child/grandchild/test.py:2:8: TID251 `cgi` is banned: The cgi module is deprecated.
716+
child/grandchild/test.py:3:8: TID251 `pipes` is banned: Use shlex instead.
717+
Found 2 errors.
718+
719+
----- stderr -----
720+
");
721+
722+
// Clear inherited base bans while preserving the inherited extensions.
723+
fixture.write_file(
724+
"child/grandchild/ruff.toml",
725+
r#"
726+
extend = "../ruff.toml"
727+
[lint.flake8-tidy-imports]
728+
banned-api = {}
729+
"#,
730+
)?;
731+
assert_cmd_snapshot!(fixture.check_command(), @"
732+
success: false
733+
exit_code: 1
734+
----- stdout -----
735+
child/grandchild/test.py:2:8: TID251 `cgi` is banned: Use a supported library instead.
736+
child/grandchild/test.py:4:25: TID251 `typing.TypedDict` is banned: Use typing_extensions.TypedDict instead.
737+
Found 2 errors.
738+
739+
----- stderr -----
740+
");
741+
Ok(())
742+
}
743+
609744
#[test]
610745
fn circular_extend() -> Result<()> {
611746
let fixture = CliTest::new()?;

‎crates/ruff_linter/src/rules/flake8_tidy_imports/rules/banned_api.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::rules::flake8_tidy_imports::matchers::NameMatchPolicy;
2525
///
2626
/// ## Options
2727
/// - `lint.flake8-tidy-imports.banned-api`
28+
/// - `lint.flake8-tidy-imports.extend-banned-api`
2829
#[derive(ViolationMetadata)]
2930
#[violation_metadata(stable_since = "v0.0.201", category = Category::Restriction)]
3031
pub(crate) struct BannedApi {

‎crates/ruff_workspace/src/options.rs‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,9 @@ pub struct Flake8TidyImportsOptions {
21552155
ban_relative_imports: Option<Strictness>,
21562156

21572157
/// Specific modules or module members that may not be imported or accessed.
2158+
/// These can be extended by the
2159+
/// [`extend-banned-api`](#lint_flake8-tidy-imports_extend-banned-api) option.
2160+
///
21582161
/// Note that this rule is only meant to flag accidental uses,
21592162
/// and can be circumvented via `eval` or `importlib`.
21602163
#[option(
@@ -2168,6 +2171,20 @@ pub struct Flake8TidyImportsOptions {
21682171
)]
21692172
banned_api: Option<FxHashMap<String, ApiBan>>,
21702173

2174+
/// Additional modules or module members that may not be imported or accessed.
2175+
/// These entries will be added to the
2176+
/// [`banned-api`](#lint_flake8-tidy-imports_banned-api) mapping and will override
2177+
/// any existing entries if the two settings overlap.
2178+
#[option(
2179+
default = r#"{}"#,
2180+
value_type = r#"dict[str, { "msg": str }]"#,
2181+
scope = "extend-banned-api",
2182+
example = r#"
2183+
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
2184+
"#
2185+
)]
2186+
extend_banned_api: Option<FxHashMap<String, ApiBan>>,
2187+
21712188
/// List of specific modules that may not be imported at module level, and should instead be
21722189
/// imported lazily (e.g., within a function definition, or an `if TYPE_CHECKING:`
21732190
/// block, or some other nested context). This also affects the rule `import-outside-top-level`
@@ -2220,6 +2237,11 @@ pub struct Flake8TidyImportsOptions {
22202237

22212238
impl Flake8TidyImportsOptions {
22222239
pub(crate) fn try_into_settings(self) -> Result<flake8_tidy_imports::settings::Settings> {
2240+
let mut banned_api = self.banned_api.unwrap_or_default();
2241+
if let Some(extend_banned_api) = self.extend_banned_api {
2242+
banned_api.extend(extend_banned_api);
2243+
}
2244+
22232245
let require_lazy = self.require_lazy.unwrap_or_default();
22242246
let ban_lazy = self.ban_lazy.unwrap_or_default();
22252247

@@ -2231,7 +2253,7 @@ impl Flake8TidyImportsOptions {
22312253

22322254
Ok(flake8_tidy_imports::settings::Settings {
22332255
ban_relative_imports: self.ban_relative_imports.unwrap_or(Strictness::Parents),
2234-
banned_api: self.banned_api.unwrap_or_default(),
2256+
banned_api,
22352257
banned_module_level_imports: self.banned_module_level_imports.unwrap_or_default(),
22362258
require_lazy,
22372259
ban_lazy,

‎ruff.schema.json‎

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)