[ruff] Detect duplicate entries in __all__ (RUF068)#22114
[ruff] Detect duplicate entries in __all__ (RUF068)#22114ntBre merged 21 commits intoastral-sh:mainfrom
ruff] Detect duplicate entries in __all__ (RUF068)#22114Conversation
|
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| RUF068 | 7 | 7 | 0 | 0 | 0 |
|
It seems that those are all true positives. This one might even be a bug. |
4c72c7c to
0824ae6
Compare
0824ae6 to
5ecf433
Compare
1d2e75d to
ccdc938
Compare
ccdc938 to
ca2ebf5
Compare
|
What about these cases? import typing
class A: ...
class B: ...
__all__ = "A" + "B"
__all__: list[str] = ["A", "B"]
__all__: typing.Any = ("A", "B") |
Do you want to test whether those situations produce false positives or whether it still catches duplicates when using tuples, concatenation and/or type annotations? |
I think it makes sense to add them to the test cases |
Added these cases as suggested |
[ruff] Add extra cases to RUF069
Now it handles the following cases:
- __all__: list[str] = ["a", "a"]
- __all__: typing.Any = ("a", "a")
- __all__.extend(["a", "a"])
- __all__ += ["a", "a"]
It still does not track mutable __all__ such as:
__all__ = ["a"]
__all__ += ["a"]
It will be a false negative.
8183c16 to
c1ea670
Compare
|
I added some extra cases, based on RUF022: __all__: list[str] = ["a", "a"]
__all__: typing.Any = ("a", "a")
__all__.extend(["a", "a"])
__all__ += ["a", "a"]It still does not track mutable __all__ = ["a"]
__all__ += ["a"]This will be a false negative. |
|
LGTM. There may be other cases, but again, we cannot check dynamically created I also think it's worth adding |
Done |
ntBre
left a comment
There was a problem hiding this comment.
Thank you! This is looking great. I just had a few more very small nits, and a couple of suggestions about additional tests (but I think they'll be handled well already).
I also think it might be worth manually testing the CLI to make sure cases with multiple deletions in the same assignment are fixed correctly. I think they should be, but we have a mechanism to isolate fixes if we need it. I think just testing something like:
$ cargo run -p ruff -- check --preview --select RUF069 --fix - <<<'__all__ = ["A", "A", "B", "B"]'should work. If it doesn't work, we should add an actual CLI test, but I think it's okay as long as the manual test works as expected.
crates/ruff_linter/src/rules/ruff/rules/duplicate_entry_in_dunder_all.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/duplicate_entry_in_dunder_all.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/duplicate_entry_in_dunder_all.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/duplicate_entry_in_dunder_all.rs
Show resolved
Hide resolved
crates/ruff_linter/src/rules/ruff/rules/duplicate_entry_in_dunder_all.rs
Outdated
Show resolved
Hide resolved
.../ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF069_RUF069.py.snap
Outdated
Show resolved
Hide resolved
It does work: $ cargo run -p ruff -- check --preview --select RUF069 --fix - <<<'__all__ = ["A", "A", "B", "B"]'
Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.98s
Running `target/debug/ruff check --preview --select RUF069 --fix -`
warning: Detected debug build without --no-cache.
__all__ = ["A", "B"]
Found 2 errors (2 fixed, 0 remaining).I also have tested it in the crates/ruff_linter/resources/test/fixtures/ruff/RUF069.py file and the fix works flawless. |
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
…der_all.rs Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
…der_all.rs Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
- non-literal-string - mult-line without comments
- use `try_set_fix` instead of manual matching - clarify that the linter detects duplicate elements, the removal is part of the fix
Helps the user to identify where is the duplicate entry in __all__
|
I should have addressed all concerns now, sorry for the delay |
|
In general, it seems that we also have code that repeats in this rule. Should we move some functions to helpers and reuse them? Do something like this: pub(crate) fn all_assignment<F>(
checker: &Checker,
target: &ast::Expr,
value: &ast::Expr,
call: F,
) where
F: FnOnce(&Checker, &[ast::Expr]),
{
let ast::Expr::Name(ast::ExprName { id, .. }) = target else {
return;
};
if id != "__all__" {
return;
}
if !checker.semantic().current_scope().kind.is_module() {
return
}
let elts = match value {
ast::Expr::List(ast::ExprList { elts, .. }) => elts,
ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => elts,
_ => return,
};
call(checker, elts);
} |
|
It is possible to leave it as it is. However, if we decide to add more checks to the rule in the future, for example, to track |
I guess we can defer this to later and have an specific MR for refactors. |
|
Yeah I think this is probably okay. I'll try to take another look tomorrow! |
ntBre
left a comment
There was a problem hiding this comment.
Very nice, thank you!
I pushed a couple of commits applying my last test suggestion and re-coding the rule to avoid a gap at RUF068, but this is great.
ruff] Detect duplicate entries in __all__ (RUF068)
| edits::remove_member(&set.elts, index, checker.locator().contents()) | ||
| .map(Fix::safe_edit) |
There was a problem hiding this comment.
I'll open a follow-up issue for this since it's not related to your PR, but this shouldn't always be a safe fix either:
❯ cat <<EOF | ruff check --diff --select B033 -
∙ {1, 2, 3,
# comment
1}
∙ EOF
@@ -1,3 +1 @@
-{1, 2, 3,
-# comment
-1}
+{1, 2, 3}
Would fix 1 error.
Hello,
This MR adds a new rule and its fix,
RUF069,DuplicateEntryInDunderAll. I'm usingRUF069because we already have RUF068 and RUF069 in the works.The rule job is to prevent users from accidentally adding duplicate entries to
__all__, which, for example, can result from copy-paste mistakes.It deals with the following syntaxes:
But it does not keep track of
__all__contents, meaning the following code snippet is a false negative:Violation Example
Ecosystem Report
The
ruff-ecosystemresults contain seven violations in four projects, all of them seem like true positives, with one instance appearing to be an actual bug.This code snippet from
reportlabcontains the same entry twice instead of exporting both functions.Closes #21945