diff --git a/sphinxlint/checkers.py b/sphinxlint/checkers.py index 791cac6ca..46312ee59 100644 --- a/sphinxlint/checkers.py +++ b/sphinxlint/checkers.py @@ -485,6 +485,39 @@ def check_block(block_lineno, block): yield from errors +_find_leading_spaces = re.compile(r'^\s*').match +# finds lines that start with bullets, numbers, and directives' ".." +_find_list_starters = re.compile(r'^\s*(?:[-+*•‣⁃]|\d+[).]|\(\d+\)|#\.|\.\.)\s+' + r'(?!index)').match + + +@checker(".rst") +def check_excessive_indentation(file, lines, options=None): + """Check for nested blocks indented more than they should. + + |Unnecessarily indented list: + | + | * this will be rendered in a blockquote + | + | .. note: this too + """ + errors = [] + last_ind_level = 0 + for lineno, line in enumerate(hide_non_rst_blocks(lines), start=1): + # print(line, end='') + if not line.strip(): + continue + curr_ind_level = len(_find_leading_spaces(line).group()) + # look for nested lists/directives with excessive indentation + if curr_ind_level > last_ind_level and _find_list_starters(line): + errors.append((lineno, "Excessive indentation in nested section")) + # update the indentation level to ignore "* ", "1. ", ".. ", etc. + if m := _find_list_starters(line): + curr_ind_level = len(m.group()) + last_ind_level = curr_ind_level + yield from errors + + _has_dangling_hyphen = re.compile(r".*[a-z]-$").match diff --git a/tests/fixtures/xfail/excessive-indentation.rst b/tests/fixtures/xfail/excessive-indentation.rst new file mode 100644 index 000000000..aea80d1b9 --- /dev/null +++ b/tests/fixtures/xfail/excessive-indentation.rst @@ -0,0 +1,64 @@ +.. expect: 15: Excessive indentation in nested section (excessive-indentation) +.. expect: 20: Excessive indentation in nested section (excessive-indentation) +.. expect: 26: Excessive indentation in nested section (excessive-indentation) +.. expect: 30: Excessive indentation in nested section (excessive-indentation) +.. expect: 35: Excessive indentation in nested section (excessive-indentation) +.. expect: 42: Excessive indentation in nested section (excessive-indentation) +.. expect: 49: Excessive indentation in nested section (excessive-indentation) +.. expect: 51: Excessive indentation in nested section (excessive-indentation) +.. expect: 53: Excessive indentation in nested section (excessive-indentation) +.. expect: 57: Excessive indentation in nested section (excessive-indentation) + + +The most common mistakes is indenting lists and directives under a paragraph: + + * This list shouldn't be indented + * Otherwise it's rendered inside a blockquote + +Directives also shouldn't be indented under a paragraph: + + .. note:: like this one + + + +* Nested lists should be indented properly + + * the bullet of this list should have been under the N + +* Same goes for directives + + .. note:: this should have been under the S + + +.. note:: + + * the opposite is also true + * lists nested under directives + * should be indented properly + * (but maybe they don't have to?) + +.. note:: + + .. note:: this is also not allowed atm, but maybe it should + + +There are also other types of lists: + +* bullet lists + + - bullet lists with different bullets + + 1. numbered lists + + 2) more numbered lists + + (3) numbered lists with more parentheses + + #. autonumbered lists + + +Each of these should give an error, since they are all indented wrong. + +Numbered lists that start with letters (a. b. c. ...) +and roman numerals (I. II. III. ...) are only supported by Sphinx 7+ +so we just ignore them diff --git a/tests/fixtures/xpass/excessive-indentation.rst b/tests/fixtures/xpass/excessive-indentation.rst new file mode 100644 index 000000000..67beeb976 --- /dev/null +++ b/tests/fixtures/xpass/excessive-indentation.rst @@ -0,0 +1,54 @@ +A paragraph indented under another paragraph will create a blockquote +and that's usually ok: + + This is an intentional blockquote + + +In some cases you also have indented directives like ".. index:" that +don't produce any output, so we ignore those too: + + .. index:: ignore + +This is also because they might appear in a list like: + +(1) + .. index:: first item + + This is the first item of the list + +(2) + .. index:: second item + + This is the second item of the list + + +Properly indented nested lists and directives are also ok: + +* A list item that contains multiple lines or paragraphs + should work fine as long as the indentation level is correct + + In this case everything is ok. + +* The nested list should be indented under the T: + + * like this + * and this + +* The start of the list is where the T is: + + * so this should be indented further + * and this too + + +* We can also have a directive nested inside a list: + + .. note:: like this + + +.. note:: + + Having text properly indented under a directive is ok + +.. note:: + + .. note:: Nesting directives is fine too if they are properly indented