From 47a164136982ea6ef80f302200b2a29d3a4b1c3c Mon Sep 17 00:00:00 2001 From: Charlie Liu Date: Tue, 30 Oct 2018 23:15:59 -0400 Subject: checker.py: Add support for list concatenation This adds support for when '__all__' is defined from concatenated lists. However, because pyflakes is not an interpreter, this only applies to certain reduced test cases. Fixes https://github.com/PyCQA/pyflakes/issues/276 --- pyflakes/checker.py | 28 ++++++++++++++++++++++------ pyflakes/test/test_imports.py | 15 +++++---------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 2060ce4..3dd968d 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -390,10 +390,10 @@ class ExportBinding(Binding): can be determined statically, they will be treated as names for export and additional checking applied to them. - The only C{__all__} assignment that can be recognized is one which takes - the value of a literal list containing literal strings. For example:: + The only recognized C{__all__} assignment via list concatenation is in the + following format: - __all__ = ["foo", "bar"] + __all__ = ['a'] + ['b'] + ['c'] Names which are imported and not otherwise used but appear in the value of C{__all__} will not have an unused import warning reported for them. @@ -405,9 +405,25 @@ class ExportBinding(Binding): else: self.names = [] if isinstance(source.value, (ast.List, ast.Tuple)): - for node in source.value.elts: - if isinstance(node, ast.Str): - self.names.append(node.s) + self.names += ast.literal_eval(source.value) + # If concatenating lists + elif isinstance(source.value, ast.BinOp): + currentValue = source.value + while isinstance(currentValue.right, ast.List): + left = currentValue.left + right = currentValue.right + self.names += ast.literal_eval(right) + # If more lists are being added + if isinstance(left, ast.BinOp): + currentValue = left + # If just two lists are being added + elif isinstance(left, ast.List): + self.names += ast.literal_eval(left) + # All lists accounted for - done + break + # If not list concatenation + else: + break super(ExportBinding, self).__init__(name, source) diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py index 86e1d37..f83e77e 100644 --- a/pyflakes/test/test_imports.py +++ b/pyflakes/test/test_imports.py @@ -1068,19 +1068,14 @@ class TestSpecialAll(TestCase): __all__ += ['c', 'd'] ''', m.UndefinedExport, m.UndefinedExport) - def test_unrecognizable(self): + def test_concatenationAssignment(self): """ - If C{__all__} is defined in a way that can't be recognized statically, - it is ignored. + The C{__all__} variable is defined through list concatenation. """ self.flakes(''' - import foo - __all__ = ["f" + "oo"] - ''', m.UnusedImport) - self.flakes(''' - import foo - __all__ = [] + ["foo"] - ''', m.UnusedImport) + import sys + __all__ = ['a'] + ['b'] + ['c'] + ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) def test_unboundExported(self): """ -- cgit v1.2.1