Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ invalid_objs.json
.tox

output/

# Claude Code per-user local settings
.claude/settings.local.json
38 changes: 38 additions & 0 deletions docs/source/components/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,41 @@ It features help pages. Add ``-h`` or ``--help`` to any command to see the avail
:theme: monokai
:show-nested:
:make-sections:

Treating warnings as errors
===========================

By default, ``codelinks analyse`` exits ``0`` even when it prints warnings. Pass
``-W`` / ``--strict`` to make it exit ``1`` if any non-suppressed warning was
emitted, mirroring ``sphinx-build -W``. This turns broken markers or missing git
metadata into a hard failure — useful as a CI/CD quality gate.

.. code-block:: bash

codelinks analyse codelinks.toml --strict

The exit-code contract is:

.. list-table::
:header-rows: 1
:widths: 60 20 20

* - Situation
- without ``-W``
- with ``-W``
* - Completed, no warnings
- ``0``
- ``0``
* - Completed, ≥1 non-suppressed warning
- ``0``
- ``1``
* - Uncaught exception / crash
- ``1``
- ``1``
* - Usage / configuration error
- ``2``
- ``2``

Because ``-W`` covers *all* warnings, expected ones (for example running outside
a git checkout) can be silenced by listing their slugs in
:ref:`suppress_warnings`. Suppressed warnings never count towards the exit code.
46 changes: 46 additions & 0 deletions docs/source/components/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,52 @@ Specifies the output directory for generated artifacts such as extracted markers
[codelinks]
outdir = "output"

.. _`suppress_warnings`:

suppress_warnings
~~~~~~~~~~~~~~~~~

A list of warning *slugs* to silence. The same list is honoured by both the
standalone ``analyse`` command and the Sphinx extension, so suppression is
configured once.

**Type:** ``list[str]``
**Default:** ``[]``

.. code-block:: toml

[codelinks]
suppress_warnings = ["codelinks.git", "codelinks.marker.too_many_fields"]

Slugs are matched hierarchically, so a parent silences everything beneath it:

- ``codelinks`` — every ``Sphinx-CodeLinks`` warning
- ``codelinks.git`` — the whole git-metadata family
- ``codelinks.git.root`` — just that one warning

The available slugs are:

.. list-table::
:header-rows: 1
:widths: 20 80

* - Family
- Slugs
* - git metadata
- ``codelinks.git.root``, ``codelinks.git.config``, ``codelinks.git.remote``,
``codelinks.git.head``, ``codelinks.git.ref``, ``codelinks.git.host``
* - one-line marker
- ``codelinks.marker.too_many_fields``, ``codelinks.marker.too_few_fields``,
``codelinks.marker.missing_square_brackets``,
``codelinks.marker.not_start_or_end_with_square_brackets``,
``codelinks.marker.newline_in_field``

Suppressed warnings are dropped entirely: they are neither printed nor counted
towards the ``--strict`` exit code (see :ref:`cli`). In Sphinx the same slugs are
folded into the native ``suppress_warnings``, so ``sphinx-build`` and
``sphinx-build -W`` honour them too; any slugs already set in ``conf.py`` are
preserved.

Project-Specific Options
------------------------

Expand Down
23 changes: 23 additions & 0 deletions docs/source/development/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
Changelog
=========

Unreleased
----------

New and Improved
................

- ✨ Added a ``-W`` / ``--strict`` flag to ``codelinks analyse``.

With the flag, ``analyse`` exits ``1`` if any non-suppressed warning was
emitted (mirroring ``sphinx-build -W``), so warnings can gate CI/CD pipelines.
Without it, behaviour is unchanged.

- ✨ Added a shared ``suppress_warnings`` configuration option.

A single ``[codelinks] suppress_warnings`` list of hierarchical slugs
(e.g. ``codelinks.git`` or ``codelinks.marker.too_many_fields``) silences
warnings for both the CLI and the Sphinx extension. In Sphinx the slugs are
folded into the native ``suppress_warnings``.

- 👌 Normalised warning slugs under the ``codelinks.git.*`` and
``codelinks.marker.*`` namespaces (previously ``codelinks.git_*`` and
``need.*``).

.. _`release:1.3.0`:

1.3.0
Expand Down
4 changes: 4 additions & 0 deletions docs/src_trace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ set_local_url = true # Set to true to enable local code html and URL
local_url_field = "local-url" # Need's field name for local URL
set_remote_url = true # Set to true to enable remote url to be generated
remote_url_field = "remote-url" # Need's field name for remote URL
# Silence best-effort git-metadata warnings so the strict (-nW) docs build is not
# tripped when building from a git worktree (packed refs). See the CLI docs for
# the -W / suppress_warnings feature this dogfoods.
suppress_warnings = ["codelinks.git"]

