[REF] tests: split checkers
task-2801043
closes odoo/documentation#3794
X-original-commit: 8e8c466892
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
parent
bd9648a519
commit
4573300bb4
2
Makefile
2
Makefile
@ -85,4 +85,4 @@ static: $(HTML_BUILD_DIR)/_static/style.css
|
|||||||
cp -r static/* $(HTML_BUILD_DIR)/_static/
|
cp -r static/* $(HTML_BUILD_DIR)/_static/
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@python tests/rst_style.py $(SOURCE_DIR)/administration $(SOURCE_DIR)/applications $(SOURCE_DIR)/contributing $(SOURCE_DIR)/developer $(SOURCE_DIR)/services redirects
|
@python tests/main.py $(SOURCE_DIR)/administration $(SOURCE_DIR)/applications $(SOURCE_DIR)/contributing $(SOURCE_DIR)/developer $(SOURCE_DIR)/services redirects
|
||||||
|
3
tests/checkers/__init__.py
Normal file
3
tests/checkers/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from . import redirect_rules
|
||||||
|
from . import resource_files
|
||||||
|
from . import rst_style
|
55
tests/checkers/redirect_rules.py
Normal file
55
tests/checkers/redirect_rules.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import sphinxlint
|
||||||
|
|
||||||
|
|
||||||
|
REDIRECT_RULE_RE = re.compile(r'^[ \t]*([\w\-/]+\.rst)[ \t]+([\w\-/]+\.rst)[ \t]*(?:#.*)?$')
|
||||||
|
REDIRECTS_FILE_VERSION_RE = re.compile('(?:redirects/)?(?:saas-)?(\d\d\.\d)\.txt')
|
||||||
|
|
||||||
|
|
||||||
|
@sphinxlint.checker('.txt')
|
||||||
|
def check_redirect_rules_format(file, lines, options=None):
|
||||||
|
""" Check that redirect rules are correctly formatted. """
|
||||||
|
if file.startswith('redirects/'): # Only check text files in the /redirects folder.
|
||||||
|
for lno, line in enumerate(lines):
|
||||||
|
if not line.rstrip() or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
if not REDIRECT_RULE_RE.search(line):
|
||||||
|
yield lno + 1, "invalid redirect rule format; learn more at redirects/MANUAL.md"
|
||||||
|
|
||||||
|
|
||||||
|
@sphinxlint.checker('.txt')
|
||||||
|
def check_redirect_rules_target(file, lines, options=None):
|
||||||
|
""" Check that redirect rules refer to existing files. """
|
||||||
|
def get_redirects_file_version(file_name_):
|
||||||
|
match_ = REDIRECTS_FILE_VERSION_RE.search(file_name_)
|
||||||
|
if match_:
|
||||||
|
return float(match_.group(1))
|
||||||
|
return -1.0
|
||||||
|
|
||||||
|
if file.startswith('redirects/'): # Only check text files in the /redirects folder.
|
||||||
|
# Find the current version, which is that of the file with the latest version.
|
||||||
|
redirects_dir = Path('redirects')
|
||||||
|
latest_redirects_version = 0.0
|
||||||
|
for redirect_file in redirects_dir.iterdir():
|
||||||
|
if redirect_file.is_dir() or redirect_file.suffix != '.txt':
|
||||||
|
continue
|
||||||
|
latest_redirects_version = max(
|
||||||
|
latest_redirects_version, get_redirects_file_version(redirect_file.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only check the existence of the redirection target if we are in the right version.
|
||||||
|
current_file_version = get_redirects_file_version(file)
|
||||||
|
if current_file_version < latest_redirects_version:
|
||||||
|
return
|
||||||
|
|
||||||
|
for lno, line in enumerate(lines):
|
||||||
|
if not line.rstrip() or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
match_res = REDIRECT_RULE_RE.search(line)
|
||||||
|
if match_res:
|
||||||
|
_old_path, new_path = match_res.groups()
|
||||||
|
redirect_file = Path('content') / new_path
|
||||||
|
if not redirect_file.is_file():
|
||||||
|
yield lno + 1, f"the redirect rule targets the non-existing file {new_path}"
|
7
tests/checkers/resource_files.py
Normal file
7
tests/checkers/resource_files.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import sphinxlint
|
||||||
|
|
||||||
|
|
||||||
|
@sphinxlint.checker('')
|
||||||
|
def check_file_extensions(file, lines, options=None):
|
||||||
|
""" Check that there is no file without extension. """
|
||||||
|
yield 0, "the file does not have an extension"
|
@ -1,18 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import sphinxlint
|
import sphinxlint
|
||||||
|
|
||||||
|
|
||||||
CUSTOM_RST_DIRECTIVES = [
|
|
||||||
'card', 'cards', # cards
|
|
||||||
'example', 'exercise', # custom_admonitions
|
|
||||||
'spoiler', # spoilers
|
|
||||||
'tab', 'tabs', 'group-tab', 'code-tab', # sphinx_tabs
|
|
||||||
]
|
|
||||||
|
|
||||||
ALLOWED_HEADING_CHARS = ['=', '-', '~', '*', '^'] # In the same order as in the guidelines.
|
ALLOWED_HEADING_CHARS = ['=', '-', '~', '*', '^'] # In the same order as in the guidelines.
|
||||||
MAIN_HEADING_CHAR = ALLOWED_HEADING_CHARS[0]
|
MAIN_HEADING_CHAR = ALLOWED_HEADING_CHARS[0]
|
||||||
MAIN_HEADING_RE = re.compile(rf'{MAIN_HEADING_CHAR}+\n[^\n]+\n{MAIN_HEADING_CHAR}+\n')
|
MAIN_HEADING_RE = re.compile(rf'{MAIN_HEADING_CHAR}+\n[^\n]+\n{MAIN_HEADING_CHAR}+\n')
|
||||||
@ -26,70 +16,8 @@ FORBIDDEN_HEADING_DELIMITER_RE = re.compile(
|
|||||||
'^(' + '|'.join(rf'\{char}+' for char in FORBIDDEN_HEADING_CHARS) + ')\n$'
|
'^(' + '|'.join(rf'\{char}+' for char in FORBIDDEN_HEADING_CHARS) + ')\n$'
|
||||||
)
|
)
|
||||||
GIT_CONFLICT_MARKERS = ['<' * 7, '>' * 7]
|
GIT_CONFLICT_MARKERS = ['<' * 7, '>' * 7]
|
||||||
REDIRECT_RULE_RE = re.compile(r'^[ \t]*([\w\-/]+\.rst)[ \t]+([\w\-/]+\.rst)[ \t]*(?:#.*)?$')
|
|
||||||
REDIRECTS_FILE_VERSION_RE = re.compile('(?:redirects/)?(?:saas-)?(\d\d\.\d)\.txt')
|
|
||||||
|
|
||||||
|
|
||||||
@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. """
|
|
||||||
for lno, line in enumerate(lines):
|
|
||||||
if any(marker in line for marker in GIT_CONFLICT_MARKERS):
|
|
||||||
yield lno + 1, "the git conflict should be resolved"
|
|
||||||
|
|
||||||
|
|
||||||
@sphinxlint.checker('')
|
|
||||||
def check_file_extensions(file, lines, options=None):
|
|
||||||
""" Check that there is no file without extension. """
|
|
||||||
yield 0, "the file does not have an extension"
|
|
||||||
|
|
||||||
|
|
||||||
@sphinxlint.checker('.txt')
|
|
||||||
def check_redirect_rules_format(file, lines, options=None):
|
|
||||||
""" Check that redirect rules are correctly formatted. """
|
|
||||||
if file.startswith('redirects/'): # Only check text files in the /redirects folder.
|
|
||||||
for lno, line in enumerate(lines):
|
|
||||||
if not line.rstrip() or line.startswith('#'):
|
|
||||||
continue
|
|
||||||
if not REDIRECT_RULE_RE.search(line):
|
|
||||||
yield lno + 1, "invalid redirect rule format; learn more at redirects/MANUAL.md"
|
|
||||||
|
|
||||||
|
|
||||||
@sphinxlint.checker('.txt')
|
|
||||||
def check_redirect_rules_target(file, lines, options=None):
|
|
||||||
""" Check that redirect rules refer to existing files. """
|
|
||||||
def get_redirects_file_version(file_name_):
|
|
||||||
match_ = REDIRECTS_FILE_VERSION_RE.search(file_name_)
|
|
||||||
if match_:
|
|
||||||
return float(match_.group(1))
|
|
||||||
return -1.0
|
|
||||||
|
|
||||||
if file.startswith('redirects/'): # Only check text files in the /redirects folder.
|
|
||||||
# Find the current version, which is that of the file with the latest version.
|
|
||||||
redirects_dir = Path('redirects')
|
|
||||||
latest_redirects_version = 0.0
|
|
||||||
for redirect_file in redirects_dir.iterdir():
|
|
||||||
if redirect_file.is_dir() or redirect_file.suffix != '.txt':
|
|
||||||
continue
|
|
||||||
latest_redirects_version = max(
|
|
||||||
latest_redirects_version, get_redirects_file_version(redirect_file.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Only check the existence of the redirection target if we are in the right version.
|
|
||||||
current_file_version = get_redirects_file_version(file)
|
|
||||||
if current_file_version < latest_redirects_version:
|
|
||||||
return
|
|
||||||
|
|
||||||
for lno, line in enumerate(lines):
|
|
||||||
if not line.rstrip() or line.startswith('#'):
|
|
||||||
continue
|
|
||||||
match_res = REDIRECT_RULE_RE.search(line)
|
|
||||||
if match_res:
|
|
||||||
_old_path, new_path = match_res.groups()
|
|
||||||
redirect_file = Path('content') / new_path
|
|
||||||
if not redirect_file.is_file():
|
|
||||||
yield lno + 1, f"the redirect rule targets the non-existing file {new_path}"
|
|
||||||
|
|
||||||
@sphinxlint.checker('.rst')
|
@sphinxlint.checker('.rst')
|
||||||
def check_heading_delimiters_characters(file, lines, options=None):
|
def check_heading_delimiters_characters(file, lines, options=None):
|
||||||
""" Check that heading delimiters use only allowed characters. """
|
""" Check that heading delimiters use only allowed characters. """
|
||||||
@ -175,58 +103,9 @@ def check_heading_spacing(file, lines, options=None):
|
|||||||
yield heading_lno + 1, "the heading should be followed by a blank line"
|
yield heading_lno + 1, "the heading should be followed by a blank line"
|
||||||
|
|
||||||
|
|
||||||
"""
|
@sphinxlint.checker('.rst', '.py', '.js', '.xml', '.css', '.sass', '.less', '.po', '.pot')
|
||||||
The following checkers are selected.
|
def check_git_conflict_markers(file, lines, options=None):
|
||||||
|
""" Check that there are no conflict markers. """
|
||||||
Base checkers:
|
for lno, line in enumerate(lines):
|
||||||
- backtick-before-role: Search for roles preceded by a backtick.
|
if any(marker in line for marker in GIT_CONFLICT_MARKERS):
|
||||||
- bad-dedent: Check for mis-alignment in indentation in code blocks.
|
yield lno + 1, "the git conflict should be resolved"
|
||||||
- carriage-return: Check for carriage returns (\r) in lines.
|
|
||||||
- directive-missing-colons: Search for directive wrongly typed as comments.
|
|
||||||
- directive-with-three-dots: Search for directives with three dots instead of two.
|
|
||||||
- horizontal-tab: Check for horizontal tabs (\t) in lines.
|
|
||||||
- hyperlink-reference-missing-backtick: Search for missing backticks in front of hyperlink
|
|
||||||
references.
|
|
||||||
- missing-backtick-after-role: Search for roles missing their closing backticks.
|
|
||||||
- missing-colon-in-role: Search for missing colons in roles.
|
|
||||||
- missing-final-newline: Check that the last line of the file ends with a newline.
|
|
||||||
- missing-space-after-literal: Search for inline literals immediately followed by a character.
|
|
||||||
- missing-space-after-role: Search for roles immediately followed by a character.
|
|
||||||
- missing-space-before-default-role: Search for missing spaces before default role.
|
|
||||||
- missing-space-before-role: Search for missing spaces before roles.
|
|
||||||
- missing-space-in-hyperlink: Search for hyperlinks missing a space.
|
|
||||||
- missing-underscore-after-hyperlink: Search for hyperlinks missing underscore after their closing
|
|
||||||
backtick.
|
|
||||||
- python-syntax: Search invalid syntax in Python examples.
|
|
||||||
- role-with-double-backticks: Search for roles with double backticks.
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
Optional checkers:
|
|
||||||
- line-too-long: Check for line length; this checker is not run by default.
|
|
||||||
|
|
||||||
Custom checkers:
|
|
||||||
- all the checkers defined in this file.
|
|
||||||
"""
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Patch sphinxlint's global constants to include our custom directives and parse their content.
|
|
||||||
with patch(
|
|
||||||
'sphinxlint.DIRECTIVES_CONTAINING_RST',
|
|
||||||
sphinxlint.DIRECTIVES_CONTAINING_RST + CUSTOM_RST_DIRECTIVES,
|
|
||||||
), patch(
|
|
||||||
'sphinxlint.DIRECTIVES_CONTAINING_RST_RE',
|
|
||||||
'(' + '|'.join(sphinxlint.DIRECTIVES_CONTAINING_RST) + ')',
|
|
||||||
), patch(
|
|
||||||
'sphinxlint.ALL_DIRECTIVES',
|
|
||||||
'(' + '|'.join(sphinxlint.DIRECTIVES_CONTAINING_RST
|
|
||||||
+ sphinxlint.DIRECTIVES_CONTAINING_ARBITRARY_CONTENT)
|
|
||||||
+ ')',
|
|
||||||
), patch(
|
|
||||||
'sphinxlint.seems_directive_re',
|
|
||||||
re.compile(rf"^\s*(?<!\.)\.\. {sphinxlint.ALL_DIRECTIVES}([^a-z:]|:(?!:))"),
|
|
||||||
), patch(
|
|
||||||
'sphinxlint.three_dot_directive_re',
|
|
||||||
re.compile(rf'\.\.\. {sphinxlint.ALL_DIRECTIVES}::'),
|
|
||||||
):
|
|
||||||
sys.exit(sphinxlint.main())
|
|
71
tests/main.py
Normal file
71
tests/main.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import sphinxlint
|
||||||
|
|
||||||
|
import checkers
|
||||||
|
|
||||||
|
|
||||||
|
CUSTOM_RST_DIRECTIVES = [
|
||||||
|
'card', 'cards', # cards
|
||||||
|
'example', 'exercise', # custom_admonitions
|
||||||
|
'spoiler', # spoilers
|
||||||
|
'tab', 'tabs', 'group-tab', 'code-tab', # sphinx_tabs
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
The following checkers are selected.
|
||||||
|
|
||||||
|
Base checkers:
|
||||||
|
- 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.
|
||||||
|
- directive-missing-colons: Search for directive wrongly typed as comments.
|
||||||
|
- directive-with-three-dots: Search for directives with three dots instead of two.
|
||||||
|
- horizontal-tab: Check for horizontal tabs (\t) in lines.
|
||||||
|
- hyperlink-reference-missing-backtick: Search for missing backticks in front of hyperlink
|
||||||
|
references.
|
||||||
|
- missing-backtick-after-role: Search for roles missing their closing backticks.
|
||||||
|
- missing-colon-in-role: Search for missing colons in roles.
|
||||||
|
- missing-final-newline: Check that the last line of the file ends with a newline.
|
||||||
|
- missing-space-after-literal: Search for inline literals immediately followed by a character.
|
||||||
|
- missing-space-after-role: Search for roles immediately followed by a character.
|
||||||
|
- missing-space-before-default-role: Search for missing spaces before default role.
|
||||||
|
- missing-space-before-role: Search for missing spaces before roles.
|
||||||
|
- missing-space-in-hyperlink: Search for hyperlinks missing a space.
|
||||||
|
- missing-underscore-after-hyperlink: Search for hyperlinks missing underscore after their closing
|
||||||
|
backtick.
|
||||||
|
- python-syntax: Search invalid syntax in Python examples.
|
||||||
|
- role-with-double-backticks: Search for roles with double backticks.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
Optional checkers:
|
||||||
|
- line-too-long: Check for line length; this checker is not run by default.
|
||||||
|
|
||||||
|
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.
|
||||||
|
with patch(
|
||||||
|
'sphinxlint.DIRECTIVES_CONTAINING_RST',
|
||||||
|
sphinxlint.DIRECTIVES_CONTAINING_RST + CUSTOM_RST_DIRECTIVES,
|
||||||
|
), patch(
|
||||||
|
'sphinxlint.DIRECTIVES_CONTAINING_RST_RE',
|
||||||
|
'(' + '|'.join(sphinxlint.DIRECTIVES_CONTAINING_RST) + ')',
|
||||||
|
), patch(
|
||||||
|
'sphinxlint.ALL_DIRECTIVES',
|
||||||
|
'(' + '|'.join(sphinxlint.DIRECTIVES_CONTAINING_RST
|
||||||
|
+ sphinxlint.DIRECTIVES_CONTAINING_ARBITRARY_CONTENT)
|
||||||
|
+ ')',
|
||||||
|
), patch(
|
||||||
|
'sphinxlint.seems_directive_re',
|
||||||
|
re.compile(rf"^\s*(?<!\.)\.\. {sphinxlint.ALL_DIRECTIVES}([^a-z:]|:(?!:))"),
|
||||||
|
), patch(
|
||||||
|
'sphinxlint.three_dot_directive_re',
|
||||||
|
re.compile(rf'\.\.\. {sphinxlint.ALL_DIRECTIVES}::'),
|
||||||
|
):
|
||||||
|
sys.exit(sphinxlint.main())
|
Loading…
Reference in New Issue
Block a user