|
16 | 16 | '^(' + '|'.join(rf'\{char}+' for char in FORBIDDEN_HEADING_CHARS) + ')\n$' |
17 | 17 | ) |
18 | 18 | GIT_CONFLICT_MARKERS = ['<' * 7, '>' * 7] |
19 | | - |
| 19 | +ALLOWED_EARLY_BREAK_CHARS_RE = re.compile( |
| 20 | + r'^\s*(\.\. |:\S+:\s+)', re.IGNORECASE |
| 21 | +) # Matches lines containing directives ('.. directive::' or ':directive:'), allowed to break early. |
20 | 22 |
|
21 | 23 | @sphinxlint.checker('.rst') |
22 | 24 | def check_heading_delimiters_characters(file, lines, options=None): |
@@ -109,3 +111,45 @@ def check_git_conflict_markers(file, lines, options=None): |
109 | 111 | for lno, line in enumerate(lines): |
110 | 112 | if any(marker in line for marker in GIT_CONFLICT_MARKERS): |
111 | 113 | yield lno + 1, "the git conflict should be resolved" |
| 114 | + |
| 115 | + |
| 116 | +@sphinxlint.checker('.rst', enabled=False, rst_only=True) |
| 117 | +def check_early_line_breaks(file, lines, options=None): |
| 118 | + """ Optional - Checks that all lines dont break early per options.max_line_length param """ |
| 119 | + def is_valid_line(line, type): |
| 120 | + """ Allowed to break early - handle tables and bullets """ |
| 121 | + if type == 1: |
| 122 | + return not ALLOWED_EARLY_BREAK_CHARS_RE.search(line) \ |
| 123 | + and not HEADING_DELIMITER_RE.search(line) \ |
| 124 | + and not line.startswith("\n") \ |
| 125 | + and not line.lstrip().startswith(("+", "|", "- ", "* ", "#. ")) \ |
| 126 | + and len(line) <= options.max_line_length |
| 127 | + if type == 0: |
| 128 | + return not ALLOWED_EARLY_BREAK_CHARS_RE.search(line) \ |
| 129 | + and not HEADING_DELIMITER_RE.search(line) \ |
| 130 | + and not line.startswith("\n") \ |
| 131 | + and not line.lstrip().startswith(("+", "|")) \ |
| 132 | + and len(line) <= options.max_line_length |
| 133 | + def get_next_line_first_word(next_line): |
| 134 | + """ Return the first word of the next line """ |
| 135 | + if next_line.startswith(" "): |
| 136 | + next_line_dict = { |
| 137 | + "*": lambda x: x.split("* ", 1)[0], |
| 138 | + "-": lambda x: x.split("- ", 1)[0], |
| 139 | + "#.": lambda x: x.split("#. ", 1)[0], |
| 140 | + "default": lambda x: x.split(" ", 1)[0] |
| 141 | + } |
| 142 | + return next_line_dict.get( |
| 143 | + next_line.lstrip()[:2], next_line_dict["default"] |
| 144 | + )(next_line.lstrip()) |
| 145 | + else: |
| 146 | + return next_line.split(" ", 1)[0] |
| 147 | + |
| 148 | + for lno, current_line in enumerate(lines): |
| 149 | + if lno + 1 < len(lines): |
| 150 | + next_line = lines[lno + 1] |
| 151 | + if is_valid_line(current_line, 0) and is_valid_line(next_line, 1): |
| 152 | + current_line_remaining_space = options.max_line_length - len(current_line) |
| 153 | + next_line_first_word = get_next_line_first_word(next_line) |
| 154 | + if current_line_remaining_space > len(next_line_first_word): |
| 155 | + yield lno + 1, f"Consider moving \"{next_line_first_word}\" to line {lno + 1}." |
0 commit comments