Skip to content

Commit

Permalink
Better line-too-long checker so we can pass on sphinx-doc's doc. (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
JulienPalard authored May 23, 2022
1 parent cecc01d commit 4c8d4a6
Showing 1 changed file with 62 additions and 38 deletions.
100 changes: 62 additions & 38 deletions sphinxlint.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def deco(func):


@checker(".py", rst_only=False)
def check_python_syntax(file, lines):
def check_python_syntax(file, lines, options=None):
"""Search invalid syntax in Python examples."""
code = "".join(lines)
if "\r" in code:
Expand All @@ -198,7 +198,7 @@ def is_in_a_table(error, line):


@checker(".rst")
def check_missing_backtick_after_role(file, lines):
def check_missing_backtick_after_role(file, lines, options=None):
"""Search for roles missing their closing backticks.
Bad: :fct:`foo
Expand All @@ -214,7 +214,7 @@ def check_missing_backtick_after_role(file, lines):


@checker(".rst")
def check_missing_space_after_literal(file, lines):
def check_missing_space_after_literal(file, lines, options=None):
r"""Search for inline literals immediately followed by a character.
Bad: ``items``s
Expand Down Expand Up @@ -259,7 +259,7 @@ def paragraphs(lines):


@checker(".rst", enabled=False)
def check_default_role(file, lines):
def check_default_role(file, lines, options=None):
"""Search for default roles (but they are allowed in many projects).
Bad: `print`
Expand All @@ -271,7 +271,7 @@ def check_default_role(file, lines):


@checker(".rst")
def check_directive_with_three_dots(file, lines):
def check_directive_with_three_dots(file, lines, options=None):
"""Search for directives with three dots instead of two.
Bad: ... versionchanged:: 3.6
Expand All @@ -283,7 +283,7 @@ def check_directive_with_three_dots(file, lines):


@checker(".rst")
def check_directive_missing_colons(file, lines):
def check_directive_missing_colons(file, lines, options=None):
"""Search for directive wrongly typed as comments.
Bad: .. versionchanged 3.6.
Expand All @@ -295,7 +295,7 @@ def check_directive_missing_colons(file, lines):


@checker(".rst")
def check_missing_space_after_role(file, lines):
def check_missing_space_after_role(file, lines, options=None):
r"""Search for roles immediately followed by a character.
Bad: :exc:`Exception`s.
Expand All @@ -313,7 +313,7 @@ def check_missing_space_after_role(file, lines):


@checker(".rst")
def check_role_without_backticks(file, lines):
def check_role_without_backticks(file, lines, options=None):
"""Search roles without backticks.
Bad: :func:pdb.main
Expand All @@ -326,7 +326,7 @@ def check_role_without_backticks(file, lines):


@checker(".rst")
def check_backtick_before_role(file, lines):
def check_backtick_before_role(file, lines, options=None):
"""Search for roles preceded by a backtick.
Bad: `:fct:`sum`
Expand All @@ -340,7 +340,7 @@ def check_backtick_before_role(file, lines):


@checker(".rst")
def check_missing_space_in_hyperlink(file, lines):
def check_missing_space_in_hyperlink(file, lines, options=None):
"""Search for hyperlinks missing a space.
Bad: `Link text<https://example.com>_`
Expand All @@ -355,7 +355,7 @@ def check_missing_space_in_hyperlink(file, lines):


@checker(".rst")
def check_missing_underscore_after_hyperlink(file, lines):
def check_missing_underscore_after_hyperlink(file, lines, options=None):
"""Search for hyperlinks missing underscore after their closing backtick.
Bad: `Link text <https://example.com>`
Expand All @@ -370,7 +370,7 @@ def check_missing_underscore_after_hyperlink(file, lines):


@checker(".rst")
def check_role_with_double_backticks(file, lines):
def check_role_with_double_backticks(file, lines, options=None):
"""Search for roles with double backticks.
Bad: :fct:``sum``
Expand All @@ -384,7 +384,7 @@ def check_role_with_double_backticks(file, lines):


@checker(".rst")
def check_missing_space_before_role(file, lines):
def check_missing_space_before_role(file, lines, options=None):
"""Search for missing spaces before roles.
Bad: the:fct:`sum`
Expand All @@ -398,7 +398,7 @@ def check_missing_space_before_role(file, lines):


@checker(".rst")
def check_missing_colon_in_role(file, lines):
def check_missing_colon_in_role(file, lines, options=None):
"""Search for missing colons in roles.
Bad: :issue`123`
Expand All @@ -410,23 +410,23 @@ def check_missing_colon_in_role(file, lines):


@checker(".py", ".rst", rst_only=False)
def check_carriage_return(file, lines):
def check_carriage_return(file, lines, options=None):
r"""Check for carriage returns (\r) in lines."""
for lno, line in enumerate(lines):
if "\r" in line:
yield lno + 1, "\\r in line"


@checker(".py", ".rst", rst_only=False)
def check_horizontal_tab(file, lines):
def check_horizontal_tab(file, lines, options=None):
r"""Check for horizontal tabs (\t) in lines."""
for lno, line in enumerate(lines):
if "\t" in line:
yield lno + 1, "OMG TABS!!!1"


