diff options
author | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2021-02-15 22:18:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-15 22:18:54 +0100 |
commit | e48946f0b4bd385fcd93b6ef920d092f0d8d7424 (patch) | |
tree | d3cb5ed7b674b1db111062c178181dd8b7f33f92 | |
parent | 493885f17375bf322a1b8d3ee7ba5a8dd4124599 (diff) | |
download | pylint-git-e48946f0b4bd385fcd93b6ef920d092f0d8d7424.tar.gz |
Graingert add generated members match against the qualified name (#4092)
* Fix #2498: add generated-members match against the qualified name
Add test for fully qualified generated-members
Co-authored-by: Gauthier Sebaux <gauthier.sebaux@wavestone.com>
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
-rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/typecheck.py | 32 | ||||
-rw-r--r-- | tests/functional/g/generated_members.py | 2 | ||||
-rw-r--r-- | tests/functional/g/generated_members.rc | 8 | ||||
-rw-r--r-- | tests/functional/g/generated_members.txt | 1 |
6 files changed, 37 insertions, 12 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 81b1008bb..cdf7db72f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -436,6 +436,8 @@ contributors: * Frank Harrison (doublethefish): contributor +* Gauthier Sebaux: contributor + * Logan Miller (komodo472): contributor * Matthew Suozzo: contributor @@ -138,6 +138,10 @@ Release date: TBA Close #3314 +* `generated-members` now matches the qualified name of members + + Close #2498 + What's New in Pylint 2.6.0? =========================== diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 055292a60..e135f6e94 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -60,6 +60,7 @@ import types from collections import deque from collections.abc import Sequence from functools import singledispatch +from typing import Pattern, Tuple import astroid import astroid.arguments @@ -855,17 +856,20 @@ accessed. Python regular expressions are accepted.", def _suggestion_mode(self): return get_global_option(self, "suggestion-mode", default=True) - def open(self): - # do this in open since config not fully initialized in __init__ + @decorators.cachedproperty + def _compiled_generated_members(self) -> Tuple[Pattern, ...]: + # do this lazily since config not fully initialized in __init__ # generated_members may contain regular expressions # (surrounded by quote `"` and followed by a comma `,`) # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') - if isinstance(self.config.generated_members, str): - gen = shlex.shlex(self.config.generated_members) + generated_members = self.config.generated_members + if isinstance(generated_members, str): + gen = shlex.shlex(generated_members) gen.whitespace += "," gen.wordchars += r"[]-+\.*?()|" - self.config.generated_members = tuple(tok.strip('"') for tok in gen) + generated_members = tuple(tok.strip('"') for tok in gen) + return tuple(re.compile(exp) for exp in generated_members) @check_messages("keyword-arg-before-vararg") def visit_functiondef(self, node): @@ -919,12 +923,12 @@ accessed. Python regular expressions are accepted.", function/method, super call and metaclasses are ignored """ - for pattern in self.config.generated_members: - # attribute is marked as generated, stop here - if re.match(pattern, node.attrname): - return - if re.match(pattern, node.as_string()): - return + if any( + pattern.match(name) + for name in (node.attrname, node.as_string()) + for pattern in self._compiled_generated_members + ): + return try: inferred = list(node.expr.infer()) @@ -955,6 +959,12 @@ accessed. Python regular expressions are accepted.", ): continue + qualname = "{}.{}".format(owner.pytype(), node.attrname) + if any( + pattern.match(qualname) for pattern in self._compiled_generated_members + ): + return + try: if not [ n diff --git a/tests/functional/g/generated_members.py b/tests/functional/g/generated_members.py index 802ebaf8f..b79c008dd 100644 --- a/tests/functional/g/generated_members.py +++ b/tests/functional/g/generated_members.py @@ -9,6 +9,8 @@ class Klass(object): print(Klass().DoesNotExist) print(Klass().aBC_set1) +print(Klass().ham.does.not_.exist) +print(Klass().spam.does.not_.exist) # [no-member] node_classes.Tuple.does.not_.exist checkers.base.doesnotexist() diff --git a/tests/functional/g/generated_members.rc b/tests/functional/g/generated_members.rc index 177eca955..8b61b4996 100644 --- a/tests/functional/g/generated_members.rc +++ b/tests/functional/g/generated_members.rc @@ -2,4 +2,10 @@ disable=too-few-public-methods,print-statement [typecheck] -generated-members=DoesNotExist,"[a-zA-Z]+_set{1,2}",node_classes.Tuple.*,checkers.*?base,(session|SESSION).rollback +generated-members= + \Afunctional\.g\.generated_members\.Klass\.ham\Z, + DoesNotExist, + "[a-zA-Z]+_set{1,2}", + node_classes.Tuple.*, + checkers.*?base, + (session|SESSION).rollback diff --git a/tests/functional/g/generated_members.txt b/tests/functional/g/generated_members.txt new file mode 100644 index 000000000..28e793c32 --- /dev/null +++ b/tests/functional/g/generated_members.txt @@ -0,0 +1 @@ +no-member:13:6::Instance of 'Klass' has no 'spam' member:INFERENCE |