diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2b19f1..9444710b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased * Ignore unused import statements which occur in `__init__.py` (RJ722, #192). +* Report first line number of method definition for unused properties (RJ722, + #200). # 1.3 (2020-02-03) diff --git a/tests/test_report.py b/tests/test_report.py index f01c6292..379336dc 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -45,7 +45,7 @@ def test_item_report(check_report): {filename}:8: unused attribute 'foobar' (60% confidence) {filename}:9: unused variable 'foobar' (60% confidence) {filename}:11: unreachable code after 'return' (100% confidence) -{filename}:13: unused property 'myprop' (60% confidence) +{filename}:14: unused property 'myprop' (60% confidence) """ if sys.version_info >= (3, 8): expected = expected.replace("{filename}:13:", "{filename}:14:") @@ -60,7 +60,7 @@ def test_make_whitelist(check_report): _.foobar # unused attribute ({filename}:8) foobar # unused variable ({filename}:9) # unreachable code after 'return' ({filename}:11) -_.myprop # unused property ({filename}:13) +_.myprop # unused property ({filename}:14) """ if sys.version_info >= (3, 8): expected = expected.replace("{filename}:13)", "{filename}:14)") diff --git a/tests/test_scavenging.py b/tests/test_scavenging.py index 88a5309b..6c601881 100644 --- a/tests/test_scavenging.py +++ b/tests/test_scavenging.py @@ -653,3 +653,82 @@ def foo(foo_li): ) check(v.unused_imports, ["List", "Text"]) + + +def test_item_attr(v): + v.scan("foo.bar = 'bar'") + assert len(v.unused_attrs) == 1 + a = v.unused_attrs[0] + assert a.name == "bar" + assert a.first_lineno == 1 + assert a.last_lineno == 1 + + +def test_item_class(v): + v.scan( + """\ +class Foo: + pass +""" + ) + assert len(v.unused_classes) == 1 + c = v.unused_classes[0] + assert c.name == "Foo" + assert c.first_lineno == 1 + assert c.last_lineno == 2 + + +def test_item_function(v): + v.scan( + """\ +def add(a, b): + return a + b +""" + ) + assert len(v.unused_funcs) == 1 + f = v.unused_funcs[0] + assert f.name == "add" + assert f.first_lineno == 1 + assert f.last_lineno == 2 + + +def test_item_import(v): + v.scan( + """\ +import bar +from foo import * +""" + ) + assert len(v.unused_imports) == 1 + i = v.unused_imports[0] + assert i.name == "bar" + assert i.first_lineno == 1 + assert i.last_lineno == 1 + + +def test_item_property(v): + v.scan( + """\ +class Foo: + @awesomify + @property + @wifi + def bar(self): + pass +Foo() +""" + ) + assert len(v.unused_props) == 1 + p = v.unused_props[0] + assert p.name == "bar" + assert p.first_lineno == 5 + assert p.last_lineno == 6 + + +def test_item_variable(v): + v.scan("v = 'Vulture'") + assert len(v.unused_vars) == 1 + var = v.unused_vars[0] + assert var.name == "v" + assert var.first_lineno == 1 + assert var.last_lineno == 1 diff --git a/tests/test_size.py b/tests/test_size.py index bf2cb507..90c771f7 100644 --- a/tests/test_size.py +++ b/tests/test_size.py @@ -84,6 +84,18 @@ def bar(self): check_size(example, size) +def test_size_property_def(): + example = """ +class Foo: + @foo + @property + @xoo + def zoo(self): + pass +""" + check_size(example, 6) + + def test_size_while(): example = """ class Foo: diff --git a/vulture/core.py b/vulture/core.py index 3863339e..4e544f3f 100644 --- a/vulture/core.py +++ b/vulture/core.py @@ -461,6 +461,15 @@ def _define( confidence=DEFAULT_CONFIDENCE, ignore=None, ): + def _get_first_lineno(first_node, typ): + if typ == "property" and sys.version_info < (3, 8): + # For python < 3.8, increment as many line numbers as there are + # decorators on the method. + # From Python3.8 onwards, lineno for property is the same as + # that of decorated method. + return first_node.lineno + len(first_node.decorator_list) + return first_node.lineno + last_node = last_node or first_node typ = collection.typ if (ignore and ignore(self.filename, name)) or _match( @@ -468,7 +477,7 @@ def _define( ): self._log('Ignoring {typ} "{name}"'.format(**locals())) else: - first_lineno = first_node.lineno + first_lineno = _get_first_lineno(first_node, typ) last_lineno = lines.get_last_line_number(last_node) collection.append( Item(