Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions pre_commit_hooks/check_docstring_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
))


def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
def check_docstring_first(src: bytes, filename: str) -> int:
"""Returns nonzero if the source has what looks like a docstring that is
not at the beginning of the source.

Expand All @@ -20,9 +20,14 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
"""
found_docstring_line = None
found_code_line = None
assignment_lines = set()

tok_gen = tokenize_tokenize(io.BytesIO(src).readline)
for tok_type, _, (sline, scol), _, _ in tok_gen:
for tok_type, string, (sline, scol), _, _ in tok_gen:
# Save all lines with top-level attribute assignments
if scol == 2 and tok_type == tokenize.OP and string == '=':
assignment_lines.add(sline)

# Looks like a docstring!
if tok_type == tokenize.STRING and scol == 0:
if found_docstring_line is not None:
Expand All @@ -31,7 +36,10 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
f'(first docstring on line {found_docstring_line}).',
)
return 1
elif found_code_line is not None:
elif (
found_code_line is not None and
sline > 0 and sline - 1 not in assignment_lines
):
print(
f'{filename}:{sline} Module docstring appears after code '
f'(code seen on line {found_code_line}).',
Expand All @@ -55,6 +63,6 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
for filename in args.filenames:
with open(filename, 'rb') as f:
contents = f.read()
retv |= check_docstring_first(contents, filename=filename)
retv |= check_docstring_first(contents, filename)

return retv
9 changes: 8 additions & 1 deletion tests/check_docstring_first_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
),
# String literals in expressions are ok.
(b'x = "foo"\n', 0, ''),
# Attribute docstrings are ok.
(
b'x = "foo"\n'
b'"""x holds the foo"""',
0,
'',
),
)


Expand All @@ -48,7 +55,7 @@

@all_tests
def test_unit(capsys, contents, expected, expected_out):
assert check_docstring_first(contents) == expected
assert check_docstring_first(contents, '<unknown>') == expected
assert capsys.readouterr()[0] == expected_out.format(filename='<unknown>')


Expand Down