# Configuration for source tracing project "dcdc"
[codelinks.projects.dcdc]
Expand Down
12 changes: 6 additions & 6 deletions src/sphinx_codelinks/analyse/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def locate_git_root(src_dir: Path) -> Path | None:
return parent
logger.warning(
f"git root is not found in the parent of {src_dir}",
subtype="git_root",
subtype="git.root",
location=str(src_dir),
)
return None
Expand All @@ -357,7 +357,7 @@ def get_remote_url(git_root: Path, remote_name: str = "origin") -> str | None:
if not config_path.exists():
logger.warning(
f"{config_path} does not exist",
subtype="git_config",
subtype="git.config",
location=str(config_path),
)
return None
Expand All @@ -370,7 +370,7 @@ def get_remote_url(git_root: Path, remote_name: str = "origin") -> str | None:
return url
logger.warning(
f"remote-url is not found in {config_path}",
subtype="git_remote",
subtype="git.remote",
location=str(config_path),
)
return None
Expand All @@ -382,7 +382,7 @@ def get_current_rev(git_root: Path) -> str | None:
if not head_path.exists():
logger.warning(
f"{head_path} does not exist",
subtype="git_head",
subtype="git.head",
location=str(head_path),
)
return None
Expand All @@ -396,7 +396,7 @@ def get_current_rev(git_root: Path) -> str | None:
if not ref_path.exists():
logger.warning(
f"{ref_path} does not exist",
subtype="git_ref",
subtype="git.ref",
location=str(ref_path),
)
return None
Expand All @@ -411,7 +411,7 @@ def form_https_url(
if not template:
logger.warning(
f"Unsupported Git host: {parsed_url.platform}",
subtype="git_host",
subtype="git.host",
)
return git_url
https_url = template.format(
Expand Down
40 changes: 35 additions & 5 deletions src/sphinx_codelinks/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
CodeLinksProjectConfigType,
generate_project_configs,
)
from sphinx_codelinks.logger import configure_cli, logger
from sphinx_codelinks.logger import (
cli_warning_count,
configure_cli,
get_logger,
set_cli_suppress_warnings,
)
from sphinx_codelinks.needextend_write import MarkedObjType, convert_marked_content
from sphinx_codelinks.source_discover.config import (
CommentType,
Expand Down Expand Up @@ -53,10 +58,20 @@
rich_help_panel="Logging",
),
]
OptStrict: TypeAlias = Annotated[ # noqa: UP040 # has to be TypeAlias
bool,
typer.Option(
...,
"-W",
"--strict",
help="Treat warnings as errors: exit 1 if any non-suppressed warning is emitted",
rich_help_panel="Logging",
),
]


@app.command(no_args_is_help=True)
def analyse( # noqa: PLR0912 # for CLI, so it needs the branches
def analyse( # noqa: PLR0912, PLR0913 # a CLI command: many branches and options
config: Annotated[
Path,
typer.Argument(
Expand Down Expand Up @@ -90,6 +105,7 @@ def analyse( # noqa: PLR0912 # for CLI, so it needs the branches
] = None,
verbose: OptVerbose = False,
quiet: OptQuiet = False,
strict: OptStrict = False,
) -> None:
"""Analyse marked content in source code."""
# @CLI command to analyse source code and extract traceability markers, IMPL_CLI_ANALYZE, impl, [FE_CLI_ANALYZE]
Expand All @@ -103,6 +119,11 @@ def analyse( # noqa: PLR0912 # for CLI, so it needs the branches
except TypeError as e:
raise typer.BadParameter(str(e)) from e

# Apply warning suppression now that the config is known, so both the
# git-metadata warnings (emitted during the run) and the marker warnings
# (emitted below) are dropped and left uncounted at one choke point.
set_cli_suppress_warnings(codelinks_config.suppress_warnings)

errors: deque[str] = deque()
if outdir:
codelinks_config.outdir = outdir
Expand Down Expand Up @@ -161,16 +182,25 @@ def analyse( # noqa: PLR0912 # for CLI, so it needs the branches
analyse_projects = AnalyseProjects(codelinks_config)
analyse_projects.run()

# Output warnings to console for CLI users
# Surface one-line marker warnings through the logging facade so they pass
# the same suppress/count choke point as git-metadata warnings; the slug is
# appended by the backend.
clog = get_logger(__name__)
for src_analyse in analyse_projects.projects_analyse.values():
for warning in src_analyse.oneline_warnings:
logger.warning(
clog.warning(
f"Oneline parser warning in {warning.file_path}:{warning.lineno} "
f"- {warning.sub_type}: {warning.msg}",
f"- {warning.msg}",
subtype=f"marker.{warning.sub_type}",
location=f"{warning.file_path}:{warning.lineno}",
)

analyse_projects.dump_markers()

# Mirror ``sphinx-build -W``: any non-suppressed warning fails the run.
if strict and cli_warning_count() > 0:
raise typer.Exit(code=1)


@app.command(no_args_is_help=True)
def discover( # noqa: PLR0913 # CLI command requires multiple parameters
Expand Down
20 changes: 20 additions & 0 deletions src/sphinx_codelinks/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,26 @@ def get_schema(cls, name: str) -> dict[str, Any] | None: # type: ignore[explici
)
"""The field name for the remote URL in the extracted need."""

suppress_warnings: list[str] = field(
default_factory=list,
metadata={
"rebuild": "env",
"types": (list,),
"schema": {
"type": "array",
"items": {"type": "string"},
},
},
)
"""Warning slugs to silence, e.g.
``["codelinks.git", "codelinks.marker.too_many_fields"]``.

Matched hierarchically: ``codelinks`` silences everything, ``codelinks.git``
the whole git-metadata family, ``codelinks.git.root`` just one. Honoured by
the standalone ``analyse`` command (matching warnings are dropped and not
counted for ``--strict``) and folded into Sphinx's native
``suppress_warnings`` for the extension."""

outdir: Path = field(
default=Path("output"),
metadata={"rebuild": "env", "types": (str), "schema": {"type": "string"}},
Expand Down
Loading
Loading