Skip to content

Commit 4c8d4a6

Browse files
authored
Better line-too-long checker so we can pass on sphinx-doc's doc. (#31)
1 parent cecc01d commit 4c8d4a6

File tree

1 file changed

+62
-38
lines changed

1 file changed

+62
-38
lines changed

sphinxlint.py

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def deco(func):
177177

178178

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

199199

200200
@checker(".rst")
201-
def check_missing_backtick_after_role(file, lines):
201+
def check_missing_backtick_after_role(file, lines, options=None):
202202
"""Search for roles missing their closing backticks.
203203
204204
Bad: :fct:`foo
@@ -214,7 +214,7 @@ def check_missing_backtick_after_role(file, lines):
214214

215215

216216
@checker(".rst")
217-
def check_missing_space_after_literal(file, lines):
217+
def check_missing_space_after_literal(file, lines, options=None):
218218
r"""Search for inline literals immediately followed by a character.
219219
220220
Bad: ``items``s
@@ -259,7 +259,7 @@ def paragraphs(lines):
259259

260260

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

272272

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

284284

285285
@checker(".rst")
286-
def check_directive_missing_colons(file, lines):
286+
def check_directive_missing_colons(file, lines, options=None):
287287
"""Search for directive wrongly typed as comments.
288288
289289
Bad: .. versionchanged 3.6.
@@ -295,7 +295,7 @@ def check_directive_missing_colons(file, lines):
295295

296296

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

314314

315315
@checker(".rst")
316-
def check_role_without_backticks(file, lines):
316+
def check_role_without_backticks(file, lines, options=None):
317317
"""Search roles without backticks.
318318
319319
Bad: :func:pdb.main
@@ -326,7 +326,7 @@ def check_role_without_backticks(file, lines):
326326

327327

328328
@checker(".rst")
329-
def check_backtick_before_role(file, lines):
329+
def check_backtick_before_role(file, lines, options=None):
330330
"""Search for roles preceded by a backtick.
331331
332332
Bad: `:fct:`sum`
@@ -340,7 +340,7 @@ def check_backtick_before_role(file, lines):
340340

341341

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

356356

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

371371

372372
@checker(".rst")
373-
def check_role_with_double_backticks(file, lines):
373+
def check_role_with_double_backticks(file, lines, options=None):
374374
"""Search for roles with double backticks.
375375
376376
Bad: :fct:``sum``
@@ -384,7 +384,7 @@ def check_role_with_double_backticks(file, lines):
384384

385385

386386
@checker(".rst")
387-
def check_missing_space_before_role(file, lines):
387+
def check_missing_space_before_role(file, lines, options=None):
388388
"""Search for missing spaces before roles.
389389
390390
Bad: the:fct:`sum`
@@ -398,7 +398,7 @@ def check_missing_space_before_role(file, lines):
398398

399399

400400
@checker(".rst")
401-
def check_missing_colon_in_role(file, lines):
401+
def check_missing_colon_in_role(file, lines, options=None):
402402
"""Search for missing colons in roles.
403403
404404
Bad: :issue`123`
@@ -410,23 +410,23 @@ def check_missing_colon_in_role(file, lines):
410410

411411

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

419419

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

427427

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

436436

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

443443

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

459462

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

534537

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

551554

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

667676

668-
def check_text(filename, text, checkers):
677+
class CheckersOptions:
678+
"""Configuration options for checkers."""
679+
680+
max_line_length = 80
681+
682+
@classmethod
683+
def from_argparse(cls, namespace):
684+
options = cls()
685+
options.max_line_length = namespace.max_line_length
686+
return options
687+
688+
689+
def check_text(filename, text, checkers, options=None):
690+
if options is None:
691+
options = CheckersOptions()
669692
errors = Counter()
670693
ext = splitext(filename)[1]
671694
checkers = {checker for checker in checkers if ext in checker.suffixes}
@@ -676,14 +699,14 @@ def check_text(filename, text, checkers):
676699
if ext not in check.suffixes:
677700
continue
678701
for lno, msg in check(
679-
filename, lines_with_rst_only if check.rst_only else lines
702+
filename, lines_with_rst_only if check.rst_only else lines, options
680703
):
681704
print(f"{filename}:{lno}: {msg} ({check.name})")
682705
errors[check.name] += 1
683706
return errors
684707

685708

686-
def check_file(filename, checkers):
709+
def check_file(filename, checkers, options: CheckersOptions = None):
687710
ext = splitext(filename)[1]
688711
if not any(ext in checker.suffixes for checker in checkers):
689712
return Counter()
@@ -696,11 +719,12 @@ def check_file(filename, checkers):
696719
except UnicodeDecodeError as err:
697720
print(f"{filename}: cannot decode as UTF-8: {err}")
698721
return Counter({4: 1})
699-
return check_text(filename, text, checkers)
722+
return check_text(filename, text, checkers, options)
700723

701724

702725
def main(argv=None):
703726
enabled_checkers, args = parse_args(argv)
727+
options = CheckersOptions.from_argparse(args)
704728
if args.list:
705729
if not enabled_checkers:
706730
print("No checkers selected.")
@@ -721,7 +745,7 @@ def main(argv=None):
721745
return 2
722746

723747
todo = [
724-
(path, enabled_checkers)
748+
(path, enabled_checkers, options)
725749
for path in chain.from_iterable(walk(path, args.ignore) for path in args.paths)
726750
]
727751

0 commit comments

Comments
 (0)