Changelog¶
Unreleased¶
New and Improved¶
🔧 Sphinx-CodeLinks now lives in the Sphinx-Needs repository, as packages/sphinx-codelinks.
The whole of
useblocks/sphinx-codelinks’ history came with it, rewritten so that every historical commit already places its files under that directory:git logandgit blameread the full history there with no--follow, andpackages/sphinx-codelinks/design/import-commit-map.txtmaps every hash the old repository had to its hash in the new one.Some of the move is mechanical and already true; the rest is a checklist being worked through, and this bullet says which is which.
The repository (done): https://github.com/useblocks/sphinx-needs. Pull requests and branches are opened there, under
packages/sphinx-codelinks/.The issue tracker (in progress): https://github.com/useblocks/sphinx-needs/issues. The open issues are being transferred, keeping their labels; the issue form has a Package dropdown with
sphinx-codelinksamong its options, and issues and pull requests concerning this package get apkg: sphinx-codelinkslabel. Old issue URLs redirect.The documentation (in progress): https://codelinks.useblocks.com stays the address. It is served by GitHub Pages from the old repository until the Read the Docs project and the DNS move are done, and by Read the Docs afterwards; nothing changes for a reader of that URL.
The old repository will be archived rather than deleted, so every permalink and every
git+https://…/sphinx-codelinks.git@<sha>pin keeps resolving. Repositories pinning@mainwill stop receiving updates and should re-point at PyPI or atgit+https://github.com/useblocks/sphinx-needs.git@…#subdirectory=packages/sphinx-codelinks.Release tags are prefixed:
sphinx-codelinks-v1.4.0rather than1.4.0. The bare namespace in that repository is Sphinx-Needs’ own, and three of this project’s seven released version numbers name existing Sphinx-Needs releases.An open pull request can be moved across without losing its commits or their authorship: clone the old repository, fetch your branch, run the same
git filter-repo --to-subdirectory-filter packages/sphinx-codelinksthe import ran, and cherry-pick the range ontomasterin the monorepo. The import pull request’s description carries the exact recipe.
✨
@need-ids:references are attached during the build to the needs they name, under each project’sref_url_field(defaultcode_url, ubCode’s key), as a list of links; a reference to an unknown need warnscodelinks.need_id_ref;codelinks write rstis deprecated (#2041).The
src-tracedirective analysed every@need-ids:marker in its files and threw the result away: reaching the needs tookcodelinks analyse,codelinks write rstand anincludeof the generatedneedextendfile. The references are now kept with the document hosting the directive and attached once every need is known, wherever it is defined. Each referenced need gets one entry per reference, in source order – once, even when two directives or two projects scan the same file (files under different roots are different files, each kept): the project’sremote_url_patternfilled in for the marker’s line, or the local link when remote URLs are off. With local URLs only, a file referenced by@need-ids:is copied into the output and gets a source page, as a file with a one-line need is.needs.jsondeclares the field as a list; a need nothing references carriesnull, which a strictunevaluatedProperties: falseschema never sees. The references replace a value the need’s own directive or a default gave the field; a user’sneedextendof the field wins. The unknown-id warning points at the source line (src/refs.cpp:5), and each project reportsN references attached, M unknown. The attach is on when local or remote URLs are, as in ubCode;ref_url_field = ""switches it off for a project. A changed source file updatesneeds.jsonand the referenced needs’ cards on the next build. A comment that starts with a configured@need-ids:marker is a reference and never a one-line need, as in ubCode: on the default one-line style, whose start sequence@matched it too,// @need-ids: A, Bused to become a need with the idB(or stop the build withduplicate_id).ref_url_fieldin[codelinks.projects.*]is accepted, where a sharedubproject.tomlthat set it for ubCode stopped the build withAdditional properties are not allowed ('ref_url_field' was unexpected).codelinks write rststill works and prints a deprecation notice on stderr; it will be removed in 2.0.0. Its-rdefault staysremote_url– not the extension’sremote-url– since changing what an existing invocation writes is not worth it for a command that is going away. A project that keeps including the generated file gets both the attached field and theneedextend’d one: remove the include, and anyneeds_fieldsdeclaration of the field made for that route.✨ Multi-line needs: a need written across the lines of a comment, from an
@need[<markup>] <type>: <title>line with:key: valueoptions to an@endneedline, its body in reStructuredText or Markdown (#2152, #1885, #1898).get_multiline_needs = trueswitches them on for a project, and[analyse.multiline_needs]sets the two words, the default markup and the markups table. A block lives in one comment, docstring or run of consecutive line comments, in every supported language (C/C++, C#, Rust, Go, JSONC, Python, YAML, Bash); comment prefixes are stripped by the comment’s kind, the doxygen*leader only when every line carries one, so*emphasis*in a body is kept. A block’s lines are never read as one-line needs or@need-ids:references.codelinks analysewrites each need tomarked_content.jsonas a"type": "multiline-need"record – the need with its option values as written, itsdoctype, and a source with a root-relative path and the open, close and body lines – and prints a malformed block as an analyse warning at its source line. Thesrc-tracedirective renders them (the next bullet). A need type and an option key are ASCII letters, digits,_and-:@need réq:is refused and:prïority: 3starts the body, as in ubCode. Amarkupsvalue must be a non-empty suffix.Removed: the
@rst…@endrstblocks,get_rstand[analyse.marked_rst]. Nothing ever rendered those blocks (#1885), so no project loses output; a configuration still naming either key is refused with a message naming its replacement – exceptget_rst = falsewith nothing else removed, which asks for nothing: it is ignored, with one warning per project, naming it, saying it can be removed (configuration scaffolds wrote it intoubproject.tomlfiles ubCode reads too). The"type": "rst"entries ofmarked_content.jsonare gone,codelinks write rstignores the new records, and the CLI prints every analyse warning asAnalyse warning in <file>:<line> - <kind>: <message>(it saidOneline parser warning).SourceAnalyse.oneline_warningsis nowSourceAnalyse.warnings, which holds the multi-line kinds too; the old name stays as a read-only alias for one release. The row fix for marked-rst blocks below (#1982) landed days before this change replaced them; multi-line needs keep its rule, a block reported at the row of its open line.✨ The
src-tracedirective renders multi-line needs, each body parsed in the markup it declares (#1885).get_rstblocks were extracted and never rendered; their replacement was, until now, extracted and dropped the same way. Every multi-line need of a directive’s files is now a need, created with the one-line needs in one order – a block by its open line – and by their rules: one need per id (the earlier line keeps it, whatever the kinds), thedocnameandlinenoof the directive, local and remote URLs and the source page’s[docs]link at the open line. The body is parsed in its declared markup whatever the page’s, so a Markdown body renders its roles and lists in a reStructuredText page and the reverse; a docutils or Sphinx warning about it names the source file and line (except in a body rendered through a template or withjinja_content, anchored at the need, and for myst-parser’s own warnings, which name the page). A block’s options are a need directive’s, converted as.. req::converts them; any other key is ignored with a warning at its line. A block rendered through a template, or in a project whosetemplatefield has a default or a predicate, is parsed as the page’s markup, and a markup no parser claims falls back to the page’s with one warning per directive, asneedimportdoes. Every warning about a block’s own content or options, the analyser’s included, iscodelinks.multiline_need; a skipped duplicate sharescodelinks.duplicate_needwith one-line needs. What a body should not contain is listed on the analyse page. Rendering uses Sphinx-Needs’ingest_need_record, new in 9.0.0: with an older Sphinx-Needs each directive with multi-line needs warns once and creates its one-line needs only.🐛 A source file added to a
src-tracedirective’s scope is seen by the next incremental build, with no-E(#2040).Sphinx re-read the document hosting the directive when a file it had analysed was edited or removed, but a file added to its
:directory:(or to the project, for a directive with neither option) was a dependency of nothing: its one-line needs and references appeared only once the document changed. Each directive now records its scope, and every build walks each recorded scope again – one directory walk per scope, no parsing – and re-reads the documents whose files changed. A:file:scope is that one file, so a new file beside it costs nothing. The build’s output and doctree directories are never traced, nor is any builder’s output elsewhere (the.ignorebullet below). The walk costs roughly 0.1 s per 2,000 discovered files on an Apple M2 Pro laptop, whatever their size – the directive’s own discovery plus astatper file – while parsing them costs tens of times more (2,000 200-line C++ files: ~0.1 s of walk against ~9 s of analysis).✨ A project that no
src-tracedirective traces has its@need-ids:references attached anyway (ubCode’s config-only mode), behind the same gate as a directive’s. Its whole source directory is analysed, in the main process and so under-j Ntoo, and no need is created from it – its line counts the one-line needs it did not create. Every build walks the directory and analyses it again only when its files or the configuration changed (and keeps the result, writing the root document if it must); a failing scan warnscodelinks.need_id_refand the build goes on.🐛 A need’s card is rewritten when its code references change, whichever document it is in: a source-only edit used to update
needs.jsonbut leave the card in a document that was not read again showing the old references. Such a document is now written again (not read again). Aneedtablein a third document that filters on the field is still rewritten only when that document is.🐛 A source file reached through a symbolic link inside the traced directory is analysed once, not once per path to it (its one-line needs used to abort the build with
duplicate_id): it is listed under its resolved path, though two spellings of one file on a case-insensitive file system are still two entries.🐛 Two
src-tracedirectives whose scopes overlap no longer abort the build (duplicate_id): the first directive in document order defines a one-line need found by both, the other skips it with acodelinks.duplicate_needwarning naming both documents, and takes it over when the first no longer traces it (in a serial build; with-j Nthe other document takes it over only when it is next read). The id is the marker’s or, for a one-line style withoutid, the one Sphinx-Needs generates. A directive moved to another document no longer aborts the next build either. Overlapping directives still analyse the shared files once each (#2042).🐛 A
git_rootthat does not containsrc_dir, and a source file that resolves to outsidesrc_dir, are diagnosed instead of producing../reference paths or a traceback (#2062).A configured
git_rootthat does not exist, cannot be read, or is neithersrc_dirnor a directory above it, is ignored with onecodelinks.git_rootwarning, and the repository root is detected fromsrc_diras when none is set: the remote URLs, the@need-ids:records and the attach all follow the detected root. It used to give records whosepathbegan with../(with local URLs), or aValueErrortraceback (with remote URLs).codelinks analyseapplies the same rule, warning on stderr, where such agit_rootwith a remote used to end it with aValueError.A file that discovery reaches through a symbolic link but whose target lies outside
src_dir– a file link, or a file below a followed directory link – is skipped with onecodelinks.outside_src_dirwarning naming the link and its target, in asrc-tracedirective, a:file:scope, the scan of a project no directive traces,codelinks discoverandcodelinks analysealike (the latter’smarked_content.jsonno longer holds the file’s markers). This changes behaviour: such a file used to be listed by discovery and analysed, and a build aborted withValueError: … is not in the subpath of …as soon as it held a one-line need (URL fields on or off) or, with a URL field on, an@need-ids:reference; it is now never traced. Widensrc_dirto cover the target, or exclude the link.🐛 A
src-tracedirective whose:file:names no file – a missing path, or a directory – warns once at the directive (codelinks.missing_file: “src-trace: <target> is not a file below <src_dir>”) and the build goes on, where it used to abort (FileNotFoundError, orIsADirectoryErrorfor a directory) – in a fresh build, or when the file was removed since the last; re-created, the file is traced again by the next build. ubCode errors the directive instead (“src-trace :file: … not found”); codelinks warns for every recoverable configuration problem (#2069).🐛 Malformed one-line markers are now reported in the build, at the source line, as
codelinks.onelinewarnings; a build with-Wthat has one fails until the marker is fixed orsuppress_warnings = ["codelinks.oneline"]is set. On a one-character start sequence such as the default@, a line without the field separator is not a marker (ubCode’s rule), so documentation tags like@paramand@briefdo not warn; an empty marker is not a marker, as in ubCode; a marker Sphinx-Needs refuses (an idneeds_id_regexrejects, say) warns too, instead of stopping the build; and the messages are ubCode’s (1 given fields, minimum is 2). The never-written warnings file undersrc_trace_cacheand its reader are gone, a file nothing had written since at least 1.4.0 (Invalid markers, #2076).🐛 A one-line need whose style has no
idfield no longer aborts the build withKeyError: 'id'when local URLs are on; its source page links back to the generated id, and the page’s back-link is a POSIX path on Windows too (#2082).🐛 The source copies and pages are build state: kept up to date by every HTML build whether or not the directive’s document is read again, under
-j Ntoo, and by HTML builders only (#2070, and the missing pages of #2044).With local URLs, each source file a need is created from or a reference names is copied into the output (
<outdir>/<src_dir name>/<path>) and paged beside the copy – the target of every local link. Both were side effects of the analysis, made only by a build that read the directive’s document, from a registry that a-j Nworker never handed back: a removed output directory, a second builder sharing the doctrees (htmlthendirhtml) or a parallel build left dead local links. What to copy and page is now kept in the environment, with the document (or the scanned project) it came from, and every HTML build writes those its output lacks or holds out of date; each page’s[docs]link is the builder’s own relative URI, so adirhtmlpage links back correctly too. An unchanged build rewrites no copy or page; a page is written again when its source changed, a document tracing it – now or before – was read again, added or removed, or the output lacks it; a copy carries its source’s modification time, so a source replaced by an older file is copied again too. A LaTeX build no longer drops source copies into its output; a source removed before the pages are written, or a second source copied to the same place (two projects whose source directories share a name), warnscodelinks.source_page. The first build after upgrading reads every document once: an environment from an earlier release holds no page records.🐛 On Sphinx 7.4 with
show_warning_types = Truethe[codelinks.*]type of a warning is shown once, not twice (#2091).Before Sphinx 8, Sphinx-CodeLinks appends the type to the warnings of its analysis itself, as Sphinx 8 does by default; it also did so when
show_warning_typeshad Sphinx 7.4 append it, so the line ended in[codelinks.outside_src_dir] [codelinks.outside_src_dir]. It is now appended only where Sphinx does not append it.🔧 Every builder writes an
.ignorefile (*) at the root of its output and doctree directories, so withgitignore = truenothing any builder writes is traced – the extension’s source copies, Sphinx’s_downloads/copies of a traced source, another builder’s whole tree – wherever the output sits (#2071): two output trees insidesrc_dirbut outside the documentation source directory (sphinx-build docs build/htmlbesidebuild/dirhtml) no longer copy each other’s copies, one level deeper per build. The directory containing the output and doctree directories is now skipped only forgitignore = falseprojects, which read no ignore file – so with the default, an output directory placed directly inside a traced source directory no longer hides the sources beside it.⬆️
typeris no longer capped below 0.26.8. The cap protected the documentation build, whosesphinxcontrib-typerimported atyper.rich_utilsname that 0.26.8 removed; thedocsextra now requiressphinxcontrib-typer0.9.1 or newer, which tracks the new typer and declares its own floor on it.⬆️
clickis no longer a dependency. The line existed only to cap it below 8.2, working around click 8.2.0 printing an empty error whencodelinksran with no arguments – an incompatibility in the typer of the time. This package never imported click itself, and the typer it requires no longer depends on click at all, so the cap was pinning a package nothing used; dropping it removes click from the environment.⬆️ docutils 0.21 or newer is now required. The line was a bare
docutilsthat left the floor to Sphinx, which accepts 0.20; it is nowdocutils>=0.21, the floor the whole Sphinx-Needs workspace declares and type-checks against. There is no upper bound: Sphinx caps docutils per series itself.🐛 A marked-rst block with text before its start marker on the same line was reported one row too low (#1982).
The row of
@rstwas taken as the number of lines before it, andsplitlines()counts a partial line too – so a one-line block such as// @rst … @endrst, or an@rstbehind a doxygen*prefix, landed on the row below. The row is now the number of newlines before the marker. The source maps inmarked_content.jsonand the blob links built from them move up by one row for such blocks; a block whose@rststarts its own line is unaffected.🐛
src_trace_projectsdeclared directly inconf.pyare normalised beforesrc-tracereads them.Projects configured without
ubproject.tomlnow get the same typedsource_discover_configandanalyse_configobjects as TOML-backed projects, so asrc-tracedirective no longer fails withKeyError: 'source_discover_config'.🐛
sphinx_codelinks.__version__reported"0.1.0".It had said so since the first commit, through seven releases, while the distribution metadata said otherwise – so
import sphinx_codelinks; sphinx_codelinks.__version__on an installed 1.4.0 returned the wrong string. It is exported in__all__, so this is a public-API fix rather than a cosmetic one. From now on the two move together: the workspace’sbumpcommand writes both, and itscheck_workspacefence fails when they disagree.🐛
locate_git_rootfound no git root in a linked git worktree, where.gitis a file rather than a directory (#106).locate_git_rootrequired.gitto be a directory, andget_remote_urlandget_current_revthen read<root>/.git/configand<root>/.git/HEADdirectly. In a linked worktree.gitis a file holdinggitdir: <path>, the remotes live in the main repository’s git directory named bycommondir, and only per-worktree state is local – so everyremote-urland every source link was missing, and a documentation build with-nWfailed outright. Both shapes are now handled, with a regression test that builds a real worktree.🐛 The
local-urlandremote-urllinks render undersphinx-build -j N, and each project’sremote-urllinks with that project’sremote_url_pattern(#2039).The
src-tracedirective wrote the two fields’needs_string_linksentries into the configuration while it was read. A parallel worker never hands such a write back, so under-j Nboth fields rendered as plain text; every rebuild reportedThe configuration has changed (… 'needs_string_links' …); and with two projects whoseremote_url_patterndiffered, the project read last decided every need’s link. The entries are now registered once, atconfig-inited.remote-urlnow holds the URL it is named for. Inneeds.jsonits value is the project’sremote_url_patternfilled in for the marker (https://github.com/org/repo/blob/<commit>/src/a.cpp#L3), where it used to be the fragmentsrc/a.cpp#L3. The card links to the same URL; its name is now the part of the URL after the commit (src/a.cpp#L3for GitHub and GitLab patterns,src/a.cpp#lines-3for Bitbucket), or the whole URL when the pattern has no commit in its path. A value that is not a URL renders as text – including apath#Llinevalue from aneeds.jsonbuilt by an earlier release, which used to be linked through the pattern.local-urlis unchanged. Aneedextendthat setsremote-urlto full URLs, ascodelinks write rst -r remote-urlgenerates, now links to each URL as given instead of to the URL appended to itself.A
remote_url_patterncontaining,or;(gitweb’s?p=repo.git;a=blob;f={path}, for one) is not supported for rendering: Sphinx-Needs splits a string-linked value on those characters, so its link renders as several broken ones. The build warns about such a pattern (codelinks.remote_url_pattern;suppress_warningssilences it, which a-Wbuild needs).🐛 A project outside a git repository, or in one without a commit yet, gets no remote URL (#2045).
Its
remote_url_patternwas filled withNonefor the commit and the build machine’s absolute path for{path}(…/blob/None//home/me/project/src/a.cpp#L1), and that reachedneeds.jsonas a URL. Now a created need’sremote-urlstays unset, and an@need-ids:reference falls back to its local link (or to nothing, without local URLs) – as in ubCode. Thecodelinks.git_rootwarning is unchanged.🔧
libclangis now genuinely optional for the test suite.It has always been an optional extra at runtime, and three test modules guarded it with
pytest.importorskip; a fourth importedsphinx_codelinks.analyse.preprocwithout a guard, and that package imports the libclang loader eagerly, so an environment without the wheel failed during collection rather than skipping. It now degrades honestly: the 56 test cases that need the engine stop running, and the other 303 run – the summary reads303 passed, 26 skipped, because a module-levelimportorskipis one skip per module and never collects the tests inside it.🔧 Lint and type checking now use the Sphinx-Needs workspace’s configuration.
The
[tool.ruff]tables are the workspace root’s, and the one per-file-ignore entry this package still needs went to the root with them, scoped topackages/sphinx-codelinks/tests/*(E402,SIM300– 2 findings if it were dropped). It is scoped rather than**/tests/*because at the root that glob would silently loosen the other packages’ suites too. Linting and formatting are nowuv run poe lint, which runs the whole workspace’s hook set. Type checking moves from mypy to ty –uv run poe typecheck– themypydependency group becomes atypingone that pins the oldest supported Sphinx and docutils, and the 61# type: ignorecomments become 17# ty: ignoreones. The deadpydantic.mypyplugin – nothing in the package imports pydantic – and the unusedpytest-docker,motoandpsutiltest dependencies go with it. No behaviour changed; the whole diff is import order, suppressions and configuration.‼️ Sphinx-Needs 8.5 or newer is now required (previously 5.0 or newer).
Sphinx-CodeLinks is being imported into the Sphinx-Needs repository as a package of its uv workspace, where there is exactly one Sphinx-Needs and the manifest is required to track it tightly —
sphinx-needs>=8.5.0,<9— so a wheel can never claim compatibility with a release it was not tested against. The per-Sphinx-Needs test factor is gone with it, and the two compatibility shims the old floor needed have been deleted: theadd_fieldimport fallback for Sphinx-Needs < 8, and theadd_extra_optionsignature probe that chose between a schema-aware and a schema-less registration. Both already took the modern branch on Sphinx-Needs 8.5, so behaviour there is unchanged.✨ Python 3.11 is now supported; the floor moves down from 3.12.
The full test suite passes on 3.11 unchanged, and the floor now equals the one the Sphinx-Needs workspace declares. The test matrix runs
py{311,312,313,314}againstsphinx{7,8,9}, with one corner left out:py311-sphinx9is deliberately not a valid environment. Not because Sphinx 9 needs Python 3.12 – Sphinx 9.0.x declaresrequires-python >=3.11and is exactly what a Python 3.11 user resolves by default, since 9.1 excludes itself for them – but to align with the Sphinx-Needs workspace this package is being imported into, whosesphinx-9dependency group issphinx~=9.1; python_version >= '3.12'and is therefore empty on 3.11. No package in that workspace is tested on 3.11 against Sphinx 9, and this matrix is replaced by that one at import, so the combination is left unexercised here too, deliberately.📚 The documentation sources moved up beside
conf.py, so Read the Docs can build them.docs/source/*is nowdocs/*, and the docs build no longer passes-c:sphinx-build -nW --keep-going -b html docs docs/_build/htmlis whatuv run poe docs-codelinksruns and what Read the Docs runs by itself. A.readthedocs.yamlcomes with it, and thedocsrequirements move from a dependency group to adocsextra, which is the only form Read the Docs can install. The rendered site is unchanged; only the “edit this page” links point at the new paths.✨ The default configuration file is now
ubproject.toml.src_trace_config_from_toml now defaults to
"ubproject.toml"and the documentation recommends this file name throughout.ubproject.tomlis the shared ubCode project file, which other useblocks tools — e.g. Sphinx-Needs vianeeds_from_tomlor the ubCode checker in VS Code — read as well. Keeping the[codelinks]configuration in this single file makes all tools aware of the configured codelinks projects, which previously required a separate file per tool (e.g.src_trace.toml) and left tools like the ubCode checker reportingUnknown codelinks project(ubcode-pub#75).A default file that does not exist or contains no
[codelinks]table is silently ignored, so existing projects withoutubproject.tomlkeep building without new warnings. The default is the valueubproject.tomlexactly – left unset, or written inconf.pyas that string – so a 1.4.0 project that wrotesrc_trace_config_from_toml = "ubproject.toml"no longer gets a warning for a missing file or a missing table. Any other value,./ubproject.tomlincluded, is an explicit file and warns when missing or without the table. A file that exists but cannot be read or parsed – invalid TOML, not UTF-8, a directory, acodelinkskey that is not a table – warns (codelinks.config) either way, as any configured file did at 1.4.0. The documentation project itself now stores its codelinks configuration inubproject.toml.🐛 A value given on the command line with
-Dnow overrides the TOML file.sphinx-build -D src_trace_set_local_url=0was silently overwritten by aset_local_urlin the[codelinks]table. The order is now-D> TOML >conf.py> default, as in Sphinx-Needs. Only the fullsrc_trace_<key>name counts: a bare-D set_local_url=0, which Sphinx rejects as an unknown setting, leaves the TOML value alone.src_trace_projectsalways comes fromconf.pyor the TOML: Sphinx refuses to override a dictionary setting with-D, and the dotted-D src_trace_projects.<name>=...form is not supported. Sphinx refuses-D src_trace_outdirtoo, whose default is a path, and the TOML value then stands. A-Dvalue Sphinx cannot convert (-D src_trace_set_local_url=yeson Sphinx 8.2 or newer) is now reported by Sphinx instead of being silently replaced by the TOML, as it already was without a TOML.👌
ubproject.tomlis read through ub-project, the shared reader of the Sphinx-Needs family, which is now a dependency (ub-project>=1.1.0,<2).Both the Sphinx extension and
codelinks analyseparse the file and select the[codelinks]table through it, and relative paths are anchored through itsanchor, at the same directories as before. What changes is what a broken file says: every message names the file and the problem (invalid TOML,not valid UTF-8,[codelinks] must be a table, got str), an explicitly configured file without a[codelinks]table says so instead of printing'codelinks', andcodelinks analyseshows why a file could not be loaded rather than only that it could not – acodelinkskey that is0,falseor[]is now reported as not a table instead of as a missing section. The extension’s warnings about its configuration file carry the typecodelinks.config, sosuppress_warnings = ["codelinks.config"]silences them.🐛 A one-line need or
@need-idsreference inside a C++ class or struct body, with no function after it, is associated with that class or struct.The C/C++ scope table named
class_definition, a node kind tree-sitter-cpp never emits, so such a marker had notagged_scope; it now namesclass_specifierandstruct_specifier, the kinds the grammar does emit.🐛 Marker positions are physical (changed output): a one-line need’s or
@need-idsreference’ssource_mapcolumns inmarked_content.json, and a reference’sNeedIdRefspan, count the characters before it on its line.They counted from the start of the comment, so an indented marker, a comment after code, or a comment after a non-ASCII character reported a column that was not its column in the file, to any tool that reads those columns (an editor jumping to a marker, say). In a build, whose links are line-based, only the order in which unknown ids on one line are reported changes. A reference’s span also started at the whitespace after
@need-ids:rather than at its first id, and ended that many characters early. A comment’s rows are split on line breaks only: a form feed no longer starts a row.A block comment’s closing
*/is not marker text:/* @need-ids: REQ_1 */no longer names*/as a need id, and/* @T, IMPL_1, impl */no longer has the typeimpl */. A one-line need on a comment’s last line – the row of a block comment’s*/or of a docstring’s closing quotes – is no longer dropped, and a@need-ids:with no id after it is no longer a reference without ids.🐛 A Python docstring is read as its content, without its prefix letters and quotes: a marker on a one-row docstring,
"""@T, IMPL_1, impl""", no longer has the typeimpl"""(#2154).A field-shaped tag line in a docstring is no longer a need: with the default
@start sequence,@param a: the first, thingbecame a need with the idthing. A line of a docstring – any bare string statement directly in a module, class or function body – with nothing alphanumeric before its start sequence, which is directly followed by a docstring tag (@param,@return,@raisesand the other Epydoc, Doxygen and Sphinx field names) in the Epydoc field shape:|[ \t]+[^ \t:]+:(:directly, or spaces or tabs, one word without space, tab or:, and:), is now adocstring_tagwarning that says to choose a start sequence the docstrings do not use.#comments are not affected, nor is a tag word that opens an ordinary marker title,@todo fix the parser, IMPL_1, impl. Whether anything alphanumeric precedes the start sequence is Python’s own notion of alphanumeric, which ubCode’s differs from on a few characters (combining marks, for one): a documented non-goal.🐛 tree-sitter reads the whole source file, so non-ASCII text no longer breaks its parse (#2155).
The analysis fed tree-sitter one byte per read, and a multi-byte UTF-8 character inside a token the grammar reads – a YAML scalar, an identifier, a Rust, Go or C# character literal – was lexed as invalid. A non-ASCII character in a YAML value lost every comment after it in the file – the one-line needs,
@need-ids:references and multi-line needs they held (key: "é" # @Title, ID_1, implfound nothing). A bash function name or a YAML key with a non-ASCII character lost the scope of the marker above it, and a Go rune literal with a non-ASCII character lost the scope of a marker after it. Non-ASCII identifiers in C, C++, Python, C#, Rust and Go parsed into an error tree as well, with unchanged output in every case measured.🐛 The marked content of one row is listed in source order, by column (changed output), so
marked_content.jsonis the same on every run (#2150).It was sorted by file and row only: two entries on one row – two
@need-ids:comments, two one-line needs – came out in the order tree-sitter handed the comments over, which differs between runs, and a need and a reference on one row always listed the reference first. Of two one-line needs with one id on one row of one file,src-tracenow creates the leftmost and skips the other with acodelinks.duplicate_needwarning; which of the two it created differed between builds. The order across files is unchanged: on one row, the needs of two files are created in the order the files are discovered.
1.4.0¶
- Released:
30.07.2026
New and Improved¶
✨ Added Bash language support for the
analysemodule.Comments in shell scripts are now parsed for need ID references and one-line need definitions.
.sh,.bash,.zsh, and.kshfiles are discovered whencomment_type = "bash". The supported comment style is#. Fish shell is not supported (notree-sitter-fishgrammar is published for the Python package).✨ Added an opt-in preprocessor-aware C/C++ extraction engine, powered by libclang.
The default tree-sitter engine sees every comment in a file, regardless of conditional compilation. Configuring the new analyse.preprocessor table switches C/C++ extraction to libclang, which evaluates the preprocessor and emits only the markers in active branches — a need behind an inactive
#if/#elseis dropped. Compiler flags are resolved per file from acompile_commands.jsoncompilation database, with an explicitdefineslist for headers, which are not listed in such a database. Active markers keep their original line numbers.The engine requires the optional
libclangdependency (pip install 'sphinx-codelinks[libclang]'). Plain tree-sitter C/C++ extraction is unaffected when it is not installed. See Preprocessor-Aware C/C++ Extraction (libclang) for details.📚 Traced the preprocessor-aware C/C++ engine in the feature documentation.
features.rstnow declares the engine as a feature with its fault children, and the implementation carries the one-line markers that link back to it. The engine is therefore covered by the project’s own traceability report, like every supported language.🧪 Added a declarative fixture and snapshot test layer for marker extraction.
Extraction cases are now data (a YAML fixture plus a captured JSON snapshot) instead of bespoke test functions, covering the one-line, need-ID-reference and
@rstblock surfaces across all supported languages.
Fixes¶
🐛 Anchor newline-terminated one-line markers to the start of the comment.
A start sequence (default
@) that appeared in free-form prose was matched anywhere in a comment, so a line such as// See @author, check the examplewas parsed as a need definition and the bogus ID raisedInvalidNeedExceptionin Sphinx-Needs. Markers that run to the end of the line now only match when nothing but comment decoration (//,#,*,///,//!, …) and whitespace precedes the start sequence. Markers with an explicitend_sequence(e.g.[[ … ]]) are self-delimiting and remain position-independent, so they may still follow prose.🐛 Pinned
typerandsphinxcontrib-typerto keep the documentation build working.typer 0.26.8removedrich_utils.STYLE_METAVARandsphinxcontrib-typer 0.9.1requiresrich_utils.STYLE_TYPES; either combination brokesphinx-build. The dependencies are now capped attyper<0.26.8andsphinxcontrib-typer<0.9.1.
1.3.0¶
- Released:
20.06.2026
New and Improved¶
✨ Added Go parser for the
analysemodule.Need ID references and one-line need definitions can now be extracted from Go source files. The supported comment styles are
//and/* */.✨ Added JSONC language support for the
analysemodule.Comments in JSONC files are now parsed for need ID references and one-line need definitions.
.jsonfiles are also checked when they begin with a comment (see jsonc.org).👌 Replaced
gitignore-parserwithignore-pythonfor source discovery.This adds native nested
.gitignoresupport, improves performance, and brings behavioral parity with ubCode. A per-projectfollow_linksconfiguration option was also added.⬆️ Support and test Sphinx-Needs v5-8.
⬆️ Allow Sphinx 9.
📚 Documented C# language support in
features.rst.🧪 Added a Sphinx integration test for C# source files.
Fixes¶
🐛 Register Sphinx-Needs fields with a typed schema.
The
project,file,directoryand URL fields are now registered with a typed (string) schema, so they no longer trigger schema violations on needs that do not set them when strict Sphinx-Needs schema validation is enabled.🐛 Do not mutate the
rebuild='env'src_trace_projectsconfiguration during builds.Incremental Sphinx builds no longer re-read every document on each run.
🐛 Route
analyselogging through the active environment instead of installing a stderr handler at import time.Routine INFO progress no longer goes to stderr unconditionally, and importing the package no longer forces a logging handler onto consumers.
🐛 Validate field default ordering in the oneline configuration.
A required field defined after a field with a default is now reported as an error instead of being silently skipped.
1.2.0¶
- Released:
18.02.2026
New and Improved¶
✨ Added Rust parser for the
analysemodule.Need ID references and one-line need definitions can now be extracted from Rust source files.
👌 Added explicit
git_rootconfiguration option.Users can now explicitly specify the Git root directory instead of relying on automatic detection.
👌 Enhanced warning logging in the oneline parser.
Warning messages now include more context to help diagnose parsing issues.
📚 Added traceability page to the documentation.
📚 Added
features.rstpage documenting the full feature set with source tracing.
Fixes¶
🐛 Fixed space handling in marker extraction.
Leading and trailing spaces in extracted marker content are now correctly stripped.
1.1.0¶
- Released:
02.10.2025
New and Improved¶
✨ Added C# parser for
analysemodule.Need ID references and marked RST blocks can be extracted from C# source files. The comments styles supported are:(
//,/* */,///)✨ Added YAML parser for
analysemodule.Need ID references can be extracted from YAML files. The supported comment style is
#as well as inline comment style, e.g.key: value # comment.👌 Directive
src-traceitself does not create need items anymore and only generate need items from the one-line need definition in the given source.The need item is removed because:
It has no use cases so far.
It creates extra need items users may not actually want in their documentation
It creates errors with some Sphinx-Needs configurations, e.g., when
need_id_requiredorneeds_statusesis defined.
Fixes¶
🐛 Replace absolute path with relative path to fix
local-urlnot working on the non-local environment🐛 Add more file extensions of C/C++ for SourceDiscover
1.0.0¶
- Released:
22.08.2025
New and Improved¶
✨ Added a new
analyseCLI command and corresponding API.The
analysecommand parses source files (Python, C/C++) and extracts markers from comments. It can extract three types of markers, as documented in the analyse section:One-line need definitions
Need ID references
Marked RST blocks
The extracted markers and their metadata are saved to a JSON file for further processing.
✨ Added a new
write rstCLI command.The
write rstcommand writes a reStructuredText file with needextend directive from the extracted markers generated byanalyse. The generated RST can be included in the Sphinx documentation to create the source code links in the existing needs👌 Replaced
virtual_docswith the newanalysemodule.The
virtual_docsfeature, which handled one-line need definitions (OneLineCommentStyle), has been migrated into the newanalysemodule and removed from the core. The caching feature ofvirtual_docsis temporarily removed and may be reintroduced later.👌 Updated the
src-traceSphinx directive.The
src-tracedirective now uses the newanalyseAPI instead of the oldvirtual_docsone.👌 Unified configuration in TOML
The configuration for
src-tracedirective defined in TOML is now compatible with the newanalysemodule.
0.1.2¶
- Released:
16.07.2025
Fixes¶
🐛 Apply default configuration values when not given
When a user does not specify certain configuration options, the extension will automatically use predefined default values, allowing users to get started quickly without needing to customize every option. Users can override these defaults by explicitly providing their own configuration values.
🐛 Fix local links for multi project configurations
Local links between docs and one-line need definitions work correctly, when src_dir in multiple project configurations point at different locations.
0.1.1¶
- Released:
11.07.2025
Initial release of Sphinx-CodeLinks
This version features:
✨ Sphinx Directive
src-trace✨ Virtual Docs and Source Discovery CLI
✨ One-line comment to define a
Sphinx-Needsneed item