# 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()) != 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