File: docstrings.py

package info (click to toggle)
python-hacking 0.10.2-4~bpo8%2B1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-backports
  • size: 256 kB
  • sloc: python: 873; makefile: 37; sh: 16
file content (161 lines) | stat: -rw-r--r-- 6,197 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#  Licensed under the Apache License, Version 2.0 (the "License"); you may
#  not use this file except in compliance with the License. You may obtain
#  a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#  License for the specific language governing permissions and limitations
#  under the License.

import tokenize

from hacking import core

START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"]
END_DOCSTRING_TRIPLE = ['"""', "'''"]


@core.flake8ext
def hacking_docstring_start_space(physical_line, previous_logical, tokens):
    r"""Check for docstring not starting with space.

    OpenStack HACKING guide recommendation for docstring:
    Docstring should not start with space

    Okay: def foo():\n    '''This is good.'''
    Okay: def foo():\n    r'''This is good.'''
    Okay: def foo():\n    a = ''' This is not a docstring.'''
    Okay: def foo():\n    pass\n    ''' This is not.'''
    H401: def foo():\n    ''' This is not.'''
    H401: def foo():\n    r''' This is not.'''
    """
    docstring = is_docstring(tokens, previous_logical)
    if docstring:
        start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE)
        if docstring[len(start_triple)] == ' ':
            # docstrings get tokenized on the last line of the docstring, so
            # we don't know the exact position.
            return (0, "H401: docstring should not start with"
                    " a space")


@core.flake8ext
def hacking_docstring_multiline_end(physical_line, previous_logical, tokens):
    r"""Check multi line docstring end.

    OpenStack HACKING guide recommendation for docstring:
    Docstring should end on a new line

    Okay: '''foobar\nfoo\nbar\n'''
    Okay: def foo():\n    '''foobar\n\nfoo\nbar\n'''
    Okay: class Foo(object):\n    '''foobar\n\nfoo\nbar\n'''
    Okay: def foo():\n    a = '''not\na\ndocstring'''
    Okay: def foo():\n    a = '''not\na\ndocstring'''  # blah
    Okay: def foo():\n    pass\n'''foobar\nfoo\nbar\n   d'''
    H403: def foo():\n    '''foobar\nfoo\nbar\ndocstring'''
    H403: def foo():\n    '''foobar\nfoo\nbar\npretend raw: r'''
    H403: class Foo(object):\n    '''foobar\nfoo\nbar\ndocstring'''\n\n
    """
    docstring = is_docstring(tokens, previous_logical)
    if docstring:
        if '\n' not in docstring:
            # not a multi line
            return
        else:
            last_line = docstring.split('\n')[-1]
        pos = max(last_line.rfind(i) for i in END_DOCSTRING_TRIPLE)
        if len(last_line[:pos].strip()) > 0:
            # Something before the end docstring triple
            return (pos,
                    "H403: multi line docstrings should end on a new line")


@core.flake8ext
def hacking_docstring_multiline_start(physical_line, previous_logical, tokens):
    r"""Check multi line docstring starts immediately with summary.

    OpenStack HACKING guide recommendation for docstring:
    Docstring should start with a one-line summary, less than 80 characters.

    Okay: '''foobar\n\nfoo\nbar\n'''
    Okay: def foo():\n    a = '''\nnot\na docstring\n'''
    H404: def foo():\n    '''\nfoo\nbar\n'''\n\n
    H404: def foo():\n    r'''\nfoo\nbar\n'''\n\n
    """
    docstring = is_docstring(tokens, previous_logical)
    if docstring:
        if '\n' not in docstring:
            # single line docstring
            return
        start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE)
        lines = docstring.split('\n')
        if lines[0].strip() == start_triple:
            # docstrings get tokenized on the last line of the docstring, so
            # we don't know the exact position.
            return (0, "H404: multi line docstring "
                    "should start without a leading new line")


@core.flake8ext
def hacking_docstring_summary(physical_line, previous_logical, tokens):
    r"""Check multi line docstring summary is separated with empty line.

    OpenStack HACKING guide recommendation for docstring:
    Docstring should start with a one-line summary, less than 80 characters.

    Okay: def foo():\n    a = '''\nnot\na docstring\n'''
    Okay: '''foobar\n\nfoo\nbar\n'''
    H405: def foo():\n    '''foobar\nfoo\nbar\n'''
    H405: def foo():\n    r'''foobar\nfoo\nbar\n'''
    H405: def foo():\n    '''foobar\n'''
    """
    docstring = is_docstring(tokens, previous_logical)
    if docstring:
        if '\n' not in docstring:
            # not a multi line docstring
            return
        lines = docstring.split('\n')
        if len(lines) > 1 and len(lines[1].strip()) is not 0:
            # docstrings get tokenized on the last line of the docstring, so
            # we don't know the exact position.
            return (0, "H405: multi line docstring "
                    "summary not separated with an empty line")


def is_docstring(tokens, previous_logical):
    """Return found docstring

    'A docstring is a string literal that occurs as the first statement in a
    module, function, class,'
    http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring
    """
    for token_type, text, start, _, _ in tokens:
        if token_type == tokenize.STRING:
            break
        elif token_type != tokenize.INDENT:
            return False
    else:
        return False
    line = text.lstrip()
    start, start_triple = _find_first_of(line, START_DOCSTRING_TRIPLE)
    if (previous_logical.startswith("def ") or
            previous_logical.startswith("class ")):
        if start == 0:
            return text


def _find_first_of(line, substrings):
    """Find earliest occurrence of one of substrings in line.

    Returns pair of index and found substring, or (-1, None)
    if no occurrences of any of substrings were found in line.
    """
    starts = ((line.find(i), i) for i in substrings)
    found = [(i, sub) for i, sub in starts if i != -1]
    if found:
        return min(found)
    else:
        return -1, None