diff --git a/.gitignore b/.gitignore index bfa441d1..c1ef3afc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ __pycache__/ parser/snooty.py *.dist/ node_modules/ -.coverage +.coverage* htmlcov/ .venv/ dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9576528a..fda6f6c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add validation for links under the `doc` role (DOCSP-6190). + - Add support for the following reStructuredText constructs: - `datalakeconf` rstobject diff --git a/snooty/parser.py b/snooty/parser.py index 9ce85686..4c60a23a 100644 --- a/snooty/parser.py +++ b/snooty/parser.py @@ -250,6 +250,9 @@ def dispatch_visit(self, node: docutils.nodes.Node) -> None: doc["label"] = node["label"] if "target" in node: doc["target"] = node["target"] + + if doc["name"] == "doc": + self.validate_doc_role(node) elif node_name == "target": doc["type"] = "target" doc["ids"] = node["ids"] @@ -402,7 +405,7 @@ def handle_directive( ): pass else: - msg = f'"{name}" could not open "{argument_text}: No such file exists"' + msg = f'"{name}" could not open "{argument_text}": No such file exists' self.diagnostics.append(Diagnostic.error(msg, util.get_line(node))) if options: @@ -411,6 +414,17 @@ def handle_directive( doc["children"] = [] return True + def validate_doc_role(self, node: docutils.nodes.Node) -> None: + """Validate target for doc role""" + target = PurePath(node["target"]).with_suffix(".txt") + fileid, target_path = util.reroot_path(target, self.docpath, self.source_path) + + if not target_path.is_file(): + msg = ( + f'"{node["name"]}" could not open "{target_path}": No such file exists' + ) + self.diagnostics.append(Diagnostic.error(msg, util.get_line(node))) + def add_static_asset(self, path: Path, upload: bool) -> StaticAsset: fileid, path = util.reroot_path(path, self.docpath, self.source_path) static_asset = StaticAsset.load(fileid, path, upload) diff --git a/snooty/test_parser.py b/snooty/test_parser.py index 2f4a89e7..0b7d1401 100644 --- a/snooty/test_parser.py +++ b/snooty/test_parser.py @@ -393,6 +393,91 @@ def test_roles() -> None: ) +def test_doc_role() -> None: + project_root = ROOT_PATH.joinpath("test_project") + path = project_root.joinpath(Path("source/test.rst")).resolve() + project_config = ProjectConfig(project_root, "") + parser = rstparser.Parser(project_config, JSONVisitor) + + # Test bad text + page, diagnostics = parse_rst( + parser, + path, + """ +* :doc:`Testing it ` +* :doc:`Testing this ` +* :doc:`Testing that <./fake-text>` +* :doc:`fake-text` +* :doc:`/fake-text` +* :doc:`./fake-text` +""", + ) + page.finish(diagnostics) + assert len(diagnostics) == 6 + + # Test valid text + page, diagnostics = parse_rst( + parser, + path, + """ +* :doc:`Testing this ` +* :doc:`Testing that <./../source/index>` +* :doc:`index` +* :doc:`/index` +* :doc:`./../source/index` +* :doc:`/index/` +""", + ) + page.finish(diagnostics) + print(ast_to_testing_string(page.ast)) + assert diagnostics == [] + assert ast_to_testing_string(page.ast) == "".join( + ( + "", + "", + "", + "", + '', + "", + "", + "", + "", + "", + '', + "", + "", + "", + "", + "", + '', + "", + "", + "", + "", + '', + "", + "", + "", + "", + '', + "", + "", + "", + "", + '', + "", + "", + "", + "", + "", + ) + ) + + def test_accidental_indentation() -> None: path = ROOT_PATH.joinpath(Path("test.rst")) project_config = ProjectConfig(ROOT_PATH, "", source="./")