diff --git a/docs/source/components/features.rst b/docs/source/components/features.rst index 601b975..f342bc4 100644 --- a/docs/source/components/features.rst +++ b/docs/source/components/features.rst @@ -266,6 +266,42 @@ Features .. fault:: Sphinx-codelinks hallucinates traceability objects in Bash :id: FAULT_BASH_2 +.. feature:: Preprocessor-Aware C/C++ Extraction + :id: FE_PREPROC + + Extract traceability objects only from the C/C++ preprocessor branches that are + actually compiled. + + The default tree-sitter parser sees every comment in a file, regardless of conditional + compilation, so a project using ``#ifdef`` variants yields traceability objects from + *all* branches — including branches that are never built. The optional libclang engine + parses each file as a real translation unit, evaluates the preprocessor, and drops + comments inside inactive regions. Objects in active branches keep their original line + numbers, so source links stay accurate without any source transformation. + + Key capabilities: + + * Evaluation of ``#if`` / ``#ifdef`` / ``#else`` regions to determine active branches + * Per-file compiler flag resolution from a ``compile_commands.json`` compilation database + * Walk-up discovery of the compilation database from the source file + * Standalone parsing of headers, which a compilation database never lists, using + configured ``defines``, ``includes`` and ``std`` + * Opt-in via the :ref:`analyse.preprocessor ` configuration table + * Graceful behavior when the optional ``libclang`` dependency is absent + + See :ref:`preprocessor_engine` for the conceptual overview and header handling. + + .. fault:: Traceability objects in inactive preprocessor branches are extracted + :id: FAULT_PREPROC_1 + + .. fault:: Traceability objects in active preprocessor branches are dropped + :id: FAULT_PREPROC_2 + + .. fault:: Compiler flags are resolved from the wrong compilation database entry + :id: FAULT_PREPROC_3 + + The wrong set of ``-D`` macros makes libclang evaluate the wrong branches as active. + .. feature:: Customized comment styles :id: FE_CMT diff --git a/src/sphinx_codelinks/analyse/analyse.py b/src/sphinx_codelinks/analyse/analyse.py index da75a82..c2c4f7a 100644 --- a/src/sphinx_codelinks/analyse/analyse.py +++ b/src/sphinx_codelinks/analyse/analyse.py @@ -184,6 +184,7 @@ def _resolve_preproc_args(self, src_path: Path) -> list[str] | None: preproc.defines, preproc.includes, preproc.std ) + # @Extract traceability objects with the preprocessor-aware libclang engine, IMPL_PREPROC_1, impl, [FE_PREPROC] def create_src_objects_libclang(self) -> None: from sphinx_codelinks.analyse.preproc import ( # noqa: PLC0415 libclang_parser, diff --git a/src/sphinx_codelinks/analyse/preproc/compile_db.py b/src/sphinx_codelinks/analyse/preproc/compile_db.py index 19a67ca..1a4aa1c 100644 --- a/src/sphinx_codelinks/analyse/preproc/compile_db.py +++ b/src/sphinx_codelinks/analyse/preproc/compile_db.py @@ -35,6 +35,7 @@ TU_SOURCE_SUFFIXES = {".c", ".cpp", ".cc", ".cxx"} +# @Discover compile_commands.json by walking up from the source file, IMPL_PREPROC_3, impl, [FE_PREPROC] def find_compile_db(start: Path, project_root: Path | None = None) -> Path | None: """Walk up from ``start`` looking for compile_commands.json. @@ -103,6 +104,7 @@ def filter_args(argv: list[str], input_file: str) -> list[str]: return out +# @Resolve per-file compiler flags from the compilation database, IMPL_PREPROC_4, impl, [FE_PREPROC] def load_flags_map(db_path: Path) -> dict[Path, list[str]]: """Parse compile_commands.json -> {absolute file path: filtered args}.""" entries = json.loads(db_path.read_text()) @@ -123,6 +125,7 @@ def load_flags_map(db_path: Path) -> dict[Path, list[str]]: return flags +# @Parse headers standalone from configured defines and includes, IMPL_PREPROC_5, impl, [FE_PREPROC] def defines_to_args( defines: list[str], includes: list[Path], std: str = DEFAULT_CPP_STD ) -> list[str]: diff --git a/src/sphinx_codelinks/analyse/preproc/libclang_parser.py b/src/sphinx_codelinks/analyse/preproc/libclang_parser.py index a9f9e09..8278581 100644 --- a/src/sphinx_codelinks/analyse/preproc/libclang_parser.py +++ b/src/sphinx_codelinks/analyse/preproc/libclang_parser.py @@ -56,6 +56,7 @@ def _is_in_skipped( ) +# @Drop comments inside inactive preprocessor branches, IMPL_PREPROC_2, impl, [FE_PREPROC] def extract_active_comments(file_path: Path, args: list[str]) -> list[LibclangComment]: """Return one LibclangComment per ACTIVE comment token in ``file_path``. diff --git a/src/sphinx_codelinks/analyse/preproc/loader.py b/src/sphinx_codelinks/analyse/preproc/loader.py index 0f3701f..7acc42d 100644 --- a/src/sphinx_codelinks/analyse/preproc/loader.py +++ b/src/sphinx_codelinks/analyse/preproc/loader.py @@ -17,6 +17,7 @@ ) +# @Guard the optional libclang dependency with an install hint, IMPL_PREPROC_6, impl, [FE_PREPROC] def load_clang_cindex() -> Any: # type: ignore[explicit-any] """Return the clang.cindex module or raise a clear install error.""" try: