Support attribute docstrings

This commit is contained in:
Max Rozentsveyg 2020-05-20 14:02:36 -04:00
parent 0e509ddcbc
commit 4a101bbf99
2 changed files with 20 additions and 5 deletions

View file

@ -11,7 +11,7 @@ NON_CODE_TOKENS = frozenset((
))
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.
@ -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:
@ -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}).',
@ -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

View file

@ -38,6 +38,13 @@ TESTS = (
),
# 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,
'',
),
)
@ -48,7 +55,7 @@ all_tests = pytest.mark.parametrize(
@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>')