@checker(".py", ".rst", rst_only=False)
def check_trailing_whitespace(file, lines):
def check_trailing_whitespace(file, lines, options=None):
"""Check for trailing whitespaces at end of lines."""
for lno, line in enumerate(lines):
stripped_line = line.rstrip("\n")
Expand All @@ -435,30 +435,33 @@ def check_trailing_whitespace(file, lines):


@checker(".py", ".rst", rst_only=False)
def check_missing_final_newline(file, lines):
def check_missing_final_newline(file, lines, options=None):
"""Check that the last line of the file ends with a newline."""
if lines and not lines[-1].endswith("\n"):
yield len(lines), "No newline at end of file."


@checker(".rst", enabled=False, rst_only=False)
def check_line_too_long(file, lines):
@checker(".rst", enabled=False, rst_only=True)
def check_line_too_long(file, lines, options=None):
"""Check for line length; this checker is not run by default."""
for lno, line in enumerate(lines):
if len(line) > 81:
# don't complain about tables, links and function signatures
if (
line.lstrip()[0] not in "+|"
and "http://" not in line
and not line.lstrip().startswith(
(".. function", ".. method", ".. cfunction")
)
):
yield lno + 1, "line too long"
# Beware, in `line` we have the trailing newline.
if len(line) - 1 > options.max_line_length:
if line.lstrip()[0] in "+|":
continue # ignore wide tables
if re.match(r"^\s*\W*(:(\w+:)+)?`.*`\W*$", line):
continue # ignore long interpreted text
if re.match(r"^\s*\.\. ", line):
continue # ignore directives and hyperlink targets
if re.match(r"^\s*__ ", line):
continue # ignore anonymous hyperlink targets
if re.match(r"^\s*``[^`]+``$", line):
continue # ignore a very long literal string
yield lno + 1, f"Line too long ({len(line)-1}/{options.max_line_length})"


@checker(".html", enabled=False, rst_only=False)
def check_leaked_markup(file, lines):
def check_leaked_markup(file, lines, options=None):
"""Check HTML files for leaked reST markup.
This only works if the HTML files have been built.
Expand Down Expand Up @@ -533,7 +536,7 @@ def type_of_explicit_markup(line):


@checker(".rst", enabled=False)
def check_triple_backticks(file, lines):
def check_triple_backticks(file, lines, options=None):
"""Check for triple backticks, like ```Point``` (but it's a valid syntax).
Bad: ```Point```
Expand All @@ -550,7 +553,7 @@ def check_triple_backticks(file, lines):


@checker(".rst", rst_only=False)
def check_bad_dedent(file, lines):
def check_bad_dedent(file, lines, options=None):
"""Check for mis-alignment in indentation in code blocks.
|A 5 lines block::
Expand Down Expand Up @@ -634,6 +637,12 @@ def __call__(self, parser, namespace, values, option_string=None):
"Can be used to see which checkers would be used with a given set of "
"--enable and --disable options.",
)
parser.add_argument(
"--max-line-length",
help="Maximum number of characters on a single line.",
default=80,
type=int,
)
parser.add_argument("paths", default=".", nargs="*")
args = parser.parse_args(argv[1:])
try:
Expand Down Expand Up @@ -665,7 +674,21 @@ def walk(path, ignore_list):
yield file if file[:2] != "./" else file[2:]


def check_text(filename, text, checkers):
class CheckersOptions:
"""Configuration options for checkers."""

max_line_length = 80

@classmethod
def from_argparse(cls, namespace):
options = cls()
options.max_line_length = namespace.max_line_length
return options


def check_text(filename, text, checkers, options=None):
if options is None:
options = CheckersOptions()
errors = Counter()
ext = splitext(filename)[1]
checkers = {checker for checker in checkers if ext in checker.suffixes}
Expand All @@ -676,14 +699,14 @@ def check_text(filename, text, checkers):
if ext not in check.suffixes:
continue
for lno, msg in check(
filename, lines_with_rst_only if check.rst_only else lines
filename, lines_with_rst_only if check.rst_only else lines, options
):
print(f"{filename}:{lno}: {msg} ({check.name})")
errors[check.name] += 1
return errors


def check_file(filename, checkers):
def check_file(filename, checkers, options: CheckersOptions = None):
ext = splitext(filename)[1]
if not any(ext in checker.suffixes for checker in checkers):
return Counter()
Expand All @@ -696,11 +719,12 @@ def check_file(filename, checkers):
except UnicodeDecodeError as err:
print(f"{filename}: cannot decode as UTF-8: {err}")
return Counter({4: 1})
return check_text(filename, text, checkers)
return check_text(filename, text, checkers, options)


def main(argv=None):
enabled_checkers, args = parse_args(argv)
options = CheckersOptions.from_argparse(args)
if args.list:
if not enabled_checkers:
print("No checkers selected.")
Expand All @@ -721,7 +745,7 @@ def main(argv=None):
return 2

todo = [
(path, enabled_checkers)
(path, enabled_checkers, options)
for path in chain.from_iterable(walk(path, args.ignore) for path in args.paths)
]

Expand Down

0 comments on commit 4c8d4a6

Please sign in to comment.