From e48946f0b4bd385fcd93b6ef920d092f0d8d7424 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 15 Feb 2021 22:18:54 +0100 Subject: [PATCH] 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 Co-authored-by: Thomas Grainger --- CONTRIBUTORS.txt | 2 ++ ChangeLog | 4 +++ pylint/checkers/typecheck.py | 32 ++++++++++++++++-------- tests/functional/g/generated_members.py | 2 ++ tests/functional/g/generated_members.rc | 8 +++++- tests/functional/g/generated_members.txt | 1 + 6 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 tests/functional/g/generated_members.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 81b1008bb2..cdf7db72fe 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 diff --git a/ChangeLog b/ChangeLog index e3183ec5df..a12f631f6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 055292a609..e135f6e94d 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -60,6 +60,7 @@ 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 @@ class should be ignored. A mixin class is detected if its name ends with \ 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 @@ def visit_attribute(self, node): 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 @@ def visit_attribute(self, node): ): 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 802ebaf8f6..b79c008dda 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 177eca955e..8b61b4996b 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 0000000000..28e793c322 --- /dev/null +++ b/tests/functional/g/generated_members.txt @@ -0,0 +1 @@ +no-member:13:6::Instance of 'Klass' has no 'spam' member:INFERENCE