Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit c9feef2

Browse files
sambhavNurdok
authored andcommitted
Fix decorator parsing (#411)
Fixes #410 The bug existed because we were not accounting for cases which should be treated as a logical new line.
1 parent 4e39a57 commit c9feef2

File tree

3 files changed

+161
-12
lines changed

3 files changed

+161
-12
lines changed

docs/release_notes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Bug Fixes
2424
backslashes for line continuation or unicode literals ``\u...`` and
2525
``\N...`` anymore. These are considered intended elements of the docstring
2626
and thus should not be escaped by using a raw docstring (#365).
27+
* Fix decorator parsing (#411).
2728

2829
4.0.1 - August 14th, 2019
2930
-------------------------
@@ -38,7 +39,7 @@ Bug Fixes
3839
the violation code table (#396).
3940
* Fixed IndentationError when parsing function arguments (#392).
4041

41-
42+
4243
4.0.0 - July 6th, 2019
4344
----------------------
4445

src/pydocstyle/parser.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,12 @@ def move(self):
267267
current = self._next_from_generator()
268268
self.current = None if current is None else Token(*current)
269269
self.line = self.current.start[0] if self.current else self.line
270-
self.got_logical_newline = (previous.kind in self.LOGICAL_NEWLINES)
270+
is_logical_blank = previous.kind in (tk.NL, tk.COMMENT)
271+
self.got_logical_newline = (
272+
previous.kind in self.LOGICAL_NEWLINES
273+
# Retain logical_newline status if last line was logically blank
274+
or (self.got_logical_newline and is_logical_blank)
275+
)
271276
return previous
272277

273278
def _next_from_generator(self):

src/tests/parser_test.py

Lines changed: 153 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -441,18 +441,45 @@ def foo():
441441
parser.parse(code, 'file_path')
442442

443443

444-
def test_matrix_multiplication_with_decorators():
444+
@pytest.mark.parametrize("code", (
445+
CodeSnippet("""
446+
def foo():
447+
a @ b
448+
(a
449+
@b)
450+
@a
451+
def b():
452+
pass
453+
"""),
454+
CodeSnippet("""
455+
def foo():
456+
a @ b
457+
(a
458+
@b)
459+
a\
460+
@b
461+
@a
462+
def b():
463+
pass
464+
"""),
465+
CodeSnippet("""
466+
def foo():
467+
a @ b
468+
(a
469+
470+
# A random comment here
471+
472+
@b)
473+
a\
474+
@b
475+
@a
476+
def b():
477+
pass
478+
"""),
479+
))
480+
def test_matrix_multiplication_with_decorators(code):
445481
"""Make sure 'a @ b' doesn't trip the parser."""
446482
parser = Parser()
447-
code = CodeSnippet("""
448-
def foo():
449-
a @ b
450-
(a
451-
@b)
452-
@a
453-
def b():
454-
pass
455-
""")
456483
module = parser.parse(code, 'file_path')
457484

458485
outer_function, = module.children
@@ -686,3 +713,119 @@ def test_invalid_syntax(code):
686713
parser = Parser()
687714
with pytest.raises(ParseError):
688715
module = parser.parse(code, "filepath")
716+
717+
718+
@pytest.mark.parametrize("code", (
719+
CodeSnippet("""\
720+
'''Test this'''
721+
722+
@property
723+
def test():
724+
pass
725+
"""),
726+
CodeSnippet("""\
727+
'''Test this'''
728+
729+
730+
731+
@property
732+
def test():
733+
pass
734+
"""),
735+
CodeSnippet("""\
736+
'''Test this'''
737+
@property
738+
739+
def test():
740+
pass
741+
"""),
742+
CodeSnippet("""\
743+
'''Test this'''
744+
745+
746+
@property
747+
748+
def test():
749+
pass
750+
"""),
751+
CodeSnippet("""\
752+
'''Test this'''
753+
754+
# A random comment in the middle to break things
755+
756+
757+
@property
758+
759+
def test():
760+
pass
761+
"""),
762+
))
763+
def test_parsing_function_decorators(code):
764+
"""Test to ensure we are correctly parsing function decorators."""
765+
parser = Parser()
766+
module = parser.parse(code, "filename")
767+
function, = module.children
768+
decorator_names = {dec.name for dec in function.decorators}
769+
assert "property" in decorator_names
770+
771+
772+
@pytest.mark.parametrize("code", (
773+
CodeSnippet("""\
774+
class Test:
775+
@property
776+
def test(self):
777+
pass
778+
"""),
779+
CodeSnippet("""\
780+
class Test:
781+
782+
783+
784+
@property
785+
def test(self):
786+
pass
787+
"""),
788+
CodeSnippet("""\
789+
class Test:
790+
791+
792+
# Random comment to trip decorator parsing
793+
794+
@property
795+
def test(self):
796+
pass
797+
"""),
798+
CodeSnippet("""\
799+
class Test:
800+
801+
802+
# Random comment to trip decorator parsing
803+
804+
A = 1
805+
806+
@property
807+
def test(self):
808+
pass
809+
"""),
810+
CodeSnippet("""\
811+
class Test:
812+
813+
814+
# Random comment to trip decorator parsing
815+
816+
A = 1
817+
818+
'''Another random comment'''
819+
820+
@property
821+
def test(self):
822+
pass
823+
"""),
824+
))
825+
def test_parsing_method_decorators(code):
826+
"""Test to ensure we are correctly parsing method decorators."""
827+
parser = Parser()
828+
module = parser.parse(code, "filename")
829+
function, = module.children[0].children
830+
decorator_names = {dec.name for dec in function.decorators}
831+
assert "property" in decorator_names

0 commit comments

Comments
 (0)