Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/test_and_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ jobs:
with:
bazel-docs-verify-target: "--lockfile_mode=error //:docs_check"

docs-link-verify:
uses: eclipse-score/cicd-workflows/.github/workflows/docs-verify.yml@c1c90b1a82a1fab0fc202979dde6686b2162d5a8 # v0.0.0
permissions:
pull-requests: write
contents: read
with:
bazel-docs-verify-target: "--lockfile_mode=error //:docs_link_check"

# This is the user configurable part of the workflow
unit-tests:
uses: ./.github/workflows/test.yml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
# Run your link checker and generate log
- name: Run LinkChecker
run: |
bazel run --lockfile_mode=error //:link_check > linkcheck_output.txt
bazel run --lockfile_mode=error //:docs_link_check > linkcheck_output.txt
continue-on-error: true

# Run your Python script to parse the linkcheck log and generate issue body
Expand Down
6 changes: 6 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ alias(
name = "actionlint",
actual = "@score_devcontainer//tools:actionlint",
)

# bazel run //:lint_check
alias(
name = "link_check",
actual = ":docs_link_check",
)
Comment on lines +43 to +46

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this alias just confusing? None of the other docs targets have an alias.

2 changes: 1 addition & 1 deletion docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
docs_env["ACTION"] = "linkcheck"
py_binary(
name = "docs_link_check",
tags = ["cli_help=Verify Links inside Documentation:\nbazel run //:link_check\n (Note: this could take a long time)"],
tags = ["cli_help=Verify Links inside Documentation:\nbazel run //:docs_link_check\n (Note: this could take a long time)"],
srcs = [incremental_src],
data = docs_data,
deps = deps,
Expand Down
2 changes: 1 addition & 1 deletion docs/internals/extensions/extension_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Some events also expect a return value.
For more information, please see the related documentation:
- [Attaching function signature](https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.connect)
- [Build API events](https://www.sphinx-doc.org/en/master/extdev/event_callbacks.html#core-events-overview)
- [sphinx-needs events](https://github.com/useblocks/sphinx-needs/blob/master/docs/contributing.rst#structure-of-the-extensions-logic)
- [sphinx-needs events](https://github.com/useblocks/sphinx-needs/blob/master/AGENTS.md#sphinx-extension-architecture)

## Adding a New Configuration Value

Expand Down
6 changes: 3 additions & 3 deletions docs/internals/extensions/source_code_linker.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ score_source_code_linker/

To see working examples for CodeLinks & TestLinks, take a look at the Docs-As-Code documentation.

[Example CodeLink](https://eclipse-score.github.io/docs-as-code/main/requirements/requirements.html#tool_req__docs_common_attr_id_scheme)
[Example CodeLink](https://eclipse-score.github.io/docs-as-code/main/requirements/requirements.html#tool_req__docs_common_attr_status)
[Example CodeLink](https://eclipse-score.github.io/docs-as-code/main/internals/requirements/requirements.html#tool_req__docs_common_attr_id_scheme)
[Example CodeLink](https://eclipse-score.github.io/docs-as-code/main/internals/requirements/requirements.html#tool_req__docs_common_attr_status)

[Example TestLink](https://eclipse-score.github.io/docs-as-code/main/requirements/requirements.html#tool_req__docs_dd_link_source_code_link)
[Example TestLink](https://eclipse-score.github.io/docs-as-code/main/internals/requirements/requirements.html#tool_req__docs_dd_link_source_code_link)
3 changes: 2 additions & 1 deletion docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

| Target | What it does |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `bazel run //:docs` | Builds documentation (also writes `metrics.json`) |
| `bazel run //:docs` | Builds documentation (also writes `metrics.json`) |
| `bazel run //:docs_check` | Verifies documentation correctness |
| `bazel run //:docs_link_check` | Lists broken links |
| `bazel run //:docs_combo` | Builds combined documentation with all external dependencies included |
| `bazel run //:traceability_gate -- --metrics-json bazel-bin/needs_json/_build/needs/metrics.json --min-req-code 70 --min-req-test 70 --min-req-fully-linked 60 --min-tests-linked 70` | Reads the pre-computed metrics.json and fails if coverage thresholds are not met |
| `bazel run //:live_preview` | Creates a live_preview of the documentation viewable in a local server |
Expand Down
8 changes: 8 additions & 0 deletions src/extensions/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ py_library(
visibility = ["//visibility:public"],
deps = ["@score_docs_as_code//src/helper_lib"],
)

py_library(
name = "broken_link_fix",
srcs = ["@score_docs_as_code//src/extensions:broken_link_fix.py"],
imports = ["."],
visibility = ["//visibility:public"],
deps = ["@score_docs_as_code//src/helper_lib"],
)
100 changes: 100 additions & 0 deletions src/extensions/broken_link_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# *******************************************************************************
# Copyright (c) 2025 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
import re
from collections.abc import Callable
from enum import StrEnum
from typing import Any, cast

from sphinx._cli.util.colour import darkgray
from sphinx.application import Sphinx
from sphinx.builders import linkcheck
from sphinx.builders.linkcheck import CheckExternalLinksBuilder, CheckResult
from sphinx.util import logging

logger = logging.getLogger(__name__)

linkcheckbase = cast(
Callable[[CheckExternalLinksBuilder, CheckResult], None],
linkcheck.CheckExternalLinksBuilder.process_result,
)


class _Status(StrEnum):
BROKEN = "broken"


def process_result(self: CheckExternalLinksBuilder, result: CheckResult) -> None:
"""hook a custom process_result function.

Args:
self: The CheckExternalLinksBuilder instance.
result: The CheckResult instance.

Returns:
None
"""
match result.status:
case _Status.BROKEN:
""" Ignore client errors from sourceforge.io """
if re.match(
r"^https://[^/]+\.sourceforge\.io(/|$)", result.uri
) and "403 Client Error" in str(result.message):
logger.info(darkgray("-ignored- ") + result.uri)
self.write_entry(
"ignored",
result.docname,
self.env.doc2path(result.docname, False),
result.lineno,
f"{result.uri} to {result.message}",
)
return

""" Ignore Anchor errors from .md files """
if re.match(
r"https?://.*?\.md#[A-Za-z0-9_.-]+$", result.uri
) and "Anchor" in str(result.message):
logger.info(darkgray("-ignored- ") + result.uri)
self.write_entry(
"ignored",
result.docname,
self.env.doc2path(result.docname, False),
result.lineno,
f"{result.uri} to {result.message}",
)
return

linkcheckbase(self, result)


def setup(app: Sphinx) -> dict[str, object]:
"""Register the custom linkcheck extension.

The extension patches the default Sphinx linkcheck builder to ignore
known false-positive anchor errors reported for selected GitHub
Markdown URLs.

Args:
app: The Sphinx application instance.

Returns:
Extension metadata required by Sphinx.
"""

# hook a custom process_result function
cast(Any, linkcheck.CheckExternalLinksBuilder).process_result = process_result

return {
"version": "0.0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
1 change: 1 addition & 0 deletions src/extensions/score_sphinx_bundle/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ py_library(
visibility = ["//visibility:public"],
deps = all_requirements + [
"@score_docs_as_code//src/extensions:score_plantuml",
"@score_docs_as_code//src/extensions:broken_link_fix",
"@score_docs_as_code//src/extensions/score_draw_uml_funcs",
"@score_docs_as_code//src/extensions/score_layout",
"@score_docs_as_code//src/extensions/score_metamodel",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/score_sphinx_bundle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"needs_config_writer",
"score_sync_toml",
"score_metrics",
"broken_link_fix",
]


Expand Down
Loading