""" utils.checks ~~~~~~~~~~~~ Custom, Sphinx-only flake8 plugins. :copyright: Copyright 2007-2019 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 python3\n']: lines = lines[1:] offset = 2 llist = [] doc_open = False for lno, line in enumerate(lines): llist.append(line) if lno == 0: 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 <= 3: 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 == 1: mod_name_len = len(line.strip()) if line.strip() != mod_name: return 2, 'X101 wrong module name in docstring heading' elif lno == 2: if line.strip() != mod_name_len * '~': return (3, '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'