mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
utils: Move "header check" to a flake8 plugin
If we want to check style, we run 'tox -e flake8': it shouldn't be necessary to run some obscure 'make' command too. Make this possible by moving the sole useful test from the target of this make command to a flake8 plugin. This includes a fix for a header that was previously excluded from checks, but is now included. Signed-off-by: Stephen Finucane <stephen@that.guru>
This commit is contained in:
parent
64b4d7c686
commit
f5c0d64658
@ -26,7 +26,7 @@ universal = 1
|
||||
[flake8]
|
||||
max-line-length = 95
|
||||
ignore = E116,E241,E251
|
||||
exclude = .git,.tox,.venv,tests/*,build/*,sphinx/search/*,sphinx/pycode/pgen2/*,doc/ext/example*.py
|
||||
exclude = .git,.tox,.venv,tests/*,build/*,doc/_build/*,sphinx/search/*,sphinx/pycode/pgen2/*,doc/ext/example*.py
|
||||
|
||||
[mypy]
|
||||
python_version = 2.7
|
||||
|
7
setup.py
7
setup.py
@ -247,6 +247,13 @@ setup(
|
||||
'distutils.commands': [
|
||||
'build_sphinx = sphinx.setup_command:BuildDoc',
|
||||
],
|
||||
# consider moving this to 'flake8:local-plugins' once flake8 3.5.0 is
|
||||
# in the wild:
|
||||
# http://flake8.pycqa.org/en/latest/user/configuration.html\
|
||||
# #using-local-plugins
|
||||
'flake8.extension': [
|
||||
'X101 = utils.checks:sphinx_has_header',
|
||||
],
|
||||
},
|
||||
install_requires=requires,
|
||||
extras_require=extras_require,
|
||||
|
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
@ -37,13 +37,6 @@ def checker(*suffixes, **kwds):
|
||||
coding_re = re.compile(br'coding[:=]\s*([-\w.]+)')
|
||||
|
||||
uni_coding_re = re.compile(r'^#.*coding[:=]\s*([-\w.]+).*')
|
||||
name_mail_re = r'[\w ]+(<.*?>)?'
|
||||
copyright_re = re.compile(r'^ :copyright: Copyright 200\d(-20\d\d)? '
|
||||
r'by %s(, %s)*[,.]$' %
|
||||
(name_mail_re, name_mail_re))
|
||||
license_re = re.compile(r" :license: (.*?).\n")
|
||||
copyright_2_re = re.compile(r'^ %s(, %s)*[,.]$' %
|
||||
(name_mail_re, name_mail_re))
|
||||
not_ix_re = re.compile(r'\bnot\s+\S+?\s+i[sn]\s\S+')
|
||||
is_const_re = re.compile(r'if.*?==\s+(None|False|True)\b')
|
||||
noqa_re = re.compile(r'#\s+NOQA\s*$', re.I)
|
||||
@ -95,66 +88,6 @@ def check_style(fn, lines):
|
||||
yield lno + 1, 'using == None/True/False'
|
||||
|
||||
|
||||
@checker('.py', only_pkg=True)
|
||||
def check_fileheader(fn, lines):
|
||||
# line number correction
|
||||
offset = 1
|
||||
if lines[0:1] == ['#!/usr/bin/env python\n']:
|
||||
lines = lines[1:]
|
||||
offset = 2
|
||||
|
||||
llist = []
|
||||
doc_open = False
|
||||
for lno, line in enumerate(lines):
|
||||
llist.append(line)
|
||||
if lno == 0:
|
||||
if line != '# -*- coding: utf-8 -*-\n':
|
||||
yield 1, "missing coding declaration"
|
||||
elif lno == 1:
|
||||
if line != '"""\n' and line != 'r"""\n':
|
||||
yield 2, 'missing docstring begin (""")'
|
||||
else:
|
||||
doc_open = True
|
||||
elif doc_open:
|
||||
if line == '"""\n':
|
||||
# end of docstring
|
||||
if lno <= 4:
|
||||
yield lno + offset, "missing module name in docstring"
|
||||
break
|
||||
|
||||
if line != '\n' and line[:4] != ' ' and doc_open:
|
||||
yield lno + offset, "missing correct docstring indentation"
|
||||
|
||||
if lno == 2:
|
||||
# if not in package, don't check the module name
|
||||
mod_name = fn[:-3].replace('/', '.').replace('.__init__', '')
|
||||
while mod_name:
|
||||
if line.lower()[4:-1] == mod_name:
|
||||
break
|
||||
mod_name = '.'.join(mod_name.split('.')[1:])
|
||||
else:
|
||||
yield 3, "wrong module name in docstring heading"
|
||||
mod_name_len = len(line.strip())
|
||||
elif lno == 3:
|
||||
if line.strip() != mod_name_len * '~':
|
||||
yield 4, "wrong module name underline, should be ~~~...~"
|
||||
else:
|
||||
yield 0, "missing end and/or start of docstring..."
|
||||
|
||||
# check for copyright and license fields
|
||||
license = llist[-2:-1]
|
||||
if not license or not license_re.match(license[0]):
|
||||
yield 0, "no correct license info"
|
||||
|
||||
offset = -3
|
||||
copyright = llist[offset:offset + 1]
|
||||
while copyright and copyright_2_re.match(copyright[0]):
|
||||
offset -= 1
|
||||
copyright = llist[offset:offset + 1]
|
||||
if not copyright or not copyright_re.match(copyright[0]):
|
||||
yield 0, "no correct copyright info"
|
||||
|
||||
|
||||
@checker('.py', '.html', '.rst')
|
||||
def check_whitespace_and_spelling(fn, lines):
|
||||
for lno, line in enumerate(lines):
|
||||
|
111
utils/checks.py
Normal file
111
utils/checks.py
Normal file
@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
utils.checks
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Custom, Sphinx-only flake8 plugins.
|
||||
|
||||
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sphinx
|
||||
|
||||
name_mail_re = r'[\w ]+(<.*?>)?'
|
||||
copyright_re = re.compile(r'^ :copyright: Copyright 200\d(-20\d\d)? '
|
||||
r'by %s(, %s)*[,.]$' % (name_mail_re, name_mail_re))
|
||||
copyright_2_re = re.compile(r'^ %s(, %s)*[,.]$' %
|
||||
(name_mail_re, name_mail_re))
|
||||
license_re = re.compile(r' :license: (.*?).\n')
|
||||
|
||||
|
||||
def flake8ext(_func):
|
||||
"""Decorate flake8_asserts functions"""
|
||||
_func.name = _func.__name__
|
||||
_func.version = sphinx.__version__
|
||||
_func.code = _func.__name__.upper()
|
||||
|
||||
return _func
|
||||
|
||||
|
||||
@flake8ext
|
||||
def sphinx_has_header(physical_line, filename, lines, line_number):
|
||||
"""Check for correct headers.
|
||||
|
||||
Make sure each Python file has a correct file header including
|
||||
copyright and license information.
|
||||
|
||||
X101 invalid header found
|
||||
"""
|
||||
# we have a state machine of sorts so we need to start on line 1. Also,
|
||||
# there's no point checking really short files
|
||||
if line_number != 1 or len(lines) < 10:
|
||||
return
|
||||
|
||||
# this file uses a funky license but unfortunately it's not possible to
|
||||
# ignore specific errors on a file-level basis yet [1]. Simply skip it.
|
||||
#
|
||||
# [1] https://gitlab.com/pycqa/flake8/issues/347
|
||||
if os.path.samefile(filename, './sphinx/util/smartypants.py'):
|
||||
return
|
||||
|
||||
# if the top-level package or not inside the package, ignore
|
||||
mod_name = os.path.splitext(filename)[0].strip('./\\').replace(
|
||||
'/', '.').replace('.__init__', '')
|
||||
if mod_name == 'sphinx' or not mod_name.startswith('sphinx.'):
|
||||
return
|
||||
|
||||
# line number correction
|
||||
offset = 1
|
||||
if lines[0:1] == ['#!/usr/bin/env python\n']:
|
||||
lines = lines[1:]
|
||||
offset = 2
|
||||
|
||||
llist = []
|
||||
doc_open = False
|
||||
|
||||
for lno, line in enumerate(lines):
|
||||
llist.append(line)
|
||||
if lno == 0:
|
||||
if line != '# -*- coding: utf-8 -*-\n':
|
||||
return 0, 'X101 missing coding declaration'
|
||||
elif lno == 1:
|
||||
if line != '"""\n' and line != 'r"""\n':
|
||||
return 0, 'X101 missing docstring begin (""")'
|
||||
else:
|
||||
doc_open = True
|
||||
elif doc_open:
|
||||
if line == '"""\n':
|
||||
# end of docstring
|
||||
if lno <= 4:
|
||||
return 0, 'X101 missing module name in docstring'
|
||||
break
|
||||
|
||||
if line != '\n' and line[:4] != ' ' and doc_open:
|
||||
return 0, 'X101 missing correct docstring indentation'
|
||||
|
||||
if lno == 2:
|
||||
mod_name_len = len(line.strip())
|
||||
if line.strip() != mod_name:
|
||||
return 4, 'X101 wrong module name in docstring heading'
|
||||
elif lno == 3:
|
||||
if line.strip() != mod_name_len * '~':
|
||||
return (4, 'X101 wrong module name underline, should be '
|
||||
'~~~...~')
|
||||
else:
|
||||
return 0, 'X101 missing end and/or start of docstring...'
|
||||
|
||||
# check for copyright and license fields
|
||||
license = llist[-2:-1]
|
||||
if not license or not license_re.match(license[0]):
|
||||
return 0, 'X101 no correct license info'
|
||||
|
||||
offset = -3
|
||||
copyright = llist[offset:offset + 1]
|
||||
while copyright and copyright_2_re.match(copyright[0]):
|
||||
offset -= 1
|
||||
copyright = llist[offset:offset + 1]
|
||||
if not copyright or not copyright_re.match(copyright[0]):
|
||||
return 0, 'X101 no correct copyright info'
|
Loading…
Reference in New Issue
Block a user