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

Commit 501b6f4

Browse files
committed
Report docstring content violations on docstring start line (fixes #83)
1 parent df872e4 commit 501b6f4

File tree

3 files changed

+42
-2
lines changed

3 files changed

+42
-2
lines changed

src/pydocstyle/parser.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ class Definition(Value):
8080
def __iter__(self):
8181
return chain([self], *self.children)
8282

83+
@property
84+
def error_lineno(self):
85+
"""Get the line number with which to report violations."""
86+
if isinstance(self.docstring, Docstring):
87+
return self.docstring.start
88+
return self.start
89+
8390
@property
8491
def _publicity(self):
8592
return {True: 'public', False: 'private'}[self.is_public]
@@ -210,6 +217,21 @@ class Decorator(Value):
210217
_fields = 'name arguments'.split()
211218

212219

220+
class Docstring(str):
221+
"""Represent a docstring.
222+
223+
This is a string, but has additional start/end attributes representing
224+
the start and end of the token.
225+
226+
"""
227+
def __new__(cls, v, start, end):
228+
return str.__new__(cls, v)
229+
230+
def __init__(self, v, start, end):
231+
self.start = start
232+
self.end = end
233+
234+
213235
VARIADIC_MAGIC_METHODS = ('__init__', '__call__', '__new__')
214236

215237

@@ -334,7 +356,11 @@ def parse_docstring(self):
334356
self.log.debug("parsing docstring, token is %r (%s)",
335357
self.current.kind, self.current.value)
336358
if self.current.kind == tk.STRING:
337-
docstring = self.current.value
359+
docstring = Docstring(
360+
self.current.value,
361+
self.current.start[0],
362+
self.current.end[0]
363+
)
338364
self.stream.move()
339365
return docstring
340366
return None

src/pydocstyle/violations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def set_context(self, definition, explanation):
3838
self.explanation = explanation
3939

4040
filename = property(lambda self: self.definition.module.name)
41-
line = property(lambda self: self.definition.start)
41+
line = property(lambda self: self.definition.error_lineno)
4242

4343
@property
4444
def message(self):

src/tests/parser_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ def do_something(pos_param0, pos_param1, kw_param0="default"):
3636
assert function.decorators == []
3737
assert function.children == []
3838
assert function.docstring == '"""Do something."""'
39+
assert function.docstring.start == 2
40+
assert function.docstring.end == 2
3941
assert function.kind == 'function'
4042
assert function.parent == module
4143
assert function.start == 1
4244
assert function.end == 3
45+
assert function.error_lineno == 2
4346
assert function.source == code.getvalue()
4447
assert function.is_public
4548
assert str(function) == 'in public function `do_something`'
@@ -95,6 +98,7 @@ def inner_function():
9598
assert outer_function.parent == module
9699
assert outer_function.start == 1
97100
assert outer_function.end == 6
101+
assert outer_function.error_lineno == 2
98102
assert outer_function.source == code.getvalue()
99103
assert outer_function.is_public
100104
assert str(outer_function) == 'in public function `outer_function`'
@@ -107,6 +111,7 @@ def inner_function():
107111
assert inner_function.parent == outer_function
108112
assert inner_function.start == 3
109113
assert inner_function.end == 5
114+
assert inner_function.error_lineno == 4
110115
assert textwrap.dedent(inner_function.source) == textwrap.dedent("""\
111116
def inner_function():
112117
'''This is the inner function.'''
@@ -239,6 +244,7 @@ class TestedClass(object):
239244
assert klass.parent == module
240245
assert klass.start == 1
241246
assert klass.end == 3
247+
assert klass.error_lineno == 3
242248
assert klass.source == code.getvalue()
243249
assert klass.is_public
244250
assert str(klass) == 'in public class `TestedClass`'
@@ -264,6 +270,7 @@ def do_it(param):
264270
assert klass.parent == module
265271
assert klass.start == 1
266272
assert klass.end == 5
273+
assert klass.error_lineno == 1
267274
assert klass.source == code.getvalue()
268275
assert klass.is_public
269276
assert str(klass) == 'in public class `TestedClass`'
@@ -276,6 +283,7 @@ def do_it(param):
276283
assert method.parent == klass
277284
assert method.start == 2
278285
assert method.end == 5
286+
assert method.error_lineno == 3
279287
assert textwrap.dedent(method.source) == textwrap.dedent("""\
280288
def do_it(param):
281289
\"""Do the 'it'\"""
@@ -307,6 +315,7 @@ def _do_it(param):
307315
assert klass.parent == module
308316
assert klass.start == 1
309317
assert klass.end == 5
318+
assert klass.error_lineno == 1
310319
assert klass.source == code.getvalue()
311320
assert klass.is_public
312321
assert str(klass) == 'in public class `TestedClass`'
@@ -319,6 +328,7 @@ def _do_it(param):
319328
assert method.parent == klass
320329
assert method.start == 2
321330
assert method.end == 5
331+
assert method.error_lineno == 3
322332
assert textwrap.dedent(method.source) == textwrap.dedent("""\
323333
def _do_it(param):
324334
\"""Do the 'it'\"""
@@ -348,6 +358,7 @@ def __str__(self):
348358
assert klass.parent == module
349359
assert klass.start == 1
350360
assert klass.end == 3
361+
assert klass.error_lineno == 1
351362
assert klass.source == code.getvalue()
352363
assert klass.is_public
353364
assert str(klass) == 'in public class `TestedClass`'
@@ -360,6 +371,7 @@ def __str__(self):
360371
assert method.parent == klass
361372
assert method.start == 2
362373
assert method.end == 3
374+
assert method.error_lineno == 2
363375
assert textwrap.dedent(method.source) == textwrap.dedent("""\
364376
def __str__(self):
365377
return "me"
@@ -388,6 +400,7 @@ class InnerClass(object):
388400
assert outer_class.parent == module
389401
assert outer_class.start == 1
390402
assert outer_class.end == 4
403+
assert outer_class.error_lineno == 2
391404
assert outer_class.source == code.getvalue()
392405
assert outer_class.is_public
393406
assert str(outer_class) == 'in public class `OuterClass`'
@@ -401,6 +414,7 @@ class InnerClass(object):
401414
assert inner_class.parent == outer_class
402415
assert inner_class.start == 3
403416
assert inner_class.end == 4
417+
assert inner_class.error_lineno == 4
404418
assert textwrap.dedent(inner_class.source) == textwrap.dedent("""\
405419
class InnerClass(object):
406420
"An inner docstring."

0 commit comments

Comments
 (0)