Skip to content

Commit

Permalink
Report docstring content violations on docstring start line (fixes Py…
Browse files Browse the repository at this point in the history
  • Loading branch information
lordmauve committed Dec 18, 2017
1 parent df872e4 commit 501b6f4
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 2 deletions.
28 changes: 27 additions & 1 deletion src/pydocstyle/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ class Definition(Value):
def __iter__(self):
return chain([self], *self.children)

@property
def error_lineno(self):
"""Get the line number with which to report violations."""
if isinstance(self.docstring, Docstring):
return self.docstring.start
return self.start

@property
def _publicity(self):
return {True: 'public', False: 'private'}[self.is_public]
Expand Down Expand Up @@ -210,6 +217,21 @@ class Decorator(Value):
_fields = 'name arguments'.split()


class Docstring(str):
"""Represent a docstring.
This is a string, but has additional start/end attributes representing
the start and end of the token.
"""
def __new__(cls, v, start, end):
return str.__new__(cls, v)

def __init__(self, v, start, end):
self.start = start
self.end = end


VARIADIC_MAGIC_METHODS = ('__init__', '__call__', '__new__')


Expand Down Expand Up @@ -334,7 +356,11 @@ def parse_docstring(self):
self.log.debug("parsing docstring, token is %r (%s)",
self.current.kind, self.current.value)
if self.current.kind == tk.STRING:
docstring = self.current.value
docstring = Docstring(
self.current.value,
self.current.start[0],
self.current.end[0]
)
self.stream.move()
return docstring
return None
Expand Down
2 changes: 1 addition & 1 deletion src/pydocstyle/violations.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def set_context(self, definition, explanation):
self.explanation = explanation

filename = property(lambda self: self.definition.module.name)
line = property(lambda self: self.definition.start)
line = property(lambda self: self.definition.error_lineno)

@property
def message(self):
Expand Down
14 changes: 14 additions & 0 deletions src/tests/parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ def do_something(pos_param0, pos_param1, kw_param0="default"):
assert function.decorators == []
assert function.children == []
assert function.docstring == '"""Do something."""'
assert function.docstring.start == 2
assert function.docstring.end == 2
assert function.kind == 'function'
assert function.parent == module
assert function.start == 1
assert function.end == 3
assert function.error_lineno == 2
assert function.source == code.getvalue()
assert function.is_public
assert str(function) == 'in public function `do_something`'
Expand Down Expand Up @@ -95,6 +98,7 @@ def inner_function():
assert outer_function.parent == module
assert outer_function.start == 1
assert outer_function.end == 6
assert outer_function.error_lineno == 2
assert outer_function.source == code.getvalue()
assert outer_function.is_public
assert str(outer_function) == 'in public function `outer_function`'
Expand All @@ -107,6 +111,7 @@ def inner_function():
assert inner_function.parent == outer_function
assert inner_function.start == 3
assert inner_function.end == 5
assert inner_function.error_lineno == 4
assert textwrap.dedent(inner_function.source) == textwrap.dedent("""\
def inner_function():
'''This is the inner function.'''
Expand Down Expand Up @@ -239,6 +244,7 @@ class TestedClass(object):
assert klass.parent == module
assert klass.start == 1
assert klass.end == 3
assert klass.error_lineno == 3
assert klass.source == code.getvalue()
assert klass.is_public
assert str(klass) == 'in public class `TestedClass`'
Expand All @@ -264,6 +270,7 @@ def do_it(param):
assert klass.parent == module
assert klass.start == 1
assert klass.end == 5
assert klass.error_lineno == 1
assert klass.source == code.getvalue()
assert klass.is_public
assert str(klass) == 'in public class `TestedClass`'
Expand All @@ -276,6 +283,7 @@ def do_it(param):
assert method.parent == klass
assert method.start == 2
assert method.end == 5
assert method.error_lineno == 3
assert textwrap.dedent(method.source) == textwrap.dedent("""\
def do_it(param):
\"""Do the 'it'\"""
Expand Down Expand Up @@ -307,6 +315,7 @@ def _do_it(param):
assert klass.parent == module
assert klass.start == 1
assert klass.end == 5
assert klass.error_lineno == 1
assert klass.source == code.getvalue()
assert klass.is_public
assert str(klass) == 'in public class `TestedClass`'
Expand All @@ -319,6 +328,7 @@ def _do_it(param):
assert method.parent == klass
assert method.start == 2
assert method.end == 5
assert method.error_lineno == 3
assert textwrap.dedent(method.source) == textwrap.dedent("""\
def _do_it(param):
\"""Do the 'it'\"""
Expand Down Expand Up @@ -348,6 +358,7 @@ def __str__(self):
assert klass.parent == module
assert klass.start == 1
assert klass.end == 3
assert klass.error_lineno == 1
assert klass.source == code.getvalue()
assert klass.is_public
assert str(klass) == 'in public class `TestedClass`'
Expand All @@ -360,6 +371,7 @@ def __str__(self):
assert method.parent == klass
assert method.start == 2
assert method.end == 3
assert method.error_lineno == 2
assert textwrap.dedent(method.source) == textwrap.dedent("""\
def __str__(self):
return "me"
Expand Down Expand Up @@ -388,6 +400,7 @@ class InnerClass(object):
assert outer_class.parent == module
assert outer_class.start == 1
assert outer_class.end == 4
assert outer_class.error_lineno == 2
assert outer_class.source == code.getvalue()
assert outer_class.is_public
assert str(outer_class) == 'in public class `OuterClass`'
Expand All @@ -401,6 +414,7 @@ class InnerClass(object):
assert inner_class.parent == outer_class
assert inner_class.start == 3
assert inner_class.end == 4
assert inner_class.error_lineno == 4
assert textwrap.dedent(inner_class.source) == textwrap.dedent("""\
class InnerClass(object):
"An inner docstring."
Expand Down

0 comments on commit 501b6f4

Please sign in to comment.