diff --git a/Makefile b/Makefile index a09927090..0ef7cb612 100644 --- a/Makefile +++ b/Makefile @@ -84,5 +84,13 @@ static: $(HTML_BUILD_DIR)/_static/style.css cp -r extensions/odoo_theme/static/* $(HTML_BUILD_DIR)/_static/ cp -r static/* $(HTML_BUILD_DIR)/_static/ +# Called by runbot for the ci/documentation_guideline check. test: @python tests/main.py $(SOURCE_DIR)/administration $(SOURCE_DIR)/applications $(SOURCE_DIR)/contributing $(SOURCE_DIR)/developer $(SOURCE_DIR)/services redirects + +# Similar as `test`, but called only manually by content reviewers to trigger extra checks. +review: + @read -p "Enter content path: " path; read -p "Enter max line length (default: 100): " line_length; \ + if [ -z "$$path" ]; then echo "Error: Path cannot be empty"; exit 1; fi; \ + if [ -z "$$line_length" ]; then line_length=100; fi; \ + python tests/main.py -e line-too-long -e early-line-breaks --max-line-length=$$line_length $(SOURCE_DIR)/$$path diff --git a/tests/checkers/rst_style.py b/tests/checkers/rst_style.py index 79e5b15de..6d2b083af 100644 --- a/tests/checkers/rst_style.py +++ b/tests/checkers/rst_style.py @@ -16,6 +16,7 @@ FORBIDDEN_HEADING_DELIMITER_RE = re.compile( '^(' + '|'.join(rf'\{char}+' for char in FORBIDDEN_HEADING_CHARS) + ')\n$' ) GIT_CONFLICT_MARKERS = ['<' * 7, '>' * 7] +ALLOWED_EARLY_BREAK_RE = re.compile(r'^\s*(\.\. |:\S+:\s+)', re.IGNORECASE) # Contains markup. @sphinxlint.checker('.rst') @@ -103,6 +104,48 @@ def check_heading_spacing(file, lines, options=None): yield heading_lno + 1, "the heading should be followed by a blank line" +@sphinxlint.checker('.rst', enabled=False) +def check_early_line_breaks(file, lines, options=None): + """ Checks that no line breaks early, i.e., before using as much of the max length as possible. + + Note: `make review` only + """ + + def is_valid_line(line_, forbidden_starting_chars_): + """ Allowed to break early - handle tables and bullets """ + return not ALLOWED_EARLY_BREAK_RE.search(line_) \ + and not HEADING_DELIMITER_RE.search(line_) \ + and not line_.startswith('\n') \ + and not line_.lstrip().startswith(forbidden_starting_chars_) \ + and len(line_) <= options.max_line_length + + def get_next_line_first_word(next_line_): + """ Return the first word of the next line """ + if next_line_.startswith(' '): + next_line_dict = { + '*': lambda x: x.split('* ', 1)[0], + '-': lambda x: x.split('- ', 1)[0], + '#.': lambda x: x.split('#. ', 1)[0], + 'default': lambda x: x.split(' ', 1)[0] + } + return next_line_dict.get(next_line_.lstrip()[:2], next_line_dict["default"])( + next_line_.lstrip() + ) + else: + return next_line_.split(' ', 1)[0] + + for lno, line in enumerate(lines): + if lno + 1 < len(lines): + next_line = lines[lno + 1] + if (is_valid_line(line, ('+', '|')) + and is_valid_line(next_line, ('+', '|', '- ', '* ', '#. ')) + ): + current_line_remaining_space = options.max_line_length - len(line) + next_line_first_word = get_next_line_first_word(next_line) + if current_line_remaining_space > len(next_line_first_word): + yield lno + 1, f"consider moving \"{next_line_first_word}\" to line {lno + 1}" + + @sphinxlint.checker('.rst', '.py', '.js', '.xml', '.css', '.sass', '.less', '.po', '.pot') def check_git_conflict_markers(file, lines, options=None): """ Check that there are no conflict markers. """ diff --git a/tests/main.py b/tests/main.py index 30ed0b030..198202252 100644 --- a/tests/main.py +++ b/tests/main.py @@ -23,9 +23,7 @@ def run_additional_checks(argv=None): """ -The following checkers are selected. - -Base checkers: +The following checkers are selected for `make test`. - backtick-before-role: Search for roles preceded by a backtick. - bad-dedent: Check for mis-alignment in indentation in code blocks. - carriage-return: Check for carriage returns (\r) in lines. @@ -49,12 +47,13 @@ Base checkers: - role-without-backticks: Search roles without backticks. - trailing-whitespace: Check for trailing whitespaces at end of lines. - unbalanced-inline-literals-delimiters: Search for unbalanced inline literals delimiters. +--- +- all the checkers defined in checkers/* files. -Optional checkers: -- line-too-long: Check for line length; this checker is not run by default. +The following checkers are only selected for `make review`. +- line-too-long: Check for line length. +- early-line-break: Check for early line breaks. -Custom checkers: -- all the checkers defined in checkers/* files """ if __name__ == '__main__': # Patch sphinxlint's global constants to include our custom directives and parse their content.