• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

domdfcoding / pre-commit-hooks / 14915752281

08 May 2025 08:42PM CUT coverage: 97.5% (-0.08%) from 97.581%
14915752281

push

github

web-flow
Updated files with 'repo_helper'. (#45)

Co-authored-by: repo-helper[bot] <74742576+repo-helper[bot]@users.noreply.github.com>

117 of 120 relevant lines covered (97.5%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/pre_commit_hooks/check_docstring_first.py
1
#!/usr/bin/env python3
2
#
3
#  check_docstring_first.py
4
"""
5
Checks the docstring does not occur after any code.
6
"""
7
#
8
#  Copyright © 2020-2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
9
#  Based on https://github.com/pre-commit/pre-commit-hooks
10
#  Copyright (c) 2014 pre-commit dev team: Anthony Sottile, Ken Struys
11
#
12
#  Modified to permit multiple "docstring" like objects
13
#  as long as the first one is at the top of the file.
14
#  This is useful when writing Sphinx docstrings for attributes, variables etc.
15
#
16
#  Permission is hereby granted, free of charge, to any person obtaining a copy
17
#  of this software and associated documentation files (the "Software"), to deal
18
#  in the Software without restriction, including without limitation the rights
19
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
#  copies of the Software, and to permit persons to whom the Software is
21
#  furnished to do so, subject to the following conditions:
22
#
23
#  The above copyright notice and this permission notice shall be included in all
24
#  copies or substantial portions of the Software.
25
#
26
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
29
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
30
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
31
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
32
#  OR OTHER DEALINGS IN THE SOFTWARE.
33
#
34

35
# stdlib
36
import io
1✔
37
import sys
1✔
38
import tokenize
1✔
39
from tokenize import tokenize as tokenize_tokenize
1✔
40
from typing import Iterable
1✔
41

42
# 3rd party
43
import click
1✔
44
from consolekit import click_command
1✔
45

46
# this package
47
from pre_commit_hooks.util import FAIL, PASS
1✔
48

49
__all__ = ("check_docstring_first", "main")
1✔
50

51
NON_CODE_TOKENS = frozenset((
1✔
52
                tokenize.COMMENT,
53
                tokenize.ENDMARKER,
54
                tokenize.NEWLINE,
55
                tokenize.NL,
56
                tokenize.ENCODING,
57
                ))
58

59

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

65
        A string will be considered a docstring if it is a STRING token with a
66
        col offset of 0.
67
        """  # noqa: D400
68

69
        found_docstring_line = None
1✔
70
        found_code_line = None
1✔
71

72
        tok_gen = tokenize_tokenize(io.BytesIO(src).readline)
1✔
73

74
        for tok_type, _, (sline, scol), _, _ in tok_gen:
1✔
75
                # Looks like a docstring!
76
                if tok_type == tokenize.STRING and scol == 0:
1✔
77
                        if found_docstring_line is not None:
1✔
78
                                pass
1✔
79
                        elif found_code_line is not None:
1✔
80
                                print(
1✔
81
                                                f'{filename}:{sline} Module docstring appears after code '
82
                                                f'(code seen on line {found_code_line}).',
83
                                                )
84
                                return FAIL
1✔
85
                        else:
86
                                found_docstring_line = sline
1✔
87
                elif tok_type not in NON_CODE_TOKENS and found_code_line is None:
1✔
88
                        found_code_line = sline
1✔
89

90
        return PASS
1✔
91

92

93
@click.argument("filenames", nargs=-1, type=click.STRING)
1✔
94
@click_command()
1✔
95
def main(filenames: Iterable[str]) -> None:
1✔
96
        """
97
        Checks the docstring does not occur after any code.
98
        """
99

100
        retv = PASS
1✔
101

102
        for filename in filenames:
1✔
103
                with open(filename, "rb") as f:
1✔
104
                        contents = f.read()
1✔
105
                retv |= check_docstring_first(contents, filename=filename)
1✔
106

107
        sys.exit(retv)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc