Move hacking to pep8 1.5.6

pep8 1.5.x drastically changed the way comments and docstrings are
handled. Previously docstrings and comments weren't tokenized so we had
to do some extra work to parse them ourselves, but now that they are
tokenized the appropriate hacking rules to use the new
tokenized text.

Also update all doctests and HACKING.rst to add space after '#' in
'# TODO'.

Note: the hacking code changes won't work with pep8 1.4.6, so the code
and dependency are both changed at the same time.

Change-Id: Ib35d493309fe7e87112a94c74beb70054ca0655c
This commit is contained in:
Joe Gordon 2014-04-08 17:01:40 -07:00
parent 983e8dc564
commit 43fb3da5e5
3 changed files with 87 additions and 74 deletions

View File

@ -10,7 +10,7 @@ General
- Use only UNIX style newlines (``\n``), not Windows style (``\r\n``) - Use only UNIX style newlines (``\n``), not Windows style (``\r\n``)
- Wrap long lines in parentheses and not a backslash for line continuation. - Wrap long lines in parentheses and not a backslash for line continuation.
- Do not write ``except:``, use ``except Exception:`` at the very least - Do not write ``except:``, use ``except Exception:`` at the very least
- Include your name with TODOs as in ``#TODO(yourname)`` - Include your name with TODOs as in ``# TODO(yourname)``
- Do not shadow a built-in or reserved word. Example:: - Do not shadow a built-in or reserved word. Example::
def list(): def list():

View File

@ -111,13 +111,12 @@ def hacking_todo_format(physical_line, tokens):
"""Check for 'TODO()'. """Check for 'TODO()'.
OpenStack HACKING guide recommendation for TODO: OpenStack HACKING guide recommendation for TODO:
Include your name with TODOs as in "#TODO(termie)" Include your name with TODOs as in "# TODO(termie)"
Okay: #TODO(sdague)
Okay: # TODO(sdague) Okay: # TODO(sdague)
H101: #TODO fail H101: # TODO fail
H101: #TODO H101: # TODO
H101: #TODO (jogo) fail H101: # TODO (jogo) fail
Okay: TODO = 5 Okay: TODO = 5
""" """
# TODO(jogo): make the following doctests pass: # TODO(jogo): make the following doctests pass:
@ -125,11 +124,12 @@ def hacking_todo_format(physical_line, tokens):
# H101: #TODO(jogo # H101: #TODO(jogo
# TODO(jogo): make this check docstrings as well (don't have to be at top # TODO(jogo): make this check docstrings as well (don't have to be at top
# of function) # of function)
pos = physical_line.find('TODO') for token_type, text, start_index, _, _ in tokens:
pos1 = physical_line.find('TODO(') if token_type == tokenize.COMMENT:
pos2 = physical_line.find('#') # make sure it's a comment pos = text.find('TODO')
if (pos != pos1 and pos2 >= 0 and pos2 < pos and len(tokens) == 0): pos1 = text.find('TODO(')
return pos, "H101: Use TODO(NAME)" if (pos != pos1):
return pos + start_index[1], "H101: Use TODO(NAME)"
def _check_for_exact_apache(start, lines): def _check_for_exact_apache(start, lines):
@ -329,8 +329,8 @@ def hacking_python3x_octal_literals(logical_line, tokens):
H232: f(0755) H232: f(0755)
""" """
for tokentype, text, _, _, _ in tokens: for token_type, text, _, _, _ in tokens:
if tokentype == tokenize.NUMBER: if token_type == tokenize.NUMBER:
match = re.match(r"0+([1-9]\d*)", text) match = re.match(r"0+([1-9]\d*)", text)
if match: if match:
yield 0, ("H232: Python 3.x incompatible octal %s should be " yield 0, ("H232: Python 3.x incompatible octal %s should be "
@ -761,27 +761,30 @@ def _find_first_of(line, substrings):
return -1, None return -1, None
def is_docstring(physical_line, previous_logical): def is_docstring(tokens, previous_logical):
"""Return True if found docstring """Return found docstring
'A docstring is a string literal that occurs as the first statement in a 'A docstring is a string literal that occurs as the first statement in a
module, function, class,' module, function, class,'
http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring
""" """
line = physical_line.lstrip() 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) start, start_triple = _find_first_of(line, START_DOCSTRING_TRIPLE)
end = max([line[-4:-1] == i for i in END_DOCSTRING_TRIPLE])
if (previous_logical.startswith("def ") or if (previous_logical.startswith("def ") or
previous_logical.startswith("class ")): previous_logical.startswith("class ")):
if start == 0: if start == 0:
return True return text
else:
# Handle multi line comments
return end and start in (-1, len(line) - len(start_triple) - 1)
@flake8ext @flake8ext
def hacking_docstring_start_space(physical_line, previous_logical): def hacking_docstring_start_space(physical_line, previous_logical, tokens):
r"""Check for docstring not starting with space. r"""Check for docstring not starting with space.
OpenStack HACKING guide recommendation for docstring: OpenStack HACKING guide recommendation for docstring:
@ -794,23 +797,18 @@ def hacking_docstring_start_space(physical_line, previous_logical):
H401: def foo():\n ''' This is not.''' H401: def foo():\n ''' This is not.'''
H401: def foo():\n r''' This is not.''' H401: def foo():\n r''' This is not.'''
""" """
# short circuit so that we don't fail on our own fail test docstring = is_docstring(tokens, previous_logical)
# when running under external pep8 if docstring:
if physical_line.find("H401: def foo()") != -1: start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE)
return if docstring[len(start_triple)] == ' ':
# docstrings get tokenized on the last line of the docstring, so
# it's important that we determine this is actually a docstring, # we don't know the exact position.
# and not a doc block used somewhere after the first line of a return (0, "H401: docstring should not start with"
# function def
if is_docstring(physical_line, previous_logical):
pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE])
if physical_line[pos + 3] == ' ':
return (pos, "H401: docstring should not start with"
" a space") " a space")
@flake8ext @flake8ext
def hacking_docstring_one_line(physical_line, previous_logical): def hacking_docstring_one_line(physical_line, previous_logical, tokens):
r"""Check one line docstring end. r"""Check one line docstring end.
OpenStack HACKING guide recommendation for one line docstring: OpenStack HACKING guide recommendation for one line docstring:
@ -832,15 +830,15 @@ def hacking_docstring_one_line(physical_line, previous_logical):
H402: class Foo:\n '''Bad punctuation,''' H402: class Foo:\n '''Bad punctuation,'''
H402: class Foo:\n r'''Bad punctuation,''' H402: class Foo:\n r'''Bad punctuation,'''
""" """
# TODO(jogo) make this apply to multi line docstrings as well docstring = is_docstring(tokens, previous_logical)
line = physical_line.lstrip() if docstring:
if is_docstring(physical_line, previous_logical): if '\n' in docstring:
pos = max([line.find(i) for i in START_DOCSTRING_TRIPLE]) # start # multi line docstring
return
line = physical_line.lstrip()
end = max([line[-4:-1] == i for i in END_DOCSTRING_TRIPLE]) # end end = max([line[-4:-1] == i for i in END_DOCSTRING_TRIPLE]) # end
if line[-5] not in ['.', '?', '!']:
if pos != -1 and end and len(line) > pos + 4: return end, "H402: one line docstring needs punctuation."
if line[-5] not in ['.', '?', '!']:
return pos, "H402: one line docstring needs punctuation."
@flake8ext @flake8ext
@ -854,17 +852,22 @@ def hacking_docstring_multiline_end(physical_line, previous_logical, tokens):
Okay: def foo():\n '''foobar\n\nfoo\nbar\n''' Okay: def foo():\n '''foobar\n\nfoo\nbar\n'''
Okay: class Foo:\n '''foobar\n\nfoo\nbar\n''' Okay: class Foo:\n '''foobar\n\nfoo\nbar\n'''
Okay: def foo():\n a = '''not\na\ndocstring''' 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''' 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\ndocstring'''
H403: def foo():\n '''foobar\nfoo\nbar\npretend raw: r''' H403: def foo():\n '''foobar\nfoo\nbar\npretend raw: r'''
H403: class Foo:\n '''foobar\nfoo\nbar\ndocstring'''\n\n H403: class Foo:\n '''foobar\nfoo\nbar\ndocstring'''\n\n
""" """
# if find OP tokens, not a docstring docstring = is_docstring(tokens, previous_logical)
ops = [t for t, _, _, _, _ in tokens if t == tokenize.OP] if docstring:
if (is_docstring(physical_line, previous_logical) and len(tokens) > 0 and if '\n' not in docstring:
len(ops) == 0): # not a multi line
pos = max(physical_line.find(i) for i in END_DOCSTRING_TRIPLE) return
if physical_line.strip() not in START_DOCSTRING_TRIPLE: 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, return (pos,
"H403: multi line docstrings should end on a new line") "H403: multi line docstrings should end on a new line")
@ -881,19 +884,23 @@ def hacking_docstring_multiline_start(physical_line, previous_logical, tokens):
H404: def foo():\n '''\nfoo\nbar\n'''\n\n H404: def foo():\n '''\nfoo\nbar\n'''\n\n
H404: def foo():\n r'''\nfoo\nbar\n'''\n\n H404: def foo():\n r'''\nfoo\nbar\n'''\n\n
""" """
if is_docstring(physical_line, previous_logical): docstring = is_docstring(tokens, previous_logical)
pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE]) if docstring:
# start of docstring when len(tokens)==0 if '\n' not in docstring:
if len(tokens) == 0 and pos != -1 and len(physical_line) == pos + 4: # single line docstring
if physical_line.strip() in START_DOCSTRING_TRIPLE: return
return (pos, "H404: multi line docstring " start, start_triple = _find_first_of(docstring, START_DOCSTRING_TRIPLE)
"should start without a leading new line") 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")
@flake8ext @flake8ext
def hacking_docstring_summary(physical_line, previous_logical, tokens, lines, def hacking_docstring_summary(physical_line, previous_logical, tokens):
line_number): r"""Check multi line docstring summary is separated with empty line.
r"""Check multi line docstring summary is separted with empty line.
OpenStack HACKING guide recommendation for docstring: OpenStack HACKING guide recommendation for docstring:
Docstring should start with a one-line summary, less than 80 characters. Docstring should start with a one-line summary, less than 80 characters.
@ -904,15 +911,17 @@ def hacking_docstring_summary(physical_line, previous_logical, tokens, lines,
H405: def foo():\n r'''foobar\nfoo\nbar\n''' H405: def foo():\n r'''foobar\nfoo\nbar\n'''
H405: def foo():\n '''foobar\n''' H405: def foo():\n '''foobar\n'''
""" """
if is_docstring(physical_line, previous_logical): docstring = is_docstring(tokens, previous_logical)
if (len(tokens) == 0 and if docstring:
not physical_line.strip().endswith( if '\n' not in docstring:
tuple(END_DOCSTRING_TRIPLE))): # not a multi line docstring
# start of multiline docstring return
if lines[line_number].strip(): lines = docstring.split('\n')
# second line is not empty if len(lines) > 1 and len(lines[1].strip()) is not 0:
return (len(physical_line) - 1, "H405: multi line docstring " # docstrings get tokenized on the last line of the docstring, so
"summary not separated with an empty line") # we don't know the exact position.
return (0, "H405: multi line docstring "
"summary not separated with an empty line")
@flake8ext @flake8ext
@ -1085,15 +1094,19 @@ def hacking_no_cr(physical_line):
@flake8ext @flake8ext
def hacking_no_backsplash_line_continuation(physical_line): def hacking_no_backsplash_line_continuation(logical_line, tokens):
r"""Wrap lines in parentheses and not a backslash for line continuation. r"""Wrap lines in parentheses and not a backslash for line continuation.
Okay: a = (5 +\n 6) Okay: a = (5 +\n 6)
H904: b = 5 + \\\n 6 H904: b = 5 + \\\n 6
""" """
if len(physical_line) > 2 and physical_line[-2] == '\\': found = False
return (len(physical_line) - 2, for token_type, text, start_index, stop_index, line in tokens:
"H904: Wrap long lines in parentheses instead of a backslash") if line.rstrip('\r\n').endswith('\\') and not found:
found = True
yield ((start_index[0], start_index[1]+len(line.strip())-1),
"H904: Wrap long lines in parentheses instead of a "
"backslash")
class GlobalCheck(object): class GlobalCheck(object):

View File

@ -1,6 +1,6 @@
pbr>=0.6,!=0.7,<1.0 pbr>=0.6,!=0.7,<1.0
pep8==1.4.6 pep8==1.5.6
pyflakes==0.8.1 pyflakes==0.8.1
flake8==2.1.0 flake8==2.1.0