From feb7c70b954aeb5e946af04a82fca9deb603438d Mon Sep 17 00:00:00 2001 From: rahlk <1433964+rahlk@users.noreply.github.com> Date: Tue, 8 Sep 2026 01:27:16 +0000 Subject: [PATCH] docs(api): sync Python API reference to python-sdk 2.0.0-rc.3 --- src/content/docs/reference/python-api/core.md | 34 +- src/content/docs/reference/python-api/java.md | 1954 +++++++++++++---- .../docs/reference/python-api/python.md | 1541 ++++++++++--- .../docs/reference/python-api/typescript.md | 1508 +++++++++++-- 4 files changed, 4101 insertions(+), 936 deletions(-) diff --git a/src/content/docs/reference/python-api/core.md b/src/content/docs/reference/python-api/core.md index 088bae8..4c386a9 100644 --- a/src/content/docs/reference/python-api/core.md +++ b/src/content/docs/reference/python-api/core.md @@ -140,9 +140,9 @@ The full generated reference follows. -[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 1.4.0](https://img.shields.io/badge/cldk-1.4.0-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/1.4.0/) +[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 2.0.0rc3](https://img.shields.io/badge/cldk-2.0.0rc3-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/2.0.0rc3/) -_API reference generated from cldk 1.4.0._ +_API reference generated from cldk 2.0.0rc3._ Core CLDK module. @@ -157,16 +157,14 @@ parsers, and sanitization utilities. > tables, call graphs, and code metrics. > - **Python**: Static analysis via codeanalyzer-python backend (Jedi plus > PyCG call-graph construction). -> - **C**: Basic analysis via libclang for parsing and extracting code structure. Typical usage involves instantiating `CLDK` with a target language, then calling `analysis` to obtain a language-specific analysis facade. > **Note** > This module requires language-specific backends to be available: -> - Java: ``codeanalyzer-*.jar`` (auto-downloaded or specified via path) +> - Java: ``codeanalyzer-java`` (the ``cldk[java]`` extra; carries the jar and its JVM) > - Python: ``codeanalyzer-python`` (auto-installed in virtualenv) -> - C: ``libclang`` (must be installed on the system) ## `CLDK` @@ -190,7 +188,7 @@ methods. | Name | Type | Description | | ---- | ---- | ----------- | -| `language` | `str` | The target programming language for analysis. Supported values are ``"java"``, ``"python"``, and ``"c"`` (case-sensitive). | +| `language` | `str` | The target programming language for analysis. Supported values are ``"java"``, ``"python"``, and ``"typescript"`` (case-sensitive). | **Raises:** @@ -199,7 +197,6 @@ methods. > **See Also** > - `JavaAnalysis`: Java-specific analysis facade. > - `PythonAnalysis`: Python-specific analysis facade. -> - `CAnalysis`: C-specific analysis facade. ### Attributes @@ -212,7 +209,7 @@ methods. #### `CLDK.java` ```python -java(project_path: str | Path | None = None, source_code: str | None = None, analysis_level: str = AnalysisLevel.symbol_table, target_files: List[str] | None = None, eager: bool = False, backend: JavaBackend | None = None) -> JavaAnalysis +java(project_path: str | Path | None = None, analysis_level: str = AnalysisLevel.symbol_table, target_files: List[str] | None = None, eager: bool = False, backend: JavaBackend | None = None) -> JavaAnalysis ``` Create a Java analysis facade. @@ -222,15 +219,14 @@ Create a Java analysis facade. | Name | Type | Description | | ---- | ---- | ----------- | | `project_path` | `str \| Path \| None` | Path to the Java project directory. Optional only when ``backend`` is a `Neo4jConnectionConfig` (the graph is read out of band over Bolt). When provided, the path is validated, it must exist and be a directory, regardless of backend. | -| `source_code` | `str \| None` | Single Java source string (deprecated; pass ``project_path`` instead). | | `analysis_level` | `str` | Analysis depth (see `AnalysisLevel`). | | `target_files` | `List[str] \| None` | Restrict analysis to these files. | | `eager` | `bool` | Force regeneration of cached analysis. | -| `backend` | `JavaBackend \| None` | Backend configuration. Defaults to `CodeAnalyzerConfig`. | +| `backend` | `JavaBackend \| None` | Backend configuration. Defaults to `CodeAnalyzerConfig`; pass a `Neo4jConnectionConfig` to use the read-only Neo4j backend. | **Raises:** -- `CldkInitializationException`: If neither or both of ``project_path`` / ``source_code`` are provided. +- `CldkInitializationException`: If ``project_path`` is missing and the backend is not Neo4j. (The 1.x ``source_code`` single-file mode was removed in 2.0.) #### `CLDK.python` @@ -266,26 +262,18 @@ Create a TypeScript analysis facade. | `analysis_level` | `str` | Analysis depth (see `AnalysisLevel`). | | `target_files` | `List[str] \| None` | Restrict analysis to these files. | | `eager` | `bool` | Force regeneration of cached analysis. | -| `backend` | `TSBackend \| None` | Backend configuration. Defaults to `CodeAnalyzerConfig`; pass a `TSCodeAnalyzerConfig` to set TypeScript-only knobs such as ``tsc_only`` (passes ``--tsc-only``), or a `Neo4jConnectionConfig` to use the read-only Neo4j backend. | - -#### `CLDK.c` - -```python -c(project_path: str | Path) -> CAnalysis -``` - -Create a C analysis facade for the given project directory. +| `backend` | `TSBackend \| None` | Backend configuration. Defaults to `CodeAnalyzerConfig`; pass a `TSCodeAnalyzerConfig` (its ``tsc_only`` is a deprecated no-op, codeanalyzer-typescript removed ``--tsc-only`` in 1.0.0), or a `Neo4jConnectionConfig` to use the read-only Neo4j backend. | #### `CLDK.analysis` ```python -analysis(project_path: str | Path | None = None, source_code: str | None = None, eager: bool = False, analysis_level: str = AnalysisLevel.symbol_table, target_files: List[str] | None = None, analysis_backend_path: str | None = None, analysis_json_path: str | Path | None = None, cache_dir: str | Path | None = None, use_ray: bool = False, neo4j_config: Neo4jConnectionConfig | None = None) -> JavaAnalysis | PythonAnalysis | CAnalysis | TypeScriptAnalysis +analysis(project_path: str | Path | None = None, source_code: str | None = None, eager: bool = False, analysis_level: str = AnalysisLevel.symbol_table, target_files: List[str] | None = None, analysis_backend_path: str | None = None, analysis_json_path: str | Path | None = None, cache_dir: str | Path | None = None, use_ray: bool = False, neo4j_config: Neo4jConnectionConfig | None = None) -> JavaAnalysis | PythonAnalysis | TypeScriptAnalysis ``` Deprecated entry point. Use the per-language factory methods instead. ``CLDK(language).analysis(...)`` is retained as a thin compatibility shim that forwards to -`java` / `python` / `typescript` / `c` with an appropriate +`java` / `python` / `typescript` with an appropriate ``backend=`` configuration object. The former ``analysis_json_path`` is folded into the unified ``cache_dir`` (it is used as @@ -293,7 +281,7 @@ the cache root when ``cache_dir`` is not given). ``analysis_backend_path`` is no supported: the backend binary ships with the packaged dependency, and passing it is ignored. .. deprecated:: - Use `java`, `python`, `typescript`, or `c` + Use `java`, `python`, or `typescript` with a ``backend=`` object. #### `CLDK.treesitter_parser` diff --git a/src/content/docs/reference/python-api/java.md b/src/content/docs/reference/python-api/java.md index 35d4351..de2a0e3 100644 --- a/src/content/docs/reference/python-api/java.md +++ b/src/content/docs/reference/python-api/java.md @@ -106,25 +106,23 @@ follows. -[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 1.4.0](https://img.shields.io/badge/cldk-1.4.0-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/1.4.0/) +[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 2.0.0rc3](https://img.shields.io/badge/cldk-2.0.0rc3-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/2.0.0rc3/) -_API reference generated from cldk 1.4.0._ +_API reference generated from cldk 2.0.0rc3._ ## Analysis Java analysis facade module. This module provides the `JavaAnalysis` class, which serves as the -primary high-level interface for performing static analysis on Java projects -and source files. It combines Tree-sitter-based parsing with the CodeAnalyzer -backend to provide comprehensive code analysis capabilities. +primary high-level interface for performing static analysis on Java projects. +It combines Tree-sitter-based parsing with the CodeAnalyzer backend to provide +comprehensive code analysis capabilities. -> **The analysis supports two modes of operation** -> - **Project mode**: Analyze an entire Java project directory, providing -> access to cross-file analysis features like call graphs and class -> hierarchies. -> - **Source code mode**: Analyze a single Java source code string, useful -> for quick syntactic analysis without a full project structure. +The analysis operates on a project directory (cross-file call graphs, class +hierarchies, the symbol table). The 1.x single-file ``source_code`` mode was +removed in 2.0 (spec leg 3, J-10): pass the project directory, or hand a source +string to `TreesitterJava` directly. > **Key capabilities include** > - Symbol table extraction (classes, methods, fields, imports) @@ -155,12 +153,9 @@ This class provides a comprehensive interface for performing static analysis on Java projects and source files. It combines Tree-sitter-based parsing for syntactic analysis with the CodeAnalyzer backend for semantic analysis. -> **The facade supports two modes of operation** -> - **Project mode**: When initialized with ``project_dir``, provides full -> analysis capabilities including cross-file call graphs, class hierarchies, -> and symbol tables. -> - **Source code mode**: When initialized with ``source_code``, provides -> syntactic analysis capabilities like parsing and AST extraction. +The facade is initialized with ``project_dir`` and provides full analysis +capabilities including cross-file call graphs, class hierarchies, and symbol +tables; the single-file ``source_code`` mode was removed in 2.0. > **Key features** > - Symbol table access with classes, methods, and fields @@ -180,13 +175,13 @@ syntactic analysis with the CodeAnalyzer backend for semantic analysis. | Name | Type | Description | | ---- | ---- | ----------- | | `project_dir` | `` | | -| `source_code` | `` | | | `analysis_level` | `` | | | `eager_analysis` | `` | | | `target_files` | `` | | | `backend_config` | `JavaBackend` | | | `treesitter_java` | `TreesitterJava` | | | `backend` | `JavaAnalysisBackend` | | +| `has_resolution_edges` | `bool` | Whether call sites carry a resolved callee on this backend right now. | #### Methods @@ -196,24 +191,22 @@ syntactic analysis with the CodeAnalyzer backend for semantic analysis. get_imports() -> List[str] ``` -Return all import statements in the source code. +Return every distinct import target of the project, sorted. -This method is intended to extract all import declarations from the -analyzed Java source code, including both single-type imports and -wildcard imports. +A **set**, not a per-file listing and not the file's import order: the Neo4j projection +aggregates every import of a module that resolves to the same target onto one edge, so the +order within a file is not recoverable there and a list that preserved it locally would be +one the two backends disagree about. The 1.x signature is a flat ``List[str]`` and never +carried the file an import belongs to either. **Returns:** -- `List[str]`: A list of import statement strings, each representing a fully -- `List[str]`: qualified import (e.g., ``"java.util.List"``, ``"java.io.*"``). - -**Raises:** - -- `NotImplementedError`: This functionality is not yet implemented. +- `List[str]`: Fully qualified import targets (``"java.util.List"``, ``"java.io.*"``), sorted and +- `List[str]`: distinct. A wildcard keeps its ``.*``; static imports are not marked here. > **See Also** -> `get_symbol_table`: For accessing compilation units which -> contain import information. +> `get_symbol_table`: per-file `JImport` records, +> with spans and the static/wildcard flags. ##### `JavaAnalysis.get_variables` @@ -221,27 +214,29 @@ wildcard imports. get_variables(**kwargs) -> Dict ``` -Return all variables discovered in the source code. - -This method is intended to extract variable declarations from the -analyzed code, including local variables, fields, and parameters. +Return the local variables each callable declares. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `**kwargs` | `` | Implementation-specific filtering options. | +| `**kwargs` | `` | The 1.x signature's filtering options, of which there are none. An unexpected keyword raises `TypeError` naming it, rather than being ignored: silently dropping a filter returns an unfiltered answer that looks filtered. | **Returns:** -- `Dict`: An implementation-defined view of variables discovered in the code. +- `Dict`: ``{".": [JLocalVariable, ...]}``, the J-1 call-graph key of +- `Dict`: meth:`get_call_graph`, and one entry per callable including those declaring nothing. +- `Dict`: Fields and parameters are *not* folded in; they have their own accessors. Each list is +- `Dict`: ordered by ``(start_line, name)``: the Neo4j projection carries a line-only span, so +- `Dict`: two variables declared on one line have no order there to preserve. **Raises:** -- `NotImplementedError`: This functionality is not yet implemented. +- `TypeError`: An unexpected keyword argument was passed. > **See Also** -> `get_fields`: For class-level field access (implemented). +> `get_fields`: class-level fields. +> `get_method_parameters`: a callable's parameters. ##### `JavaAnalysis.get_service_entry_point_classes` @@ -321,10 +316,6 @@ analysis. - `JApplication`: class:`~cldk.models.java.JApplication` object containing: - All compilation units (``symbol_table`` attribute) - Project-level metadata - Aggregated statistics about the codebase -**Raises:** - -- `NotImplementedError`: If called in single-file mode (``source_code`` was provided instead of ``project_dir``). - > **See Also** > `get_symbol_table`: For direct access to the symbol table. > `get_compilation_units`: For a list of compilation units. @@ -384,12 +375,11 @@ extends and implements relationships. **Returns:** -- `nx.DiGraph`: Would return a ``networkx.DiGraph`` with classes as nodes and -- `nx.DiGraph`: edges representing inheritance (subclass -> superclass). - -**Raises:** - -- `NotImplementedError`: This functionality is not yet implemented. +- `nx.DiGraph`: A ``networkx.DiGraph`` with one node per declared type (interfaces, enums, annotations +- `nx.DiGraph`: and records included) and an edge **subclass → supertype** carrying +- `nx.DiGraph`: ``type="EXTENDS"`` or ``type="IMPLEMENTS"``, Java projects the two as separate +- `nx.DiGraph`: relationship types and this keeps them apart. A supertype outside the project is a node +- `nx.DiGraph`: too, spelled as the declaration wrote it. > **See Also** > `get_sub_classes`: For finding subclasses of a specific class. @@ -467,12 +457,12 @@ Constructs and returns a directed graph representing method call relationships across the entire project. Each node represents a method, and each edge represents a call from one method to another. -The call graph requires ``analysis_level`` to be set to ``"call_graph"`` -during initialization for accurate results. +The call graph requires ``analysis_level`` of at least ``"call_graph"``; +below it the graph is empty. **Returns:** -- `nx.DiGraph`: A ``networkx.DiGraph`` where: - Nodes represent methods with attributes containing method metadata (class name, signature, etc.) - Edges represent call relationships, directed from caller to callee - Edge attributes may include call site information +- `nx.DiGraph`: A ``networkx.DiGraph`` where: - Nodes are keyed by the string ``"."`` (e.g. ``"com.acme.Svc.run(java.lang.String)"``), with a `JMethodDetail` under ``method_detail`` and ``kind="callable"`` - Edges represent call relationships, directed from caller to callee, with ``type``, ``weight`` and ``calling_lines`` > **See Also** > `get_callers`: For finding callers of a specific method. @@ -497,10 +487,6 @@ analysis results, sharing with other tools, or debugging. - `str`: including compilation units, classes, methods, and call - `str`: relationships. -**Raises:** - -- `NotImplementedError`: If called in single-file mode (``source_code`` was provided instead of ``project_dir``). - > **See Also** > `get_call_graph`: For the graph object directly. @@ -528,10 +514,6 @@ understanding how a method is used throughout the codebase. - `Dict`: A dictionary containing information about all callers, including: - Caller method signatures - Call site locations (file and line) - Caller class information -**Raises:** - -- `NotImplementedError`: If called in single-file mode (``source_code`` was provided instead of ``project_dir``). - > **See Also** > `get_callees`: For the reverse direction (what a method calls). > `get_call_graph`: For the complete call relationship graph. @@ -560,10 +542,6 @@ dependencies and tracing execution paths. - `Dict`: A dictionary containing information about all callees, including: - Callee method signatures - Target class information - Call site locations within the source method -**Raises:** - -- `NotImplementedError`: If called in single-file mode (``source_code`` was provided instead of ``project_dir``). - > **See Also** > `get_callers`: For the reverse direction (who calls a method). > `get_call_graph`: For the complete call relationship graph. @@ -647,7 +625,7 @@ any inclusion substring AND does not contain any exclusion substring. ##### `JavaAnalysis.get_class` ```python -get_class(qualified_class_name: str) -> JType +get_class(qualified_class_name: str) -> JType | None ``` Return a specific class by its qualified name. @@ -663,9 +641,9 @@ methods, fields, annotations, modifiers, and inheritance information. **Returns:** -- `JType`: class:`~cldk.models.java.JType` object containing all analyzed -- `JType`: information about the class. Returns ``None`` if the class is not -- `JType`: found in the analyzed project. +- `JType \| None`: class:`~cldk.models.java.JType` object containing all analyzed +- `JType \| None`: information about the class. Returns ``None`` if the class is not +- `JType \| None`: found in the analyzed project. > **See Also** > `get_classes`: For all classes in the project. @@ -674,7 +652,7 @@ methods, fields, annotations, modifiers, and inheritance information. ##### `JavaAnalysis.get_method` ```python -get_method(qualified_class_name: str, qualified_method_name: str) -> JCallable +get_method(qualified_class_name: str, qualified_method_name: str) -> JCallable | None ``` Return a specific method by class and method signature. @@ -691,9 +669,19 @@ signature, parameters, return type, annotations, body, and metrics. **Returns:** -- `JCallable`: class:`~cldk.models.java.JCallable` object containing all -- `JCallable`: analyzed information about the method. Returns ``None`` if the -- `JCallable`: method is not found. +- `JCallable \| None`: class:`~cldk.models.java.JCallable` object containing all +- `JCallable \| None`: analyzed information about the method. Returns ``None`` if the +- `JCallable \| None`: method is not found. + +> **Note** +> Two fields depend on which backend answered. On the +> ``analysis.json`` backend ``code`` is the **body block** and +> ``body`` holds every body node. On the Neo4j backend ``code`` is +> the whole **declaration** (it *ends with* the body block, because +> the graph projects one line range per callable and no +> ``body_span``) and ``body`` holds the ``call`` nodes only, about +> 30% of the graph's body nodes, which is what ``call_sites`` needs +> and all it needs. > **See Also** > `get_methods_in_class`: For all methods of a class. @@ -702,13 +690,10 @@ signature, parameters, return type, annotations, body, and metrics. ##### `JavaAnalysis.get_method_parameters` ```python -get_method_parameters(qualified_class_name: str, qualified_method_name: str) -> List[str] +get_method_parameters(qualified_class_name: str, qualified_method_name: str) -> List[JCallableParameter] ``` -Return the parameter types for a specific method. - -Retrieves the list of parameter type names defined in the method -signature. +Return the parameters of a specific method. **Parameters:** @@ -719,9 +704,10 @@ signature. **Returns:** -- `List[str]`: A list of parameter type names as strings, in the order they -- `List[str]`: appear in the method signature. Returns an empty list if the -- `List[str]`: method is not found or has no parameters. +- `List[JCallableParameter]`: class:`~cldk.models.java.models.JCallableParameter` objects +- `List[JCallableParameter]`: (name, type, annotations, position), in signature order. Returns an +- `List[JCallableParameter]`: empty list if the method is not found or has no parameters. (1.x +- `List[JCallableParameter]`: annotated this ``List[str]``; it always returned the objects.) > **See Also** > `get_method`: For complete method information. @@ -729,7 +715,7 @@ signature. ##### `JavaAnalysis.get_java_file` ```python -get_java_file(qualified_class_name: str) -> str +get_java_file(qualified_class_name: str) -> str | None ``` Return the file path containing a class with the given name. @@ -746,8 +732,8 @@ to source files. **Returns:** -- `str`: The file path (as a string) containing the class definition. -- `str`: Returns ``None`` if no class with the given name is found. +- `str \| None`: The file path (as a string) containing the class definition. +- `str \| None`: Returns ``None`` if no class with the given name is found. > **See Also** > `get_class`: To get the full class object by name. @@ -1050,15 +1036,10 @@ Strips all single-line (``//``) and multi-line (``/* */``) comments from the source code, including Javadoc comments. This is useful for code analysis that should ignore comment content. -**Returns:** - -- `str`: A string containing the source code with all comments removed. -- `str`: Whitespace where comments were removed may be preserved or -- `str`: collapsed depending on the implementation. +**Raises:** -> **Note** -> This method operates on the ``source_code`` provided during -> initialization. It requires single-file mode. +- `NotImplementedError`: always. This accessor only ever operated on the +- meth:`TreesitterJava.remove_all_comments` directly. > **See Also** > `get_all_comments`: For extracting comments instead. @@ -1083,16 +1064,18 @@ specified annotations, such as ``@Override``, ``@Test``, **Returns:** -- `Dict[str, List[Dict]]`: Would return a dictionary mapping annotation names to lists of -- `Dict[str, List[Dict]]`: method information dictionaries containing method details and -- `Dict[str, List[Dict]]`: bodies. - -**Raises:** - -- `NotImplementedError`: This functionality is not yet implemented. +- `Dict[str, List[Dict]]`: A dictionary keyed by **the strings passed in**, each mapping to a list of +- `Dict[str, List[Dict]]`: ``{"class", "signature", "method_name", "body"}`` dicts, sorted by +- `Dict[str, List[Dict]]`: ``(class, signature)``. An annotation no callable carries is omitted. ``body`` is +- `Dict[str, List[Dict]]`: attr:`~cldk.models.java.models.JCallable.code`, which is the body block off +- `Dict[str, List[Dict]]`: ``analysis.json`` and the whole declaration off the Neo4j projection, the same +- `Dict[str, List[Dict]]`: documented model property `get_test_methods` hands back. +- `Dict[str, List[Dict]]`: Matching reads the analyzer's own annotations rather than re-parsing source, so it +- `Dict[str, List[Dict]]`: answers on a Neo4j-backed analysis, which carries no module source at all. > **See Also** > `get_test_methods`: For finding test methods specifically. +> `get_decorated_callables`: The projected form, whose J-5 marker rule this shares. ##### `JavaAnalysis.get_test_methods` @@ -1102,18 +1085,24 @@ get_test_methods() -> Dict[str, str] Return methods identified as test methods. -Finds all test methods in the source code by looking for methods -annotated with common test framework annotations (e.g., ``@Test`` -from JUnit). +A callable is a test method when one of its own annotations is a test-declaring one: +``@Test`` (JUnit 4/5, TestNG), ``@ParameterizedTest``, ``@RepeatedTest``, ``@TestFactory`` +or ``@TestTemplate``. The annotation is matched by simple name, so a fully qualified +spelling (``@org.junit.Test``) matches too, and its arguments are ignored, the same +marker rule the spec's J-5 gives ``get_decorated_callables``. -**Returns:** +This reads the **analyzer's own** annotations off the model rather than re-parsing a +module's ``source``, so it answers identically on both backends: a Neo4j-backed analysis +carries no module ``source`` at all (``JCompilationUnit.source`` is ``""``), and the +source-parsing version returned ``{}`` there, an empty reading as "this application has +no tests" on an application with thousands. -- `Dict[str, str]`: A dictionary mapping test method signatures to their source -- `Dict[str, str]`: code bodies. +**Returns:** -> **Note** -> This method operates on the ``source_code`` provided during -> initialization. It requires single-file mode. +- `Dict[str, str]`: A dictionary mapping ``"."``, the call-graph node key of J-1, +- `Dict[str, str]`: unique application-wide, to the callable's ``code``. Note that ``code`` is the body +- `Dict[str, str]`: block off ``analysis.json`` and the whole declaration off the Neo4j projection +- `Dict[str, str]`: (`code`). > **See Also** > `get_methods_with_annotations`: For finding methods with @@ -1138,11 +1127,10 @@ where the specified method is invoked. **Returns:** -- `List[int]`: Would return a list of line numbers (integers) where calls occur. - -**Raises:** - -- `NotImplementedError`: This functionality is not yet implemented. +- `List[int]`: Sorted, distinct **absolute file lines** of every call to a method of that name anywhere +- `List[int]`: in the project, read off ``get_call_graph()``'s ``calling_lines`` edge attribute. A full +- `List[int]`: signature is accepted and cut at its first ``(``; overloads share a name at a call site +- `List[int]`: and so cannot be separated here. Empty when nothing calls that name. > **See Also** > `get_callers`: For finding caller methods instead of lines. @@ -1167,11 +1155,10 @@ analysis. **Returns:** -- `Set[str]`: Would return a set of method names that are call targets. - -**Raises:** - -- `NotImplementedError`: This functionality is not yet implemented. +- `Set[str]`: The subset of ``declared_methods``' keys, cut to their simple names at the last +- `Set[str]`: ``(``, so a signature-keyed dict such as `get_methods_in_class`'s can be passed +- `Set[str]`: straight in, that some call site in the project actually writes. Simple-name matching +- `Set[str]`: no overload resolution, no receiver typing, no hierarchy walk. > **See Also** > `get_call_graph`: For full semantic call resolution. @@ -1281,10 +1268,14 @@ Identifies database delete operations by analyzing get_comments_in_a_method(qualified_class_name: str, method_signature: str) -> List[JComment] ``` -Return all comments contained within a specific method. +Return the method's own comment. -Retrieves all comment nodes (single-line, multi-line, and Javadoc) -that appear within the body of the specified method. +**Not** every comment inside the body: on both backends this is the +analyzer's per-declaration comment list, which holds the comment +immediately above the declaration and nothing else (at most one; 70 of +the 128 callables in the committed ``-a 4`` fixture have one, 65 of +them javadoc). Comments *inside* a method body reach the SDK only +through `get_comment_in_file`, which reports the whole file's. **Parameters:** @@ -1298,6 +1289,14 @@ that appear within the body of the specified method. - `List[JComment]`: A list of `JComment` objects found - `List[JComment]`: within the method body. Returns empty list if method not found. +> **Note** +> On a backend whose source keeps only per-declaration javadoc, the +> Neo4j backend, this narrows to **the method's javadoc alone**: a +> strictly smaller set than every comment in the body, and still a +> real answer about a real declaration, which is why this accessor +> narrows where `get_all_comments` and +> `get_comment_in_file` refuse (J-16). + > **See Also** > `get_comments_in_a_class`: For class-level comments. > `get_all_comments`: For all comments in the project. @@ -1308,11 +1307,13 @@ that appear within the body of the specified method. get_comments_in_a_class(qualified_class_name: str) -> List[JComment] ``` -Return all comments contained within a specific class. +Return the class's own comment. -Retrieves all comment nodes that appear within the class body, -including Javadoc comments, method-level comments, and inline -comments. +**Not** the comments inside the class body: on both backends this is +the type declaration's own comment list, the comment immediately +above ``class Foo``. A method's comment is on +`get_comments_in_a_method`, and an inline comment in a body is +on neither; `get_comment_in_file` reports the whole file's. **Parameters:** @@ -1325,6 +1326,10 @@ comments. - `List[JComment]`: A list of `JComment` objects found - `List[JComment]`: within the class. Returns empty list if class not found. +> **Note** +> Narrows to the class's javadoc alone on a javadoc-only backend, in +> exactly the way `get_comments_in_a_method` does (J-16). + > **See Also** > `get_comments_in_a_method`: For method-specific comments. > `get_comment_in_file`: For file-level comments. @@ -1351,6 +1356,10 @@ including file-level comments, class comments, and method comments. - `List[JComment]`: A list of `JComment` objects found - `List[JComment]`: in the file. Returns empty list if file not found. +**Raises:** + +- `CodeanalyzerExecutionException`: If the backend's source carries no file-level comments at all, the Neo4j projection does not, naming what is missing and what to read instead. An empty list would read as "this file has no comments" (J-16). + > **See Also** > `get_all_comments`: For comments across all files. @@ -1370,6 +1379,10 @@ by file path. - `Dict[str, List[JComment]]`: A dictionary mapping file paths (strings) to lists of - `Dict[str, List[JComment]]`: class:`~cldk.models.java.JComment` objects. +**Raises:** + +- `CodeanalyzerExecutionException`: As `get_comment_in_file` does, and for the same reason (J-16). + > **See Also** > `get_all_docstrings`: For Javadoc comments only. @@ -1391,458 +1404,1589 @@ fields. - `Dict[str, List[JComment]]`: class:`~cldk.models.java.JComment` objects where - `Dict[str, List[JComment]]`: ``is_javadoc`` is ``True``. +> **Note** +> *Which* javadoc depends on the backend: the ``analysis.json`` +> backend reports each compilation unit's own comment list, holding +> the **file-level** javadoc; the Neo4j backend reports the javadoc of +> each **declaration** in the file (type, callable, field, enum +> constant, record component). Both are javadoc keyed by file, and +> they are different sets for the same file (J-16). + > **See Also** > `get_all_comments`: For all comment types. -## Schema +##### `JavaAnalysis.locate` + +```python +locate(path: str, line: int) -> LocateResult +``` -Java data models module. +Resolve a source position to its enclosing callable, with the source in hand. + +The single most-needed query for triaging a scanner alert: an alert arrives as +``file:line`` and this resolves it to the enclosing callable in one call, rather than +``get_method``, falling back to ``get_callers``, falling back to scanning the symbol table +by hand. Four outcomes stay distinguishable, see +`LocateResult`: inside a callable (``callable`` set, +plus ``body`` when a body node is that precise), at real module scope (``module_scope`` +diagnostic), in the gap between two callables (also module scope, never snapped to the +nearest callable), or in a file the analysis has no module for (``file_not_in_graph``). + +There is no ``col`` parameter: the Neo4j graph projects only ``start_line``/``end_line`` on +``:JCallable`` and ``:JBodyNode``, so a column would work in process and be silently inert +over the graph. + +**Parameters:** -This module defines Pydantic model classes for representing Java code elements -extracted during static analysis. These models form the core data structures -returned by `JavaAnalysis` and related classes. +| Name | Type | Description | +| ---- | ---- | ----------- | +| `path` | `str` | The file path. Normalised against the backend's module keys, so a ``./``-prefixed or absolute path resolves rather than reading back as ``file_not_in_graph``. | +| `line` | `int` | The 1-based line number. | -> **The models represent** -> - **Types**: Classes, interfaces, enums, records (`JType`) -> - **Callables**: Methods, constructors (`JCallable`) -> - **Fields**: Class and instance variables (`JField`) -> - **Comments**: Javadoc and inline comments (`JComment`) -> - **Imports**: Import declarations (`JImport`) -> - **CRUD Operations**: Database operations (`JCRUDOperation`) -> - **Call Information**: Method call details (`JMethodDetail`, `JCallSite`) +**Returns:** -All models inherit from Pydantic's `BaseModel`, providing: - - Automatic validation of field types - - JSON serialization/deserialization - - Schema generation for documentation +- `LocateResult`: class:`~cldk.analysis.commons.results.LocateResult` carrying the innermost body +- `LocateResult`: node, the enclosing callable, its owning type, its module, and the source slice, never +- `LocateResult`: an ambiguous empty. ``module.module_name`` is the unit's **declared package** (J-2), +- `LocateResult`: and ``source`` is the enclosing callable's text, which is the **body block** on the +- `LocateResult`: ``analysis.json`` backend and the whole **declaration** over Neo4j (see +- `LocateResult`: meth:`get_source`). A module-scope result over Neo4j is ``""`` plus a +- `LocateResult`: ``module_source_unavailable`` diagnostic: the graph carries no module text. > **See Also** -> - `JavaAnalysis`: Analysis facade using these models. -> - `enums`: Related enumeration types. +> `locate_many`: The bulk form, the point, not an optimisation. -### `JComment` +##### `JavaAnalysis.locate_many` ```python -class JComment(BaseModel) +locate_many(positions: Sequence[Tuple[str, int]]) -> List[LocateResult] ``` -Represents a comment in Java code. +Resolve many ``(path, line)`` positions in one round trip, in input order. -#### Attributes +**Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `content` | `str \| None` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `start_column` | `int` | | -| `end_column` | `int` | | -| `is_javadoc` | `bool` | | +| `positions` | `Sequence[Tuple[str, int]]` | The ``(path, line)`` pairs to resolve, e.g. from a scanner's alert list. | -### `JImport` +**Returns:** + +- `List[LocateResult]`: class:`~cldk.analysis.commons.results.LocateResult` per input position, in the +- `List[LocateResult]`: same order. + +> **See Also** +> `locate`: The single-position form. + +##### `JavaAnalysis.resolve_callable` ```python -class JImport(BaseModel) +resolve_callable(name: str, in_class: str | None = None, in_module: str | None = None) -> SliceNode ``` -Represents a Java import declaration. - -#### Attributes +Resolve a callable name to the one callable it names, in the caller's vocabulary. -| Name | Type | Description | -| ---- | ---- | ----------- | -| `path` | `str` | | -| `is_static` | `bool` | | -| `is_wildcard` | `bool` | | +The addressing step every name-taking accessor performs, exposed so a caller can perform it +once and keep the answer:: -### `JRecordComponent` + node = java.resolve_callable("cancelOrder(java.lang.Integer, boolean)", in_class="TradeDirect") + node.callable # "…impl.direct.TradeDirect.cancelOrder(java.lang.Integer, boolean)" + node.file, node.line -```python -class JRecordComponent(BaseModel) -``` +``name`` matches whole or as a dotted suffix, **and** against the signature with its +parameter tail cut, so ``"cancelOrder"`` names a method a caller has not typed the +parameters of; the tail-carrying spelling is what resolves one overload out of a pair +(J-3). ``in_class`` is a dotted suffix of the owning type's qualified name, which, for a +local or anonymous class, carries the callable that declares it (the J-1 erratum); +``in_module`` is a repo-relative path suffix or a dotted spelling of the unit's **declared +package**, optionally qualified by a type it declares (J-2). Ambiguity raises with every +candidate; nothing is guessed. -Represents a component of a Java record. +Everything the analyzer emitted as a callable is addressable (J-6): an initializer +(``$0()``) resolves and behaves like a method, and an **implicit** callable +resolves with ``line=-1``, it has no span at all, which is why `get_source` refuses +it by name rather than returning an empty string. -#### Attributes +**Raises:** -| Name | Type | Description | -| ---- | ---- | ----------- | -| `comment` | `JComment \| None` | | -| `name` | `str` | | -| `type` | `str` | | -| `modifiers` | `List[str]` | | -| `annotations` | `List[str]` | | -| `default_value` | `Union[str, None, Any]` | | -| `is_var_args` | `bool` | | +- `AmbiguousName`: More than one callable matched. +- `SelectorNotInGraph`: Nothing matched, naming the argument that missed. -### `JField` +##### `JavaAnalysis.resolve_value` ```python -class JField(BaseModel) +resolve_value(name: str, within: str) -> SliceNode ``` -Represents a field in a Java class or interface. +Resolve a value name inside a callable, in Java, a parameter, to the position that +carries it. -#### Attributes +The same resolution the dataflow accessors perform on their ``src``, exposed so a caller can +check what a name means before asking a question of it:: -| Name | Type | Description | -| ---- | ---- | ----------- | -| `comment` | `JComment \| None` | | -| `type` | `str` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `variables` | `List[str]` | | -| `modifiers` | `List[str]` | | -| `annotations` | `List[str]` | | -| `variable_initializers` | `Dict[str, str] \| None` | | + java.resolve_value("orderID", within="TradeDirect.cancelOrder").kind # "parameter" -### `JCallableParameter` +**Raises:** + +- `AmbiguousName`: ``within`` named more than one callable, or ``name`` more than one value. +- `SelectorNotInGraph`: No such callable, or no such value in it. + +##### `JavaAnalysis.get_source` ```python -class JCallableParameter(BaseModel) +get_source(node_id: str) -> str ``` -Represents a parameter of a Java callable. +Return the source text named by ``node_id``, a callable, or one of its body nodes. -#### Attributes +``node_id`` is a callable's ``"."`` name (what `resolve_callable` +returns in ``callable``), a callable's opaque id (what it returns in ``ref``), or the +body-node id `node_id` hands back, so a +statement or call site `locate` found can be re-fetched precisely. Passed back as +received, never composed. + +**What comes back for a callable depends on the backend, and it is the graph's difference, +not this method's.** On the ``analysis.json`` backend it is the **body block**; over Neo4j +it is the whole **declaration**, which *ends with* that body block: the projection carries +one line range per callable and no ``body_span`` (upstream codeanalyzer-java#176). The +relation is exact and total, so a caller reading either can rely on it; it is recorded in +the lossiness table of ``docs/agent-api-reference.md`` and asserted by the live parity +suite. A **body node** likewise has text only on the ``analysis.json`` backend: the graph +carries none below callable granularity. + +**Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str \| None` | | -| `type` | `str` | | -| `annotations` | `List[str]` | | -| `modifiers` | `List[str]` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `start_column` | `int` | | -| `end_column` | `int` | | - -### `JEnumConstant` +| `node_id` | `str` | A callable's name or id, or an id from `locate`, passed back as received, not composed. | -```python -class JEnumConstant(BaseModel) -``` +**Returns:** -Represents a constant in an enumeration. +- `str`: The source text, never an ambiguous empty string. -#### Attributes +**Raises:** -| Name | Type | Description | -| ---- | ---- | ----------- | -| `name` | `str` | | -| `arguments` | `List[str]` | | +- `KeyError`: Nothing matches ``node_id``, or it names a node with no recoverable source, an implicit callable (the analyzer emits it with no span and no body), or, over Neo4j, a body node. The message names the reason. -### `JCRUDOperation` +##### `JavaAnalysis.describe` ```python -class JCRUDOperation(BaseModel) +describe(nodes: Sequence[object]) -> List[SliceNode] ``` -Represents a CRUD operation. +Fill in ``source`` for these positions, in one round trip. -#### Attributes +Addressing answers *where*; this answers *what*, and it is a second call because source is +the one field with no size ceiling. Takes anything carrying an address, slice nodes, a +``locate()`` result, and gives back the same +`SliceNode` shape with ``source`` filled. + +Afterwards, ``source=None`` means exactly one thing: **this position exists and there is no +text for it.** A ref that names nothing raises instead. + +**Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `line_number` | `int` | | -| `operation_type` | `CRUDOperationType \| None` | | - -### `JCRUDQuery` +| `nodes` | `Sequence[object]` | The positions to hydrate. An empty sequence costs no round trip. | -```python -class JCRUDQuery(BaseModel) -``` +**Returns:** -Represents a CRUD query. +- `List[SliceNode]`: The same positions, in the same order, with ``source`` filled where the backend has +- `List[SliceNode]`: text for them. -#### Attributes +**Raises:** -| Name | Type | Description | -| ---- | ---- | ----------- | -| `line_number` | `int` | | -| `query_arguments` | `List[str] \| None` | | -| `query_type` | `CRUDQueryType \| None` | | +- `KeyError`: A ref names nothing in this application. +- `TypeError`: An element carries no address to look up. -### `JCallSite` +##### `JavaAnalysis.get_cfg` ```python -class JCallSite(BaseModel) +get_cfg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[JCfgEdge] ``` -Represents a call site. +Return one page of the control flow edges within one callable. -#### Attributes +The intraprocedural half of "how does this method run": the analyzer's own CFG, one edge +per successor, with the branch kind on the edge rather than implied by order. Endpoints are +body-node ids `get_source` accepts, so a statement on a path can be read back. + +**Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `comment` | `JComment \| None` | | -| `method_name` | `str` | | -| `receiver_expr` | `str` | | -| `receiver_type` | `str` | | -| `argument_types` | `List[str]` | | -| `argument_expr` | `List[str]` | | -| `return_type` | `str` | | -| `callee_signature` | `str` | | -| `is_static_call` | `bool \| None` | | -| `is_private` | `bool \| None` | | -| `is_public` | `bool \| None` | | -| `is_protected` | `bool \| None` | | -| `is_unspecified` | `bool \| None` | | -| `is_constructor_call` | `bool` | | -| `crud_operation` | `JCRUDOperation \| None` | | -| `crud_query` | `JCRUDQuery \| None` | | -| `start_line` | `int` | | -| `start_column` | `int` | | -| `end_line` | `int` | | -| `end_column` | `int` | | +| `callable` | `str` | The callable's name, resolved as in `resolve_callable`. | +| `in_class` | `str \| None` | Disambiguate by owning class. | +| `page_size` | `int` | Most edges to return. | +| `cursor` | `str \| None` | ``next_cursor`` from a previous page. | -### `JVariableDeclaration` - -```python -class JVariableDeclaration(BaseModel) -``` +**Returns:** -Represents a variable declaration. +- `EdgePage[JCfgEdge]`: class:`~cldk.analysis.commons.results.EdgePage` of +- `EdgePage[JCfgEdge]`: class:`~cldk.models.java.models.JCfgEdge`, whose ``complete`` says whether the page is +- `EdgePage[JCfgEdge]`: the whole graph and whose ``total`` says how large that is. -#### Attributes +**Raises:** -| Name | Type | Description | -| ---- | ---- | ----------- | -| `comment` | `JComment \| None` | | -| `name` | `str` | | -| `type` | `str` | | -| `initializer` | `str` | | -| `start_line` | `int` | | -| `start_column` | `int` | | -| `end_line` | `int` | | -| `end_column` | `int` | | +- `AmbiguousName`: ``callable`` named more than one callable. +- `SelectorNotInGraph`: Nothing matched. +- `ValueError`: ``page_size`` below 1, or a cursor from another page. +- `CodeanalyzerUsageException`: ``callable`` is an implicit callable, it resolves (J-6) and the analyzer emits it with no body, so there is no flow to page, or the analysis was built below ``analysis_level="program_dependency_graph"``. -### `InitializationBlock` +##### `JavaAnalysis.get_cdg` ```python -class InitializationBlock(BaseModel) +get_cdg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[JCdgEdge] ``` -Represents an initialization block in Java. - -#### Attributes +Return one page of the control dependence edges within one callable. -| Name | Type | Description | -| ---- | ---- | ----------- | -| `file_path` | `str` | | -| `comments` | `List[JComment]` | | -| `annotations` | `List[str]` | | -| `thrown_exceptions` | `List[str]` | | -| `code` | `str` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `is_static` | `bool` | | -| `referenced_types` | `List[str]` | | -| `accessed_fields` | `List[str]` | | -| `call_sites` | `List[JCallSite]` | | -| `variable_declarations` | `List[JVariableDeclaration]` | | -| `cyclomatic_complexity` | `int` | | +``src`` is the branching node ``dst`` is control dependent on, the analyzer's +post-dominance over the CFG, not re-derived here. Arguments and failures are +`get_cfg`'s. -### `JCallable` +##### `JavaAnalysis.get_ddg` ```python -class JCallable(BaseModel) +get_ddg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[JDdgEdge] ``` -Represents a callable entity such as a method or constructor in Java. - -#### Attributes +Return one page of the data dependence edges within one callable. -| Name | Type | Description | -| ---- | ---- | ----------- | -| `signature` | `str` | | -| `is_implicit` | `bool` | | -| `is_constructor` | `bool` | | -| `comments` | `List[JComment]` | | -| `annotations` | `List[str]` | | -| `modifiers` | `List[str]` | | -| `thrown_exceptions` | `List[str]` | | -| `declaration` | `str` | | -| `parameters` | `List[JCallableParameter]` | | -| `return_type` | `Optional[str]` | | -| `code` | `str` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `code_start_line` | `int` | | -| `referenced_types` | `List[str]` | | -| `accessed_fields` | `List[str]` | | -| `call_sites` | `List[JCallSite]` | | -| `is_entrypoint` | `bool` | | -| `variable_declarations` | `List[JVariableDeclaration]` | | -| `crud_operations` | `List[JCRUDOperation] \| None` | | -| `crud_queries` | `List[JCRUDQuery] \| None` | | -| `cyclomatic_complexity` | `int \| None` | | +Each edge names the variable it flows (``var``) and the evidence for it (``prov``), which in +Java is one of **two** tiers: ``ssa`` (324,959 edges on the reference graph) or +``points-to`` (1,134). `prov_rank` ranks ``points-to`` +least certain, which is what a caller weighing two hops reads. Arguments and failures are +`get_cfg`'s. -### `JType` +##### `JavaAnalysis.slice_backward` ```python -class JType(BaseModel) +slice_backward(src: str, within: str, depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice ``` -Represents a Java class or interface. +Return everything the value ``src`` depends on. -#### Attributes +On an analysis whose port lattice carries no dependence edge, codeanalyzer-java before +3.0.3, or ``--l3-engine wala``, that is the seed plus the argument vertex at every call +site that passes a value into the parameter, and nothing behind those arguments. It is +still a real answer that varies with the program, which is why this one answers where +`slice_forward` refuses. From 3.0.3 the walk carries on into the statements that +computed those arguments. + +**Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `is_interface` | `bool` | | -| `is_inner_class` | `bool` | | -| `is_local_class` | `bool` | | -| `is_nested_type` | `bool` | | -| `is_class_or_interface_declaration` | `bool` | | -| `is_enum_declaration` | `bool` | | -| `is_annotation_declaration` | `bool` | | -| `is_record_declaration` | `bool` | | -| `is_concrete_class` | `bool` | | -| `comments` | `List[JComment] \| None` | | -| `extends_list` | `List[str] \| None` | | -| `implements_list` | `List[str] \| None` | | -| `modifiers` | `List[str] \| None` | | -| `annotations` | `List[str] \| None` | | -| `parent_type` | `str` | | -| `nested_type_declarations` | `List[str] \| None` | | -| `callable_declarations` | `Dict[str, JCallable]` | | -| `field_declarations` | `List[JField]` | | -| `enum_constants` | `List[JEnumConstant] \| None` | | -| `record_components` | `List[JRecordComponent] \| None` | | -| `initialization_blocks` | `List[InitializationBlock] \| None` | | -| `is_entrypoint_class` | `bool` | | +| `src` | `str` | The value's name, in Java, a parameter of ``within``. | +| `within` | `str` | The callable to look inside. Required: a value name is scoped by its callable. | +| `depth` | `int \| None` | Most hops from the seed; ``None`` for the whole cone. | +| `max_nodes` | `int` | Most nodes to return. A cap that fires is reported, never silent. | -### `JCompilationUnit` +**Returns:** + +- `Slice`: class:`~cldk.analysis.commons.results.Slice` containing the seed, ordered by node id. + +**Raises:** + +- `AmbiguousName`: ``within`` or ``src`` matched more than one thing. +- `SelectorNotInGraph`: Either matched nothing. +- `ValueError`: ``depth`` is not a positive ``int``, or ``max_nodes`` is below 1. + +##### `JavaAnalysis.slice_forward` ```python -class JCompilationUnit(BaseModel) +slice_forward(src: str, within: str, depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice ``` -Represents a compilation unit in Java. +Return everything the value ``src`` can affect. -#### Attributes +**Java cannot answer this today and says so.** A parameter vertex has no outgoing dependence +edge in codeanalyzer-java's output, so the result would be the seed alone for every +parameter of every application, indistinguishable from "this parameter affects nothing". +Arguments and names are judged first; then it raises naming the gap. -| Name | Type | Description | -| ---- | ---- | ----------- | -| `file_path` | `str` | | -| `package_name` | `str` | | -| `comments` | `List[JComment]` | | -| `imports` | `List[str]` | | -| `import_declarations` | `List[JImport]` | | -| `type_declarations` | `Dict[str, JType]` | | -| `is_modified` | `bool` | | +**Raises:** -#### Methods +- `CodeanalyzerExecutionException`: The analyzer's port lattice carries no dependence edge. -##### `JCompilationUnit.normalize_import_fields` +##### `JavaAnalysis.backward_cone` ```python -normalize_import_fields(data: Any) -> Any +backward_cone(sinks: Sequence[str], depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice ``` -Normalize legacy and structured import payloads into both model fields. +Return every callable that can reach any of ``sinks``, "what could get here". + +A call-graph cone, so its vertices are callables; the sinks are in the result and in +``roots``. Bounded by default, because an unbounded cone on a real application is a +truncated answer to a question nobody asked; ``depth=None`` asks for the whole thing and +``total`` says how much a cap left out. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `data` | `Any` | Raw input payload for ``JCompilationUnit``. | +| `sinks` | `Sequence[str]` | The callables to walk back from, each resolved as in `resolve_callable`. | +| `depth` | `int \| None` | Most call hops back; ``None`` for the whole cone. | +| `max_nodes` | `int` | Most nodes to return. | -**Returns:** +**Raises:** -- `Any`: Input payload with ``imports`` and ``import_declarations`` synchronized. +- `AmbiguousName`: A sink matched more than one callable. +- `SelectorNotInGraph`: A sink matched none. +- `TypeError`: ``sinks`` is a bare string. +- `ValueError`: ``sinks`` is empty, or a bound is out of range. -### `JMethodDetail` +##### `JavaAnalysis.reaches` ```python -class JMethodDetail(BaseModel) +reaches(src: str, dst: str, depth: int | None = None) -> bool ``` -Represents details about a method in a Java class. +Return whether there is a call path from ``src`` to ``dst``. + +The cheap check before asking for the paths themselves. **Unbounded by default**, unlike the +slices: a hop budget on a boolean would make "there is no path" and "there is no path within +five hops" the same ``False``. + +**Raises:** + +- `AmbiguousName`: Either name matched more than one callable. +- `SelectorNotInGraph`: Either matched none. +- `ValueError`: ``depth`` is not a positive ``int``. + +##### `JavaAnalysis.callers_of` + +```python +callers_of(name: str, in_class: str | None = None, in_module: str | None = None) -> List[SliceNode] +``` + +Return the callables that call this one, one hop back, addressed by name. + +The name-based sibling of `get_callers`, which takes a class name plus a method +signature and returns raw dicts; that one is a frozen 1.x signature and is unchanged. ``[]`` +is unambiguous, a name matching nothing raises. + +**Raises:** + +- `AmbiguousName`: ``name`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. + +##### `JavaAnalysis.callees_of` + +```python +callees_of(name: str, in_class: str | None = None, in_module: str | None = None) -> List[SliceNode] +``` + +Return the callables this one calls, one hop forward, addressed by name. + +Java's call graph has no external vertices on either backend (J-1), so unlike Python's and +TypeScript's this never reports a ``kind="external"`` node; ``get_external_symbols`` is where +call targets outside the project live. + +**Raises:** + +- `AmbiguousName`: ``name`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. + +##### `JavaAnalysis.paths_between` + +```python +paths_between(src: str, dst: str, src_within: str, dst_within: str, depth: int | None = None, max_paths: int = DEFAULT_MAX_PATHS) -> FlowPaths +``` + +Return how the value ``src`` reaches the value ``dst``, the sequences, where a slice is +the set. + +Two scopes, not one, and neither defaults to the other: a value is addressed by a name plus +the callable it enters, and a single scope could never find the cross-callable path this +exists for. + +**Java cannot answer this today and says so**: see `slice_forward`. Arguments and +names are judged first. + +**Raises:** + +- `AmbiguousName / SelectorNotInGraph`: A name matched more than one thing, or nothing. +- `ValueError`: A bound is out of range, or the two endpoints are the same position. +- `CodeanalyzerExecutionException`: The analyzer's port lattice carries no dependence edge. + +##### `JavaAnalysis.call_paths_between` + +```python +call_paths_between(src: str, dst: str, depth: int | None = None, max_paths: int = DEFAULT_MAX_PATHS) -> FlowPaths +``` + +Return how one callable reaches another, the evidence-carrying form of `reaches`. + +Every hop is ``via="call"`` with no ``var`` and no ``prov``: a call edge carries neither, and +saying so is better than inventing a provenance. Only shortest paths, ordered so that +``max_paths`` truncates a prefix of one total order rather than an arbitrary subset. + +**Raises:** + +- `AmbiguousName / SelectorNotInGraph`: A name matched more than one callable, or none. +- `ValueError`: A bound is out of range, or ``src`` and ``dst`` name the same callable. + +##### `JavaAnalysis.flows_to_call` + +```python +flows_to_call(src: str, callee: str, within: str, depth: int | None = None) -> bool +``` + +Return whether the value ``src`` reaches any argument of a call to ``callee``. + +**Java cannot answer this today and says so**: see `slice_forward`. + +**Raises:** + +- `AmbiguousName / SelectorNotInGraph`: A name matched more than one thing, or nothing. +- `ValueError`: ``depth`` is not a positive ``int``. +- `CodeanalyzerExecutionException`: The analyzer's port lattice carries no dependence edge. + +##### `JavaAnalysis.flows_to_argument` + +```python +flows_to_argument(src: str, callee: str, arg: str, within: str, depth: int | None = None) -> bool +``` + +Return whether the value ``src`` reaches ``callee``'s parameter ``arg``. + +A different question from `flows_to_call`: a tainted value routinely reaches a method +without reaching the parameter that matters. ``arg`` is named, never numbered. + +**Java cannot answer this today and says so**: see `slice_forward`. + +**Raises:** + +- `AmbiguousName / SelectorNotInGraph`: A name matched more than one thing, or nothing, including ``arg`` naming no parameter of ``callee``, which is a caller error and not a ``False``. +- `ValueError`: ``depth`` is not a positive ``int``. +- `CodeanalyzerExecutionException`: The analyzer's port lattice carries no dependence edge. + +##### `JavaAnalysis.get_callables_overview` + +```python +get_callables_overview() -> List[JCallableOverview] +``` + +Return a lightweight overview of every callable in the project, in one bulk read. + +A field-projected alternative to `get_methods` for enumeration: each +`JCallableOverview` carries the callable's addressable +key, declaring type, kind, location, modifiers and annotation names, but not the full +reconstruction (body nodes, call sites, local classes). Body-inspect the few you need +afterwards via `get_method` or `get_method_bodies`. + +**Returns:** + +- `List[JCallableOverview]`: A flat list, one entry per callable the analyzer emitted, initializers, implicit +- `List[JCallableOverview]`: constructors and the callables of local and anonymous classes included (J-6). + +> **See Also** +> `get_decorated_callables`: The same projection filtered by annotation. +> `get_method_bodies`: Bulk source fetch for chosen keys. + +##### `JavaAnalysis.get_method_bodies` + +```python +get_method_bodies(signatures: List[str]) -> Dict[str, str] +``` + +Return source text for the given callables, in one bulk read. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signatures` | `List[str]` | The keys `get_callables_overview` hands back (``JCallableOverview.key``, the J-1 ``"."`` name), matched exactly. A bare Java signature is unique only within its declaring type, so it is not an address here. | + +**Returns:** + +- `Dict[str, str]`: A dict mapping each key to its source text. Keys with no matching callable are omitted, +- `Dict[str, str]`: as are callables with no source text of their own, the implicit constructors, and only +- `Dict[str, str]`: those (1,117 of daytrader8's 1,216). The ``$N()`` initializers carry a body +- `Dict[str, str]`: block and do come back. Every value is a real, non-empty ``str``. + +> **Note** +> The text differs by backend exactly as `get_source` does: the body block off +> ``analysis.json``, the whole declaration off the Neo4j projection +> (codeanalyzer-java#176). + +##### `JavaAnalysis.get_decorated_callables` + +```python +get_decorated_callables(markers: List[str]) -> List[JCallableOverview] +``` + +Return overviews of callables annotated with any of the given markers, in one bulk read. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `markers` | `List[str]` | Annotation names. Each matches by simple name (``Test``), with a leading ``@`` ignored (``@Test``), or by fully-qualified name (``org.junit.Test``), J-5. Nothing is matched fuzzily (E8). | + +**Returns:** + +- `List[JCallableOverview]`: A list of `JCallableOverview`, one per matching +- `List[JCallableOverview]`: callable. + +> **See Also** +> `get_callables_overview`: The unfiltered projection. + +##### `JavaAnalysis.get_entrypoints` + +```python +get_entrypoints() -> List[JCallableOverview] +``` + +Return overviews of every callable the analyzer marked as an entrypoint, in one bulk read. + +codeanalyzer-java's own detection pass already finds servlet methods, JAX-RS resource +methods, MDB listeners and the rest; this surfaces that mark instead of making a caller +rediscover it. 133 of daytrader8's 1,216 callables carry it. + +**Returns:** + +- `List[JCallableOverview]`: A list of `JCallableOverview`. Empty means the pass +- `List[JCallableOverview]`: found no entrypoint *callables*, the mark itself is never missing, on either backend. + +> **See Also** +> `get_entrypoint_classes`: The type-level sibling this walk never sees. +> `get_entrypoint_coverage`: Whether the pass itself had gaps, which Java, alone +> of the three languages, cannot say. + +##### `JavaAnalysis.get_entrypoint_classes` + +```python +get_entrypoint_classes() -> List[JClassOverview] +``` + +Return overviews of every type the analyzer marked as an entrypoint in its own right. + +`get_entrypoints` walks callables only, so a type marked at the declaration with no +individually-marked method is invisible to it. This is that sibling, the projected form of +`get_entry_point_classes`, which keeps its 1.x ``Dict[str, JType]`` shape. + +##### `JavaAnalysis.get_entrypoint_coverage` + +```python +get_entrypoint_coverage() -> EntrypointCoverage +``` + +Report the entrypoint pass's coverage, which for Java is that **there is no report**. + +codeanalyzer-java 3.0.1 emits the entrypoint marks and nothing about the pass that made +them: ``analysis.json`` has no report key and the ``:JApplication`` anchor carries only +``name``/``schema_version``/``analyzer_name``/``analyzer_version``. So this returns an +`EntrypointCoverage` whose ``diagnostics`` carry +``entrypoint_report_unavailable`` and whose other fields are therefore not coverage +information, the same "say so honestly" shape as +`diagnostics`'s +``module_source_unavailable``, and identical on both backends (J-4). + +It is deliberately **not** synthesised from the ``is_entrypoint`` booleans: a count of +syntactically-marked callables is not a coverage record. + +##### `JavaAnalysis.get_callsites_for` + +```python +get_callsites_for(signatures: List[str]) -> Dict[str, List[JCallSite]] +``` + +Return the call sites of the given callables, keyed by the key that named them. + +Avoids the per-callable reconstruction fan-out when call sites are wanted for a specific +frontier. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signatures` | `List[str]` | The keys `get_callables_overview` hands back, matched exactly. | + +**Returns:** + +- `Dict[str, List[JCallSite]]`: A dict mapping each existing key to its list of +- `Dict[str, List[JCallSite]]`: class:`~cldk.models.java.models.JCallSite` (empty when the callable makes no calls); +- `Dict[str, List[JCallSite]]`: keys with no matching callable are omitted. + +> **See Also** +> `has_resolution_edges`: Distinguishes a genuinely unresolved callee from a graph +> carrying no resolution at all. + +##### `JavaAnalysis.get_external_symbols` + +```python +get_external_symbols() -> Dict[str, JExternalSymbol] +``` + +Return every call-graph endpoint outside the analysed project, keyed by its +``@external`` id. + +**Returns:** + +- `Dict[str, JExternalSymbol]`: The analyzer's own ``external_symbols`` map. Empty means the run homed them and this +- `Dict[str, JExternalSymbol]`: project's call graph makes no calls outside itself. + +**Raises:** + +- `CodeanalyzerExecutionException`: The run never homed them, which is a different fact. codeanalyzer-java emits ``external_symbols`` only under ``--external-calls``, which ``--emit neo4j`` forces and a local ``-a`` run does not, so the Neo4j backend answers and the local one refuses rather than returning an empty dict that would read as "nothing outside". + +##### `JavaAnalysis.get_artifacts` + +```python +get_artifacts() -> Dict[str, PyArtifact] +``` + +Return every non-code project artifact (``pom.xml``, properties files, descriptors, …), +keyed by repo-relative path. + +This layer (``Artifact``/``ConfigKey``/``Package`` nodes) is the one part of the graph every +``codeanalyzer-`` projects identically and unprefixed, so it is carried in the shared +``Py*`` models rather than in Java-specific ones. ``JArtifact.text_truncated`` has no home +on the shared model; read it off ``JApplication.artifacts`` when it matters. + +> **See Also** +> `get_dependencies`, `get_config_keys`, `get_config_uses`. + +##### `JavaAnalysis.get_dependencies` + +```python +get_dependencies(direct_only: bool = False, ecosystem: str | None = None, declared_in: str | None = None) -> List[PyDependency] +``` + +Return every declared dependency, one entry per declaring manifest, optionally filtered. + +All three filters default to "don't filter". The Maven ``group`` coordinate has no home on +the shared model; read it off ``JApplication.dependencies`` when ``name`` alone is +ambiguous. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `direct_only` | `bool` | When ``True``, excludes lockfile-only transitive pins. | +| `ecosystem` | `str \| None` | When given, only dependencies from this package ecosystem (``"maven"``). | +| `declared_in` | `str \| None` | When given, only dependencies declared by this artifact id. | + +##### `JavaAnalysis.get_config_keys` + +```python +get_config_keys() -> Dict[str, PyConfigKey] +``` + +Return every configuration key flattened out of a config-bearing artifact, keyed +``"@key/"`` (``pom.xml@key/project.artifactId``). + +That is the analyzer's own id with its ``can://artifact//`` prefix dropped: the +application name belongs to the run, not to the key, and ``can://`` ids stay off the public +surface (E6). The full id is still on ``PyConfigKey.id``. Python and TypeScript key this by +the raw id today; aligning the three is tracked as python-sdk#346 and is deliberately not +done piecemeal here. + +##### `JavaAnalysis.get_config_uses` + +```python +get_config_uses(key: str | None = None) -> List[PyConfigUseEdge] +``` + +Return resolved code-to-config edges: which body node reads which config key. + +Always ``[]`` on codeanalyzer-java 3.0.1, which has no code-to-config detector (there is no +``config_uses`` on the Java wire), so there is nothing for ``key`` to filter. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | `str \| None` | When given, only edges whose target key has this bare ``key``. | + +> **See Also** +> `get_config_readers`: The same edges, resolved to their reading callables. +> `get_unresolved_config_reads`: The reads this cannot show. + +##### `JavaAnalysis.get_unresolved_config_reads` + +```python +get_unresolved_config_reads() -> List[PyConfigRead] +``` + +Return every detector-matched config read that never closed on exactly one declared key. + +Always ``[]`` on codeanalyzer-java 3.0.1: there is no config-read detector, so there is +nothing to have failed to resolve. + +##### `JavaAnalysis.get_config_readers` + +```python +get_config_readers(key: str) -> List[JCallableOverview] +``` + +Return overviews of every callable reading configuration key ``key``. + +Always ``[]`` for the same reason `get_config_uses` is: with no code-to-config edges +on the Java wire there is no edge to resolve to a reading callable. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | `str` | The bare configuration key, matched as `get_config_uses` matches it. | + +##### `JavaAnalysis.get_interfaces` + +```python +get_interfaces() -> Dict[str, JType] +``` + +Return every interface in the project, keyed by qualified name. + +The ``kind``-filtered siblings of `get_classes`, sharing TypeScript's names for the +same concepts (G3). Measured: 3 interfaces in daytrader8, 594 in ThingsBoard. + +##### `JavaAnalysis.get_enums` + +```python +get_enums() -> Dict[str, JType] +``` + +Return every enum in the project, keyed by qualified name (192 in ThingsBoard; daytrader8 +declares none). + +##### `JavaAnalysis.get_enum_members` + +```python +get_enum_members(qualified_enum_name: str) -> List[JEnumConstant] +``` + +Return the constants declared by one enum. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `qualified_enum_name` | `str` | The enum's qualified name, as `get_enums` keys it. | + +**Raises:** + +- `SelectorNotInGraph`: The name is not an enum of this application, no type at all, or a type of another kind. An empty list means an enum that declares no constant, which is a different answer (D7). + +##### `JavaAnalysis.get_records` + +```python +get_records() -> Dict[str, JType] +``` + +Return every record in the project, keyed by qualified name, the one Java-only type kind +(35 in ThingsBoard; daytrader8 declares none). Annotation types have no leaf accessor of +their own and stay reachable through `get_classes` (J-7). + +## Schema + +Java schema models, a pydantic mirror of ``codeanalyzer-java/src/main/java/com/ibm/cldk/schema`` +at the 3.0.1 floor, schema v2 (the pin itself is ``[tool.backend-versions]`` in ``pyproject.toml``). + +The wire is one containment tree: ``JAnalysis{analyzer, application}`` → +``JApplication{symbol_table{path → JCompilationUnit}, call_graph, param_in, param_out, artifacts, +dependencies, config_uses, config_reads_unresolved, entrypoint_report}`` → +``JCompilationUnit{types{name → JType}}`` → ``JType{fields{}, callables{signature → +JCallable}, types{}}`` → ``JCallable{body{}, cfg, cdg, ddg, summary, types{}}``. Every node carries a +``can://`` ``id`` and a ``kind``; a unit carries its full ``source`` once and every node's text is a +slice of it. Gson omits ``null`` fields, so an absent key is a ``None``/empty default here. + +What the 1.x models exposed as stored fields is kept as **properties** where the wire still has the +fact in another shape (J-8): ``code`` over ``span`` + ``source``, ``call_sites`` over the ``call`` +body nodes, ``thrown_exceptions`` over ``error_channel``, ``cyclomatic_complexity`` over ``metrics``, +``variable_declarations`` over ``local_variables``, ``referenced_types``/``accessed_fields`` over +``refs``, the ``is_*`` type predicates over ``kind`` and the owner chain. What the wire does not +carry (CRUD) is an empty list, and the facade raises for it (J-4). + +``extra="forbid"`` is intentional: drift between the analyzer's JSON and these models fails loudly. + +### `JSpan` + +```python +class JSpan(_Base) +``` + +``start``/``end`` are ``[line, column]`` (1-based); ``bytes`` are ``[from, to]`` **UTF-8 byte** +offsets into the owning unit's ``source`` (the analyzer's ``Spans.java`` computes them as prefix +sums over ``getBytes(UTF_8)``). + +**``-1`` in any position means "not known", never zero.** A span rebuilt from the Neo4j +projection carries real lines and ``-1`` for both columns and both byte offsets, because the +graph writes ``start_line``/``end_line`` and nothing else -- a ``0`` there would read as column +one, offset zero, which is a position, and a wrong one. Off ``analysis.json`` every position is +the analyzer's own. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `start` | `Tuple[int, int]` | | +| `end` | `Tuple[int, int]` | | +| `bytes` | `Tuple[int, int]` | | + +### `JComment` + +```python +class JComment(_Spanned) +``` + +A comment or Javadoc block. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `content` | `str` | | +| `is_javadoc` | `bool` | | + +### `JImport` + +```python +class JImport(_Spanned) +``` + +An import declaration: ``name`` is the imported simple name, ``path`` the fully-qualified target. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `path` | `str` | | +| `is_static` | `bool` | | +| `is_wildcard` | `bool` | | + +### `JDecorator` + +```python +class JDecorator(_Spanned) +``` + +An annotation use; ``args`` are the source spellings of its arguments (``name="accountejb"``). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `args` | `List[str]` | | + +### `JTypeParameter` + +```python +class JTypeParameter(_Spanned) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `bounds` | `List[str]` | | +| `decorators` | `List[JDecorator]` | | + +### `JEnumConstant` + +```python +class JEnumConstant(_Spanned) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `arguments` | `List[str]` | | +| `comments` | `List[JComment]` | | +| `decorators` | `List[JDecorator]` | | + +### `JRecordComponent` + +```python +class JRecordComponent(_Spanned) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `type` | `str` | | +| `modifiers` | `List[str]` | | +| `decorators` | `List[JDecorator]` | | +| `comments` | `List[JComment]` | | +| `is_variadic` | `bool` | | +| `annotations` | `List[str]` | | +| `comment` | `Optional[JComment]` | | +| `is_var_args` | `bool` | | + +### `JCallableParameter` + +```python +class JCallableParameter(_Spanned) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `Optional[str]` | | +| `type` | `str` | | +| `modifiers` | `List[str]` | | +| `decorators` | `List[JDecorator]` | | +| `is_variadic` | `bool` | | +| `annotations` | `List[str]` | | + +### `JLocalVariable` + +```python +class JLocalVariable(_Spanned) +``` + +A local variable declaration inside a callable (the 1.x ``JVariableDeclaration``). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `type` | `str` | | +| `initializer` | `Optional[str]` | | +| `comments` | `List[JComment]` | | +| `comment` | `Optional[JComment]` | | + +### `JField` + +```python +class JField(_Node) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `kind` | `Literal['field']` | | +| `name` | `str` | | +| `type` | `str` | | +| `modifiers` | `List[str]` | | +| `comments` | `List[JComment]` | | +| `decorators` | `List[JDecorator]` | | +| `initializer` | `Optional[str]` | | +| `annotations` | `List[str]` | | +| `variables` | `List[str]` | | +| `variable_initializers` | `Optional[Dict[str, str]]` | | +| `comment` | `Optional[JComment]` | | + +### `JMetrics` + +```python +class JMetrics(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cyclomatic` | `int` | | + +### `JRefs` + +```python +class JRefs(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `types` | `List[str]` | | +| `fields` | `List[str]` | | + +### `JBodyNode` + +```python +class JBodyNode(_Spanned) +``` + +One entry of a callable's ``body{}`` map, keyed ``L:C``, ``@entry``/``@exit``/``@formal_in:N``/ +``@formal_out`` or ``L:C/actual_in:N``/``L:C/actual_out``. Every attribute is optional: the +analyzer writes the empty call-shaped fields on non-call nodes too. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `kind` | `str` | | +| `callee` | `Optional[str]` | | +| `arguments` | `List[str]` | | +| `receiver_expr` | `Optional[str]` | | +| `receiver_type` | `Optional[str]` | | +| `argument_types` | `List[str]` | | +| `argument_expr` | `List[str]` | | +| `callee_signature` | `Optional[str]` | | +| `method_name` | `Optional[str]` | | +| `return_type` | `Optional[str]` | | +| `accessibility` | `Optional[str]` | | +| `comment` | `Optional[JComment]` | | +| `is_static_call` | `Optional[bool]` | | +| `is_constructor_call` | `bool` | | +| `of` | `Optional[str]` | | +| `parent` | `Optional[str]` | | + +### `JCfgEdge` + +```python +class JCfgEdge(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `kind` | `str` | | + +### `JCdgEdge` + +```python +class JCdgEdge(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | + +### `JDdgEdge` + +```python +class JDdgEdge(_Base) +``` #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `method_declaration` | `str` | | -| `klass` | `str` | | -| `method` | `JCallable` | | +| `src` | `str` | | +| `dst` | `str` | | +| `var` | `Optional[str]` | | +| `prov` | `List[str]` | | + +### `JSummaryEdge` + +```python +class JSummaryEdge(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | -### `JGraphEdgesST` +### `JCRUDOperation` ```python -class JGraphEdgesST(BaseModel) +class JCRUDOperation(_Base) ``` -Represents an edge in a graph structure for method dependencies. +Not emitted by codeanalyzer-java 3.0.1 (upstream #187); kept for import compatibility. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `source` | `JMethodDetail` | | -| `target` | `JMethodDetail` | | -| `type` | `str` | | -| `weight` | `str` | | -| `source_kind` | `str \| None` | | -| `destination_kind` | `str \| None` | | +| `line_number` | `int` | | +| `operation_type` | `Optional[CRUDOperationType]` | | -### `JGraphEdges` +### `JCRUDQuery` ```python -class JGraphEdges(BaseModel) +class JCRUDQuery(_Base) ``` +Not emitted by codeanalyzer-java 3.0.1 (upstream #187); kept for import compatibility. + #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `source` | `JMethodDetail` | | -| `target` | `JMethodDetail` | | -| `type` | `str` | | -| `weight` | `str` | | -| `source_kind` | `str \| None` | | -| `destination_kind` | `str \| None` | | +| `line_number` | `int` | | +| `query_arguments` | `Optional[List[str]]` | | +| `query_type` | `Optional[CRUDQueryType]` | | + +### `JCallSite` + +```python +class JCallSite(_Base) +``` + +The 1.x per-call record, built on demand from a ``call`` body node (`from_body_node`). +The four visibility booleans derive from ``accessibility`` and are ``None`` when the callee was +not resolved. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `comment` | `Optional[JComment]` | | +| `method_name` | `str` | | +| `receiver_expr` | `str` | | +| `receiver_type` | `str` | | +| `argument_types` | `List[str]` | | +| `argument_expr` | `List[str]` | | +| `return_type` | `str` | | +| `callee_signature` | `str` | | +| `is_static_call` | `Optional[bool]` | | +| `is_private` | `Optional[bool]` | | +| `is_public` | `Optional[bool]` | | +| `is_protected` | `Optional[bool]` | | +| `is_unspecified` | `Optional[bool]` | | +| `is_constructor_call` | `bool` | | +| `crud_operation` | `Optional[JCRUDOperation]` | | +| `crud_query` | `Optional[JCRUDQuery]` | | +| `start_line` | `int` | | +| `start_column` | `int` | | +| `end_line` | `int` | | +| `end_column` | `int` | | + +#### Methods + +##### `JCallSite.from_body_node` + +```python +from_body_node(node: JBodyNode) -> 'JCallSite' +``` + +### `JCallable` + +```python +class JCallable(_Node) +``` + +A method, constructor or initializer (``$N()``). Implicit callables (default +constructors) carry no span, body, parameters, metrics or declaration. + +``cfg``/``cdg``/``ddg`` are present from L3 and ``summary`` from L4 **off ``analysis.json``**, +where ``None`` means the run was below that level and re-running higher fills them in. +**Off the Neo4j projection all four are ``None`` at every level**, and re-ingesting does not +change that: ``--emit neo4j`` already forces L4, and this leg's projection does not rebuild the +per-callable graphs from it (leg 3b reads them on demand). So ``None`` here is "this backend +does not carry it", not "analyse deeper". + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `kind` | `str` | | +| `signature` | `str` | | +| `parameters` | `List[JCallableParameter]` | | +| `return_type` | `Optional[str]` | | +| `error_channel` | `List[str]` | | +| `modifiers` | `List[str]` | | +| `decorators` | `List[JDecorator]` | | +| `type_parameters` | `List[JTypeParameter]` | | +| `body_span` | `Optional[JSpan]` | | +| `declaration` | `Optional[str]` | | +| `is_implicit` | `bool` | | +| `comments` | `List[JComment]` | | +| `is_entrypoint` | `bool` | | +| `entrypoint_frameworks` | `List[str]` | | +| `metrics` | `Optional[JMetrics]` | | +| `refs` | `Optional[JRefs]` | | +| `local_variables` | `List[JLocalVariable]` | | +| `body` | `Dict[str, JBodyNode]` | | +| `cfg` | `Optional[List[JCfgEdge]]` | | +| `cdg` | `Optional[List[JCdgEdge]]` | | +| `ddg` | `Optional[List[JDdgEdge]]` | | +| `summary` | `Optional[List[JSummaryEdge]]` | | +| `types` | `Dict[str, 'JType']` | | +| `code` | `str` | The 1.x ``code``: the **body block** (``body_span``), which is what ``code_start_line`` | +| `code_start_line` | `int` | The file line :attr:`code` starts on: the body block's first line, or the declaration's | +| `annotations` | `List[str]` | | +| `thrown_exceptions` | `List[str]` | | +| `cyclomatic_complexity` | `Optional[int]` | | +| `variable_declarations` | `List[JLocalVariable]` | | +| `referenced_types` | `List[str]` | | +| `accessed_fields` | `List[str]` | | +| `call_sites` | `List[JCallSite]` | | +| `is_constructor` | `bool` | | +| `is_static` | `bool` | | +| `crud_operations` | `List[JCRUDOperation]` | | +| `crud_queries` | `List[JCRUDQuery]` | | + +### `JType` + +```python +class JType(_Node) +``` + +A class, interface, enum, annotation or record. The wire has no ``name``: the map key is the +simple name and the id's last segment; `name` is stamped from the key (J-13). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `kind` | `Literal['class', 'interface', 'enum', 'annotation', 'record']` | | +| `span` | `JSpan` | | +| `comments` | `List[JComment]` | | +| `modifiers` | `List[str]` | | +| `base_types` | `List[str]` | | +| `interfaces` | `List[str]` | | +| `decorators` | `List[JDecorator]` | | +| `type_parameters` | `List[JTypeParameter]` | | +| `is_entrypoint_class` | `bool` | | +| `entrypoint_frameworks` | `List[str]` | | +| `enum_constants` | `List[JEnumConstant]` | | +| `record_components` | `List[JRecordComponent]` | | +| `fields` | `Dict[str, JField]` | | +| `callables` | `Dict[str, JCallable]` | | +| `types` | `Dict[str, 'JType']` | | +| `name` | `str` | | +| `qualified_name` | `str` | ``package.Outer.Inner`` for a member type, the source spelling, nested types joined | +| `is_interface` | `bool` | | +| `is_nested_type` | `bool` | | +| `is_local_class` | `bool` | | +| `is_inner_class` | `bool` | | +| `is_class_or_interface_declaration` | `bool` | | +| `is_enum_declaration` | `bool` | | +| `is_annotation_declaration` | `bool` | | +| `is_record_declaration` | `bool` | | +| `is_concrete_class` | `bool` | | +| `extends_list` | `List[str]` | | +| `implements_list` | `List[str]` | | +| `annotations` | `List[str]` | | +| `parent_type` | `str` | Qualified name of the enclosing type, ``""`` at top level (the 1.x value). | +| `field_declarations` | `List[JField]` | | +| `callable_declarations` | `Dict[str, JCallable]` | | +| `nested_type_declarations` | `List[str]` | Qualified names of the member types (the 1.x value; both backends feed them to ``get_class``). | +| `initialization_blocks` | `List[JCallable]` | | + +### `JCompilationUnit` + +```python +class JCompilationUnit(_Node) +``` + +One ``.java`` file. The symbol-table key is its repo-relative path (`file_path`); the +wire carries no ``file_path``/``package_name``. The wire key ``imports`` holds structured +`JImport` records, exposed as `import_declarations`; the 1.x ``imports`` (a list of +paths) is the property of that name. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `model_config` | `` | | +| `kind` | `Literal['module']` | | +| `span` | `JSpan` | | +| `package` | `str` | | +| `source` | `str` | | +| `comments` | `List[JComment]` | | +| `import_declarations` | `List[JImport]` | | +| `types` | `Dict[str, JType]` | | +| `content_hash` | `Optional[str]` | | +| `file_path` | `str` | | +| `package_name` | `str` | | +| `imports` | `List[str]` | | +| `type_declarations` | `Dict[str, JType]` | | +| `is_modified` | `bool` | Always ``False``: the analyzer emits a snapshot, never an edit state. | +| `code` | `str` | | #### Methods -##### `JGraphEdges.validate_source` +##### `JCompilationUnit.model_post_init` + +```python +model_post_init(__context: Any) -> None +``` + +##### `JCompilationUnit.slice` ```python -validate_source(value) -> JMethodDetail +slice(span: JSpan) -> str ``` +``source`` between the span's UTF-8 byte offsets (J-15); a plain index when the file is ASCII. + +### `JCallGraphEdge` + +```python +class JCallGraphEdge(_Base) +``` + +A wire call-graph edge: ``can://`` endpoints, provenance tokens ``declared`` / ``rta``. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `prov` | `List[str]` | | +| `weight` | `int` | | + +### `JParamEdge` + +```python +class JParamEdge(_Base) +``` + +An L4 ``param_in``/``param_out`` edge with global endpoints +(``@L:C/actual_in:N`` → ``@formal_in:N``). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | + +### `JExternalSymbol` + +```python +class JExternalSymbol(_Base) +``` + +A call target outside the project, keyed by its ``@external/…`` id on the application. +Declared in the analyzer's schema; not emitted for daytrader8 at any level. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `kind` | `str` | | +| `signature` | `str` | | +| `declaring_type` | `Optional[str]` | | + +### `JConfigKey` + +```python +class JConfigKey(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `key` | `str` | | +| `namespace` | `str` | | +| `value` | `Optional[str]` | | +| `span` | `Optional[JSpan]` | | +| `references` | `List[str]` | | + +### `JArtifact` + +```python +class JArtifact(_Base) +``` + +A recognized non-code file (config, manifest, build descriptor). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `Literal['artifact']` | | +| `path` | `str` | | +| `format` | `str` | | +| `roles` | `List[str]` | | +| `size_bytes` | `int` | | +| `sha256` | `str` | | +| `source` | `str` | | +| `text_truncated` | `bool` | | +| `extraction` | `str` | | +| `config_keys` | `List[JConfigKey]` | | + +### `JDependency` + +```python +class JDependency(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `group` | `Optional[str]` | | +| `name` | `str` | | +| `ecosystem` | `str` | | +| `spec` | `str` | | +| `kind` | `str` | | +| `extras` | `List[str]` | | +| `declared_in` | `str` | | +| `direct` | `bool` | | +| `locked_version` | `Optional[str]` | | +| `prov` | `List[str]` | | + +### `JEntrypointReport` + +```python +class JEntrypointReport(_Base) +``` + +Coverage and failure record for the entrypoint pass (codeanalyzer-java#235, 3.1.0). + +The same four fields Python and TypeScript already carry (``PyEntrypointReport`` / +``TSEntrypointReport``), so `EntrypointCoverage` reads +one shape across the three languages. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `frameworks_detected` | `List[str]` | | +| `rulesets` | `List[str]` | | +| `unresolved` | `Dict[str, int]` | | +| `errors` | `List[str]` | | + +### `JConfigUse` + +```python +class JConfigUse(_Base) +``` + +One code-to-config edge: a config read whose key closed on a declared `JConfigKey`. + +``src`` is the reading call's body-node id (``@:``), ``dst`` the matched +key's id. ``prov`` names the **tier** that resolved it and is the reason this is not flattened: +``["literal"]`` is a string literal at the call site (codeanalyzer-java#233); ``"dataflow"`` +means the key was reached over the L3 DDG / L4 call graph (codeanalyzer-java#237), weaker +evidence, and monotone with the analysis level. Python's ``PyConfigUseEdge`` verbatim. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `prov` | `List[str]` | | + +### `JConfigRead` + +```python +class JConfigRead(_Base) +``` + +One config read whose key closed on no declared key, first class, so a read nobody can +trace stays as visible as one that resolves. + +``key`` is the literal text when it *was* a literal that matches no declared key +(``reason="undefined-key"``) and ``None`` when it never closed on one (``reason="non-literal"``); +``prov`` lists every tier that was **attempted** before giving up, so ``["literal", "dataflow"]`` +means the dataflow tier ran too and still could not name the key. Python's ``PyConfigRead`` +verbatim. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `site` | `str` | | +| `callee` | `str` | | +| `key` | `Optional[str]` | | +| `reason` | `str` | | +| `prov` | `List[str]` | | + ### `JApplication` ```python -class JApplication(BaseModel) +class JApplication(_Base) ``` -Represents a Java application. +The application root. ``call_graph``/``param_in``/``param_out`` are absent below the level +that computes them, empty here, never ``None``. -Parameters ----------- -symbol_table : List[JCompilationUnit] - The symbol table representation -system_dependency : List[JGraphEdges] - The edges of the system dependency graph. Default None. +That reading is exact for ``call_graph`` on both backends. ``param_in``/``param_out`` are +**always empty off the Neo4j projection**, at every level: ``--emit neo4j`` forces L4, so the +dataflow overlay was computed, but this leg does not project the port lattice back out (leg 3b +does). An empty pair there is this backend's silence, not the analyzer's. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `Literal['application']` | | | `symbol_table` | `Dict[str, JCompilationUnit]` | | -| `call_graph` | `List[JGraphEdges]` | | -| `system_dependency_graph` | `List[JGraphEdges]` | | +| `call_graph` | `List[JCallGraphEdge]` | | +| `external_symbols` | `Optional[Dict[str, JExternalSymbol]]` | | +| `param_in` | `List[JParamEdge]` | | +| `param_out` | `List[JParamEdge]` | | +| `artifacts` | `Dict[str, JArtifact]` | | +| `dependencies` | `List[JDependency]` | | +| `config_uses` | `Optional[List[JConfigUse]]` | | +| `config_reads_unresolved` | `Optional[List[JConfigRead]]` | | +| `entrypoint_report` | `Optional[JEntrypointReport]` | | #### Methods -##### `JApplication.validate_source` +##### `JApplication.model_post_init` + +```python +model_post_init(__context: Any) -> None +``` + +### `JAnalyzer` + +```python +class JAnalyzer(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `version` | `str` | | + +### `JAnalysis` + +```python +class JAnalysis(_Base) +``` + +The envelope ``analysis.json`` IS. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `schema_version` | `str` | | +| `language` | `str` | | +| `max_level` | `int` | | +| `k_limit` | `Optional[int]` | | +| `analyzer` | `JAnalyzer` | | +| `application` | `JApplication` | | + +### `JMethodDetail` ```python -validate_source(symbol_table) -> Dict[str, JCompilationUnit] +class JMethodDetail(_Base) ``` +The ``method_detail`` node attribute of ``get_call_graph()``: built from the string node key +(``klass`` = everything before the signature's simple name) and the resolved callable. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `method_declaration` | `Optional[str]` | | +| `klass` | `str` | | +| `method` | `JCallable` | | + diff --git a/src/content/docs/reference/python-api/python.md b/src/content/docs/reference/python-api/python.md index 79c1bd1..84186a3 100644 --- a/src/content/docs/reference/python-api/python.md +++ b/src/content/docs/reference/python-api/python.md @@ -123,9 +123,9 @@ follows. -[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 1.4.0](https://img.shields.io/badge/cldk-1.4.0-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/1.4.0/) +[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 2.0.0rc3](https://img.shields.io/badge/cldk-2.0.0rc3-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/2.0.0rc3/) -_API reference generated from cldk 1.4.0._ +_API reference generated from cldk 2.0.0rc3._ ## Analysis @@ -195,6 +195,7 @@ regeneration of all analysis artifacts. | `target_files` | `` | | | `treesitter_python` | `TreesitterPython` | | | `backend` | `PythonAnalysisBackend` | | +| `has_resolution_edges` | `bool` | Whether :meth:`get_callsites_for` can resolve call sites on this backend right now. | #### Methods @@ -280,7 +281,7 @@ classes, functions, and their relationships discovered during analysis. ##### `PythonAnalysis.get_symbol_table` ```python -get_symbol_table() -> Dict[str, PyModule] +get_symbol_table(paths: Sequence[str] | None = None) -> Dict[str, PyModule] ``` Return the symbol table mapping file paths to module objects. @@ -289,6 +290,12 @@ Returns a dictionary that maps each analyzed file's path to its corresponding `PyModule` object. This is useful for looking up module information when you know the file path. +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `paths` | `Sequence[str] \| None` | Restrict the result to these modules, named by symbol-table key (the module's file path). Absolute paths and native separators are accepted; a path naming no module raises rather than contributing nothing. ``None`` (the default) returns the whole application, on a large graph that is thousands of modules, so prefer naming the ones you need. | + **Returns:** - `Dict[str, PyModule]`: A dictionary where keys are file paths (as strings) and values are @@ -296,6 +303,12 @@ up module information when you know the file path. - `Dict[str, PyModule]`: analyzed structure of each file, including classes, functions, - `Dict[str, PyModule]`: imports, and other symbols. +**Raises:** + +- `TypeError`: ``paths`` is a bare string. It takes a *sequence* of paths, a string is a sequence of characters, and iterating it is never what you meant. +- `ValueError`: ``paths`` is an empty sequence. Omit the keyword to enumerate everything; the argument that means "the whole application" is the argument not passed. +- `SelectorNotInGraph`: a path names no module in this application (``cldk.utils.exceptions``, a ``ValueError``). A partial miss raises too, so a short result can never be read as a complete one. + > **See Also** > `get_python_module`: For direct lookup by file path. > `get_modules`: For a flat list without file paths. @@ -403,7 +416,7 @@ dependencies. ##### `PythonAnalysis.get_call_graph` ```python -get_call_graph() -> nx.DiGraph +get_call_graph(roots: Sequence[str] | None = None, depth: int | None = None) -> nx.DiGraph ``` Return the project call graph as a NetworkX directed graph. @@ -413,6 +426,21 @@ call relationships across the entire project. Each node represents a callable (function or method), and each edge represents a call from one callable to another. +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `roots` | `Sequence[str] \| None` | Restrict the result to the sub-graph reachable from these callables, named by signature. ``None`` (the default) returns the whole application's call graph. | +| `depth` | `int \| None` | Maximum number of call hops from a root, an ``int`` >= 1; ``None`` is unbounded. Requires ``roots``. | + +The unscoped graph on a real application runs to hundreds of thousands of edges, which is +not an answer to a question about one function, ``roots=`` and ``depth=`` are how you ask +the question you actually have. The result is the *induced* sub-graph over the reached +nodes, so an edge between two nodes you can see is never silently absent, and a root that +calls nothing is a graph of one node rather than an empty one. A root the graph does not +hold raises `SelectorNotInGraph` instead of quietly +contributing nothing. + > **The call graph is built using** > - Jedi for semantic call resolution > - PyCG for inter-procedural call-graph construction @@ -598,7 +626,8 @@ Return source bodies for the given callable signatures, in one bulk read. **Returns:** - `Dict[str, str]`: A dict mapping each signature to its source body. Signatures with no matching callable -- `Dict[str, str]`: are omitted. +- `Dict[str, str]`: are omitted, as are callables whose ``code`` is ``None``, every returned value is a +- `Dict[str, str]`: real ``str``. ##### `PythonAnalysis.get_decorated_callables` @@ -622,6 +651,75 @@ Return overviews of callables decorated with any of the given markers, in one bu > **See Also** > `get_callables_overview`: The unfiltered projection. +##### `PythonAnalysis.get_entrypoints` + +```python +get_entrypoints() -> List[PyCallableOverview] +``` + +Return overviews of every callable the analyzer marked as an entrypoint, in one bulk read. + +The analyzer's own entrypoint-detection pass already finds route handlers, CLI commands, +and other externally-invoked callables (``PyCallable.is_entrypoint``); this just surfaces +that mark instead of making a caller rediscover it (e.g. by sharding +`get_callables_overview` across workers to guess which callables are reachable from +outside the application). + +**Returns:** + +- `List[PyCallableOverview]`: A list of `PyCallableOverview` for every entrypoint +- `List[PyCallableOverview]`: callable. Empty means the project genuinely has none, not that the graph lacks the mark. + +> **See Also** +> `get_callables_overview`: The unfiltered projection. +> `get_decorated_callables`: The same projection filtered by decorator instead. +> `get_entrypoint_classes`: The class-level sibling this walk never sees. +> `get_entrypoint_coverage`: Whether the detection pass itself had gaps. + +##### `PythonAnalysis.get_entrypoint_classes` + +```python +get_entrypoint_classes() -> List[PyClassOverview] +``` + +Return overviews of every class the analyzer marked as an entrypoint in its own right, +in one bulk read. + +`get_entrypoints` walks callables only, so a class-based view (a Django/Flask CBV, +say) marked ``is_entrypoint`` at the class with no individually-marked method is invisible +to it. This is that sibling. + +**Returns:** + +- `List[PyClassOverview]`: A list of `PyClassOverview` for every entrypoint class. +- `List[PyClassOverview]`: Empty means the project genuinely has none, not that the graph lacks the mark. + +> **See Also** +> `get_entrypoints`: The callable-level projection. + +##### `PythonAnalysis.get_entrypoint_coverage` + +```python +get_entrypoint_coverage() -> EntrypointCoverage +``` + +Return the entrypoint-detection pass's own coverage/failure record, in one bulk read. + +The analyzer's detection pass "under-approximates by design, so silence is its failure +mode" (its own ``PyEntrypointReport`` docstring); `get_entrypoints` returning ``[]`` +cannot, on its own, distinguish "ran clean, found none" from "had gaps". This can. + +**Returns:** + +- `EntrypointCoverage`: class:`~cldk.analysis.commons.results.EntrypointCoverage`. Non-empty +- `EntrypointCoverage`: ``diagnostics`` means this backend cannot supply the report at all (a Neo4j graph +- `EntrypointCoverage`: emitted by codeanalyzer-python 1.4.0 does not carry it) rather than the pass having +- `EntrypointCoverage`: run clean, see the model's +- `EntrypointCoverage`: own docstring for the field-by-field contract. + +> **See Also** +> `get_entrypoints`: The accessor whose empty result this disambiguates. + ##### `PythonAnalysis.get_callsites_for` ```python @@ -645,6 +743,29 @@ frontier (e.g. dispatch-edge synthesis or external-reader detection). - `Dict[str, List[PyCallsite]]`: class:`~cldk.models.python.PyCallsite` (empty if the callable has no call sites). - `Dict[str, List[PyCallsite]]`: Signatures with no matching callable are omitted. +> **See Also** +> `has_resolution_edges`: Distinguishes a genuinely unresolved call site from a +> graph with no resolution data at all. + +##### `PythonAnalysis.get_external_symbols` + +```python +get_external_symbols() -> Dict[str, PyExternalSymbol] +``` + +Every call-graph endpoint outside the analyzed project (an imported library or builtin +member), keyed by its ``can://…/@external/…`` id. + +The analyzer mints one of these ghost symbols for every call target that isn't a declared +class/callable, so no call-graph edge dangles; `get_callsites_for`'s resolved +``callee_signature`` for an external target is exactly this dict's key. + +**Returns:** + +- `Dict[str, PyExternalSymbol]`: A dict mapping each ``@external`` can-id to its +- `Dict[str, PyExternalSymbol]`: class:`~cldk.models.python.PyExternalSymbol`. Empty means this project's call graph +- `Dict[str, PyExternalSymbol]`: makes no calls outside itself. + ##### `PythonAnalysis.get_methods_in_class` ```python @@ -669,6 +790,13 @@ methods (like ``__init__``, ``__str__``, etc.). - `Dict[str, PyCallable]`: class:`~cldk.models.python.PyCallable` objects. Returns an - `Dict[str, PyCallable]`: empty dictionary if the class is not found or has no methods. +> **Note** +> Returned callables' call sites are not resolved the way `get_callsites_for` +> resolves them: on the Neo4j backend ``callee_signature`` is always ``None`` here; on +> the local backend an external target keeps Jedi's raw, unaddressable dotted guess +> instead of the resolved ``@external`` can-id. Use `get_callsites_for` for the +> same call sites with resolved signatures. + > **See Also** > `get_method`: For a single method by name. > `get_constructors`: For ``__init__`` methods specifically. @@ -679,23 +807,35 @@ methods (like ``__init__``, ``__str__``, etc.). get_method(qualified_class_name: str, qualified_method_name: str) -> PyCallable | None ``` -Return a specific method by class and method name. +Return a specific method or module-level function by scope and name. Retrieves detailed information about a single method, including its signature, parameters, return type, decorators, and body. +``qualified_class_name`` is looked up the same way as +`get_all_methods_in_application`'s outer keys: a class signature resolves to that +class's methods, and a module name (``PyModule.module_name``) resolves to that module's +top-level functions. + **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The fully qualified name of the class containing the method (e.g., ``"mypackage.models.User"``). | +| `qualified_class_name` | `str` | The fully qualified name of the class containing the method (e.g., ``"mypackage.models.User"``), or a module name for module-level functions. | | `qualified_method_name` | `str` | The name of the method to retrieve (e.g., ``"save"`` or ``"__init__"``). | **Returns:** - `PyCallable \| None`: class:`~cldk.models.python.PyCallable` object containing - `PyCallable \| None`: all analyzed information about the method, or ``None`` if -- `PyCallable \| None`: the method is not found. +- `PyCallable \| None`: neither a matching class nor a matching module resolves. + +> **Note** +> The returned callable's call sites are not resolved the way `get_callsites_for` +> resolves them: on the Neo4j backend ``callee_signature`` is always ``None`` here; on +> the local backend an external target keeps Jedi's raw, unaddressable dotted guess +> instead of the resolved ``@external`` can-id. Use `get_callsites_for` for the +> same call sites with resolved signatures. > **See Also** > `get_methods_in_class`: For all methods of a class. @@ -756,548 +896,990 @@ but this returns a dictionary for API consistency. - `Dict[str, PyCallable]`: class:`~cldk.models.python.PyCallable` objects. Returns an - `Dict[str, PyCallable]`: empty dictionary if the class has no explicit constructor. +> **Note** +> Returned callables' call sites are not resolved the way `get_callsites_for` +> resolves them: on the Neo4j backend ``callee_signature`` is always ``None`` here; on +> the local backend an external target keeps Jedi's raw, unaddressable dotted guess +> instead of the resolved ``@external`` can-id. Use `get_callsites_for` for the +> same call sites with resolved signatures. + > **See Also** > `get_method`: For any method by name. > `get_methods_in_class`: For all methods including constructors. -##### `PythonAnalysis.get_classes` +##### `PythonAnalysis.locate` ```python -get_classes() -> Dict[str, PyClass] +locate(path: str, line: int) -> LocateResult ``` -Return all classes in the project. +Resolve a source position to its enclosing callable, with the source in hand. -Retrieves all class definitions discovered during analysis, organized -by their fully qualified names. This includes regular classes, -dataclasses, abstract base classes, and nested classes. +The single most-needed query for triaging a scanner alert: an alert arrives as +``file:line`` and this resolves it to the enclosing callable in one call, rather than +``get_method``, falling back to ``get_callers``, falling back to scanning the symbol table +by hand. Four outcomes stay distinguishable, see +`LocateResult`: inside a callable (``callable`` set, +plus ``body`` when a body node is that precise), at real module scope (``module_scope`` +diagnostic), in the gap between two callables (also module scope, never snapped to the +nearest callable), or in a file the graph has no module for (``file_not_in_graph``). + +There is no ``col`` parameter. Column-level disambiguation would have to be honoured by +both backends to mean anything, and the Neo4j graph projects only ``start_line`` / +``end_line`` on ``:PyCallable`` and ``:PyBodyNode``, so a ``col`` would work in-process +and be silently ignored over Neo4j. Better absent than documented and inert. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `path` | `str` | The file path. Normalised against the backend's module keys, so a ``./``-prefixed or absolute path resolves rather than reading back as ``file_not_in_graph``. | +| `line` | `int` | The 1-based line number. | **Returns:** -- `Dict[str, PyClass]`: A dictionary mapping fully qualified class names (strings) to -- `Dict[str, PyClass]`: class:`~cldk.models.python.PyClass` objects containing class -- `Dict[str, PyClass]`: metadata, methods, attributes, and inheritance information. +- `LocateResult`: class:`~cldk.analysis.commons.results.LocateResult` carrying the innermost body +- `LocateResult`: node, the enclosing callable, its owning type, its module, and the source slice , +- `LocateResult`: never an ambiguous empty. > **See Also** -> `get_class`: For a single class by name. -> `get_classes_by_criteria`: For filtered class retrieval. +> `locate_many`: The bulk form, the point, not an optimisation. -##### `PythonAnalysis.get_class` +##### `PythonAnalysis.locate_many` ```python -get_class(qualified_class_name: str) -> PyClass | None +locate_many(positions: Sequence[Tuple[str, int]]) -> List[LocateResult] ``` -Return a specific class by its qualified name. - -Retrieves detailed information about a single class, including -its methods, attributes, base classes, and decorators. +Resolve many ``(path, line)`` positions in one round trip, in input order. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The fully qualified name of the class (e.g., ``"mypackage.models.User"``). | +| `positions` | `Sequence[Tuple[str, int]]` | The ``(path, line)`` pairs to resolve, e.g. from a scanner's alert list. | **Returns:** -- `PyClass \| None`: class:`~cldk.models.python.PyClass` object containing all -- `PyClass \| None`: analyzed information about the class, or ``None`` if the class -- `PyClass \| None`: is not found in the analyzed project. +- `List[LocateResult]`: class:`~cldk.analysis.commons.results.LocateResult` per input position, in the +- `List[LocateResult]`: same order. > **See Also** -> `get_classes`: For all classes in the project. -> `get_python_file`: To find which file contains a class. +> `locate`: The single-position form. -##### `PythonAnalysis.get_classes_by_criteria` +##### `PythonAnalysis.resolve_callable` ```python -get_classes_by_criteria(inclusions: List[str] | None = None, exclusions: List[str] | None = None) -> Dict[str, PyClass] +resolve_callable(name: str, in_class: str | None = None, in_module: str | None = None) -> SliceNode ``` -Return classes matching inclusion/exclusion filter criteria. - -Filters the project's classes based on substring matching against -their qualified names. Classes are included if their name contains -any inclusion substring AND does not contain any exclusion substring. - -**Parameters:** +Resolve a callable name to the one callable it names, in the caller's vocabulary. -| Name | Type | Description | -| ---- | ---- | ----------- | -| `inclusions` | `List[str] \| None` | List of substrings that class names must contain to be included. If ``None`` or empty, no inclusion filtering is applied (effectively includes nothing unless you have at least one inclusion pattern). | -| `exclusions` | `List[str] \| None` | List of substrings that class names must NOT contain. Classes matching any exclusion pattern are filtered out, even if they match an inclusion pattern. | +The addressing step every name-taking accessor performs, exposed so a caller can perform it +once and keep the answer:: -**Returns:** + node = py.resolve_callable("invoice_transaction", in_class="PaymentPortal") + node.callable # the full dotted signature -- what get_call_graph(roots=[...]) wants + node.file, node.line -- `Dict[str, PyClass]`: A dictionary mapping qualified class names to -- `Dict[str, PyClass]`: class:`~cldk.models.python.PyClass` objects for classes -- `Dict[str, PyClass]`: matching the criteria. +``name`` matches whole or as a dotted suffix; ``in_class`` is a dotted suffix of the owning +class, ``in_module`` a path (``"controllers/payment.py"``) or a dotted module name +(``"controllers.payment"``). Ambiguity raises with every candidate; nothing is guessed. -> **Note** -> The filtering uses substring matching (``in`` operator), not -> regular expressions or glob patterns. +**Raises:** -> **See Also** -> `get_classes`: For all classes without filtering. +- `AmbiguousName`: More than one callable matched. +- `SelectorNotInGraph`: Nothing matched -- naming the argument that missed. -##### `PythonAnalysis.get_fields` +##### `PythonAnalysis.resolve_value` ```python -get_fields(qualified_class_name: str) -> List[PyClassAttribute] +resolve_value(name: str, within: str) -> SliceNode ``` -Return class-level attributes (fields) for a specific class. - -Retrieves all class attributes defined in the specified class, -including instance attributes, class attributes, and properties. - -**Parameters:** +Resolve a value name inside a callable -- a parameter, a captured global or a closure +capture -- to the position that carries it. -| Name | Type | Description | -| ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The fully qualified name of the class (e.g., ``"mypackage.models.User"``). | +The same resolution ``slice_backward`` / ``flows_to_call`` perform on their ``src``, +exposed so a caller can check what a name means before asking a question of it:: -**Returns:** + py.resolve_value("invoice_id", within="PaymentPortal.invoice_transaction").kind # "parameter" + py.resolve_value("AccessError", within="…invoice_transaction").defined_in # "payment" -- `List[PyClassAttribute]`: A list of `PyClassAttribute` objects, -- `List[PyClassAttribute]`: each containing information about an attribute's name, type -- `List[PyClassAttribute]`: annotation (if present), and default value. +**Raises:** -> **See Also** -> `get_class`: For complete class information. +- `AmbiguousName`: ``within`` named more than one callable, or ``name`` more than one value. +- `SelectorNotInGraph`: No such callable, or no such value in it. -##### `PythonAnalysis.get_nested_classes` +##### `PythonAnalysis.get_source` ```python -get_nested_classes(qualified_class_name: str) -> List[PyClass] +get_source(node_id: str) -> str ``` -Return inner/nested classes defined within a class. +Return the source text named by ``node_id``, a callable, or one of its body nodes. -Retrieves all classes that are defined inside the specified class -(nested class definitions). +Generalises `get_method_bodies` below callable granularity: ``node_id`` is either a +callable's signature, or the opaque body-node id +`node_id` hands back alongside +`body`, so a statement or call site +`locate` found can be re-fetched precisely, not just the callable enclosing it. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The fully qualified name of the outer class (e.g., ``"mypackage.models.Container"``). | +| `node_id` | `str` | A callable signature, or a body-node id from `locate`, passed back as received, not composed. | **Returns:** -- `List[PyClass]`: A list of `PyClass` objects for each -- `List[PyClass]`: nested class. Returns an empty list if no nested classes exist. +- `str`: The source text, never an ambiguous empty string. + +**Raises:** + +- `KeyError`: No callable/body node matches ``node_id``, or it has no recoverable source (no span). +- `NotImplementedError`: (Neo4j backend only) ``node_id`` names a body node, the attached graph carries no source text below callable granularity. > **See Also** -> `get_class`: For the outer class information. +> `get_method_bodies`: The bulk, callable-only, omit-if-absent form. +> `locate`: The usual way to obtain a ``node_id`` in the first place. -##### `PythonAnalysis.get_sub_classes` +##### `PythonAnalysis.get_cfg` ```python -get_sub_classes(qualified_class_name: str) -> Dict[str, PyClass] +get_cfg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[CfgEdge] ``` -Return all classes that inherit from the specified class. +Return one page of the control flow edges inside one callable. -Finds all classes in the project that directly or indirectly extend -the specified base class. This is useful for understanding class -hierarchies and finding implementations of abstract base classes. +The callable is the scope; the page is the size bound. Naming a callable says *which* +edges you want, not *how many* there will be, see `get_ddg`, where one callable's +answer runs to 1.39 million edges on a real application. CFG is the small one (the largest +measured is 402 edges), so this returns a single complete page in practice; it pages +anyway, because three sibling accessors that answer in two different shapes are a trap for +anything composing them. + +``src`` and ``dst`` are body-node ids in the same vocabulary +`node_id` uses, so an endpoint can be +handed straight back to `get_source`. No ``can://`` URI and no ordinal appears in +either the argument or the result. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The fully qualified name of the base class to find subclasses of (e.g., ``"mypackage.base.BaseModel"``). | +| `callable` | `str` | The callable's name, resolved the way `locate` and the addressing layer resolve names, so ``"charge"`` is enough when it is unique and an ambiguous name raises listing the candidates instead of being guessed at. | +| `in_class` | `str \| None` | Narrow to the class this names, when the bare name is ambiguous. | +| `page_size` | `int` | Most edges in the page. Defaults to `DEFAULT_PAGE_SIZE`. | +| `cursor` | `str \| None` | ``next_cursor`` from a previous page, to continue where it left off. ``None`` starts at the beginning. | **Returns:** -- `Dict[str, PyClass]`: A dictionary mapping qualified class names to -- `Dict[str, PyClass]`: class:`~cldk.models.python.PyClass` objects for all classes -- `Dict[str, PyClass]`: that inherit from the specified class. +- `EdgePage[CfgEdge]`: class:`~cldk.analysis.commons.results.EdgePage` of +- `EdgePage[CfgEdge]`: class:`~cldk.models.python.CfgEdge`, each carrying the edge ``kind`` +- `EdgePage[CfgEdge]`: (``"true"``/``"false"`` on a conditional, ``"exception"``, ``"loop_back"``, ...), in +- `EdgePage[CfgEdge]`: the canonical order (source, target, kind) that makes this page the same page on +- `EdgePage[CfgEdge]`: every backend. + +**Raises:** + +- `AmbiguousName`: ``callable`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. +- `ValueError`: ``page_size`` below 1, or ``cursor`` not from a previous page. +- `CodeanalyzerUsageException`: This analysis was built below ``analysis_level="program_dependency_graph"``, where the analyzer emits no control or data flow at all, reported rather than returned as a misleading empty page. > **See Also** -> `get_extended_classes`: For the reverse (what a class extends). -> `get_class_hierarchy`: For the full inheritance graph (not implemented). +> `get_cdg`, `get_ddg`: The other two graphs of the same callable. -##### `PythonAnalysis.get_extended_classes` +##### `PythonAnalysis.get_cdg` ```python -get_extended_classes(qualified_class_name: str) -> List[str] +get_cdg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[CdgEdge] ``` -Return the base class names that a class extends. +Return one page of the control dependence edges inside one callable. -Retrieves the list of parent/base classes for the specified class. -This includes direct base classes from the class definition. +``src`` is the branch a ``dst`` is control dependent on, "this statement runs only +because that test went this way", computed by the analyzer over the CFG `get_cfg` +returns. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The fully qualified name of the class to get base classes for (e.g., ``"mypackage.models.User"``). | +| `callable` | `str` | The callable's name, resolved as in `get_cfg`. | +| `in_class` | `str \| None` | Narrow to the class this names. | +| `page_size` | `int` | Most edges in the page. | +| `cursor` | `str \| None` | ``next_cursor`` from a previous page. | **Returns:** -- `List[str]`: A list of base class names (as strings). These may be qualified -- `List[str]`: or unqualified names depending on how they appear in the source. +- `EdgePage[CdgEdge]`: class:`~cldk.analysis.commons.results.EdgePage` of +- `EdgePage[CdgEdge]`: class:`~cldk.models.python.CdgEdge`, ordered by source then target. The largest CDG +- `EdgePage[CdgEdge]`: measured on a real application is 314 edges, so this is one page in practice. -> **Note** -> Python does not distinguish between classes and interfaces, -> so all base types are returned here. Use this method instead -> of `get_implemented_interfaces`. +**Raises:** -> **See Also** -> `get_sub_classes`: For finding classes that extend this class. +- `AmbiguousName`: ``callable`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. +- `ValueError`: ``page_size`` below 1, or ``cursor`` not from a previous page. +- `CodeanalyzerUsageException`: Analysis level below ``program_dependency_graph``. -##### `PythonAnalysis.get_class_hierarchy` +##### `PythonAnalysis.get_ddg` ```python -get_class_hierarchy() -> nx.DiGraph +get_ddg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[DdgEdge] ``` -Return the complete class inheritance hierarchy as a graph. - -This method is intended to return a NetworkX directed graph representing -the full class inheritance relationships in the project. +Return one page of the data dependence edges inside one callable. -**Returns:** - -- `nx.DiGraph`: Would return a ``networkx.DiGraph`` with classes as nodes and -- `nx.DiGraph`: inheritance edges from subclass to superclass. - -**Raises:** +Every edge names the variable that flows (``var``) and the evidence for it (``prov``), so +a caller separates syntactic dependence from alias-aware dependence without asking a +second question. ``prov`` is one of ``"ssa"``, ``"reaching-defs"`` or ``"points-to"``; +``"points-to"`` is the alias-derived delta that only a level-4 analysis carries, so a +level-3 answer is narrower rather than wrong. -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +The same statement pair appears more than once when it carries several variables or +several kinds of evidence, that is the point, not duplication. -> **See Also** -> `get_sub_classes`: For finding subclasses of a specific class. -> `get_extended_classes`: For finding base classes of a class. - -##### `PythonAnalysis.get_service_entry_point_classes` +**This is the accessor pagination exists for.** Per-callable scoping bounds which edges +you get, not how many: the largest single callable measured on a real application has +1,386,918 DDG edges, 27% of the whole application's 5,134,655, and returning that as one +list is around half a gigabyte of objects. 15,520 of that application's 15,549 callables +have fewer than 10,000, so with the default page size the common case is still one call +and no loop:: -```python -get_service_entry_point_classes(**kwargs) -> Dict[str, PyClass] -``` + page = py.get_ddg("Portal.charge") + page.total # 169, the size of the whole answer, not of this page + page.has_more # False: this is everything -Return classes that serve as service entry points. + while page.has_more: # only the outliers need this + page = py.get_ddg("Portal.charge", cursor=page.next_cursor) -This method is intended to identify classes that act as entry points -for services, such as Flask views, Django views, FastAPI endpoints, -or other framework-specific entry points. +Nothing is discarded to make the page fit: the rest is reachable through +``next_cursor``, and ``total`` says up front how much of it there is. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `**kwargs` | `` | Framework-specific filtering options. | +| `callable` | `str` | The callable's name, resolved as in `get_cfg`. | +| `in_class` | `str \| None` | Narrow to the class this names. | +| `page_size` | `int` | Most edges in the page. Defaults to `DEFAULT_PAGE_SIZE`. | +| `cursor` | `str \| None` | ``next_cursor`` from a previous page. | **Returns:** -- `Dict[str, PyClass]`: Would return a dictionary of class names to `PyClass` objects. +- `EdgePage[DdgEdge]`: class:`~cldk.analysis.commons.results.EdgePage` of +- `EdgePage[DdgEdge]`: class:`~cldk.models.python.DdgEdge`, in the canonical order (source, target, +- `EdgePage[DdgEdge]`: variable, provenance). An empty page whose ``total`` is 0, from a level-3-or-deeper +- `EdgePage[DdgEdge]`: analysis, is an honest answer: this callable has no data dependence. **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +- `AmbiguousName`: ``callable`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. +- `ValueError`: ``page_size`` below 1, or ``cursor`` not from a previous page. +- `CodeanalyzerUsageException`: Analysis level below ``program_dependency_graph``, where an empty page could not be told apart from the honest empty above. -> **See Also** -> `get_entry_point_classes`: Related entry point detection. - -##### `PythonAnalysis.get_service_entry_point_methods` +##### `PythonAnalysis.slice_backward` ```python -get_service_entry_point_methods(**kwargs) -> Dict[str, Dict[str, PyCallable]] +slice_backward(src: str, within: str, depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice ``` -Return methods that serve as service entry points. +Return everything the value ``src`` depends on, its backward slice. + +Address the value the way you would say it out loud: a parameter, a module global the +callable reads, or a name it closed over, scoped by the callable it lives in. No +``can://`` id and no ordinal appears in either the argument or the result:: + + sl = py.slice_backward("invoice_id", within="PaymentPortal.invoice_transaction") + sl.total # how big the whole answer is + sl.truncated # whether you are looking at all of it + sl.resolved # what the names matched, for audit -This method is intended to identify methods decorated with framework- -specific decorators like ``@app.route``, ``@api_view``, etc. +The traversal runs in the database, over data dependence, control dependence, argument +passing, returns and call summaries at once. What comes back is a **set** of positions, +not a path, ``paths_between`` is the accessor that answers "how", because one cone of +10,000 nodes holds millions of distinct paths. + +**The traversal is bounded by default**, to five hops +(`DEFAULT_DEPTH`). Unbounded, this question has only +two answers on a real application and nothing in between: a value in a callable nothing +calls slices back to exactly **one** node: itself, honestly, because nothing feeds it , +while a value in a called one reaches a median of **195,786**, a fifth of the program. +Capping the second kind at ``max_nodes`` would hand you 10,000 arbitrary nodes of a +195,819-node closure; bounding the hops instead answers a narrower question *completely*, +and measured over that distribution no slice at five hops is capped at all. + +So ``truncated`` should normally be ``False`` and ``total`` should normally be the whole +of what you got. When you want the fifth of the program, ask for it: ``depth=None``. +Between the two, any ``depth=`` bounds the traversal and gives a complete slice of a +smaller question, while ``max_nodes`` only ever gives part of the large one. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `**kwargs` | `` | Framework-specific filtering options. | +| `src` | `str` | The value's name. A global may be qualified by its module (``"payment.AccessError"``) when the bare name is ambiguous inside the callable. | +| `within` | `str` | The callable to look inside, a suffix of its dotted signature is enough. Required: a value name has no meaning outside a callable. | +| `depth` | `int \| None` | Most hops from the seed. Defaults to `DEFAULT_DEPTH` (5); ``None`` for the whole cone. | +| `max_nodes` | `int` | Most nodes in the result. Defaults to `DEFAULT_MAX_NODES`. | **Returns:** -- `Dict[str, Dict[str, PyCallable]]`: Would return a nested dictionary of class names to method names -- `Dict[str, Dict[str, PyCallable]]`: class:`PyCallable` objects. +- `Slice`: class:`~cldk.analysis.commons.results.Slice` containing the seed, ordered by an +- `Slice`: opaque node id, with ``source`` left unhydrated, pass the nodes you care about to +- `Slice`: ``describe()`` when you want to read them. **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +- `AmbiguousName`: ``within`` matched more than one callable, or ``src`` more than one value inside it. The error carries every candidate; nothing is guessed. +- `SelectorNotInGraph`: No such callable, or no such value in it. +- `ValueError`: ``depth`` is not a positive ``int``, or ``max_nodes`` is below 1. +- `CodeanalyzerUsageException`: This analysis was built below ``analysis_level="program_dependency_graph"``, where there is no dataflow to slice. > **See Also** -> `get_methods_with_decorators`: For finding decorated methods. +> `slice_forward`: The same question the other way round. +> `get_ddg`: One callable's data dependence, without traversal. -##### `PythonAnalysis.get_entry_point_classes` +##### `PythonAnalysis.slice_forward` ```python -get_entry_point_classes() -> Dict[str, PyClass] +slice_forward(src: str, within: str, depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice ``` -Return classes identified as application entry points. - -This method is intended to identify main application classes, -CLI entry points, and other classes that serve as program starting -points. - -**Returns:** +Return everything the value ``src`` can affect, its forward slice. -- `Dict[str, PyClass]`: Would return a dictionary of class names to `PyClass` objects. +The taint direction, and usually the informative one for a value entering a callable: +nothing flows *into* a parameter except from its callers, so `slice_backward` from +one is often the seed alone, while this follows it through the body and out through every +call it feeds:: -**Raises:** + sl = py.slice_forward("invoice_id", within="PaymentPortal.invoice_transaction") + [n for n in sl.nodes if n.kind == "argument"] # where it is passed on -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +Arguments, bounds and failures are `slice_backward`'s, including the five-hop +default. Forward cones are the larger of the two, measured *unbounded*, p95 440,270 nodes +of 885,218 on a real application, so ``depth=None`` is the more expensive request here. -##### `PythonAnalysis.get_entry_point_methods` +##### `PythonAnalysis.reaches` ```python -get_entry_point_methods() -> Dict[str, Dict[str, PyCallable]] +reaches(src: str, dst: str, depth: int | None = None) -> bool ``` -Return methods identified as application entry points. +Return whether there is a call path from ``src`` to ``dst``. + +The cheap question to ask before the expensive one: a boolean, computed in the database as +a bounded search, so "is this sink reachable at all" costs no more than it has to. When +the answer is yes and you need the chain, that is ``call_paths_between``. + +**Parameters:** -This method is intended to identify main functions, CLI commands, -and other methods that serve as program starting points. +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The calling callable's name, a dotted suffix is enough when it is unique. | +| `dst` | `str` | The called callable's name. | +| `depth` | `int \| None` | Most call hops; ``None`` (the default) for any distance. Unlike the slices, this one is unbounded by default: a hop budget on a boolean would report "no path" for a path that is merely long, and the unbounded call is cheap anyway (measured 20ms mean, 112ms worst over 200 random pairs). | **Returns:** -- `Dict[str, Dict[str, PyCallable]]`: Would return a nested dictionary of class names to method names -- `Dict[str, Dict[str, PyCallable]]`: class:`PyCallable` objects. +- `bool`: ``True`` when a call path exists. Self-reachability is ``True`` only through a real +- `bool`: ``reaches(x, x)`` is not vacuously true. **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +- `AmbiguousName`: Either name matched more than one callable. +- `SelectorNotInGraph`: Either name matched none. +- `ValueError`: ``depth`` is not a positive ``int``. -##### `PythonAnalysis.get_implemented_interfaces` +##### `PythonAnalysis.backward_cone` ```python -get_implemented_interfaces(qualified_class_name: str) -> List[str] +backward_cone(sinks: Sequence[str], depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice ``` -Return interfaces implemented by a class. +Return every callable that can reach any of ``sinks``, "what could get here". + +A call-graph cone, so its nodes are callables rather than positions inside them. The sinks +are in the result, and a sink nothing calls comes back as its own one-node cone rather +than as an empty answer that could not be told from a name that matched nothing:: -This method exists for API parity with Java analysis. In Python, -there is no syntactic distinction between classes and interfaces; -abstract base classes (ABCs) and protocols serve similar purposes -but are syntactically identical to regular classes. + cone = py.backward_cone(["AccountMove.write"]) + cone.total # within five call hops, the default + py.backward_cone([...], depth=None) # the whole cone: 9,282 for every .write **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `qualified_class_name` | `str` | The class to query. | +| `sinks` | `Sequence[str]` | The callables to walk back from. A bare string is refused, pass ``["name"]`` to walk back from just one, and an empty sequence is refused too, because "everything" is the argument omitted and there is no everything here. | +| `depth` | `int \| None` | Most call hops back. Defaults to `DEFAULT_DEPTH` (5), for one rule across the three traversals; ``None`` for the whole cone. A cone is smaller than a slice, the largest measured is 9,346 callables, under ``max_nodes``, so here the default buys interpretability rather than protection from truncation. | +| `max_nodes` | `int` | Most nodes in the result; a cap that fires is reported by ``truncated`` and quantified by ``total``. | **Raises:** -- `NotImplementedError`: Always raised. Use `get_extended_classes` instead to get all base classes, which may include ABCs or Protocol classes. +- `AmbiguousName`: A sink name matched more than one callable. +- `SelectorNotInGraph`: A sink name matched none. +- `TypeError`: ``sinks`` is a bare string. +- `ValueError`: ``sinks`` is empty, ``depth`` is not a positive ``int``, or ``max_nodes`` is below 1. -> **See Also** -> `get_extended_classes`: The correct method for Python -> to get parent classes including abstract base classes. - -##### `PythonAnalysis.get_methods_with_decorators` +##### `PythonAnalysis.callers_of` ```python -get_methods_with_decorators(decorators: List[str]) -> Dict[str, List[Dict]] +callers_of(name: str, in_class: str | None = None, in_module: str | None = None) -> List[SliceNode] ``` -Return methods decorated with specific decorators. +Return the callables that call ``name``, addressed by name. -This method is intended to find all methods that have any of the -specified decorators applied, such as ``@property``, ``@staticmethod``, -``@classmethod``, or custom decorators. +The name-based sibling of `get_all_callers`: that one takes a class signature plus a +method name and returns raw dicts, this one takes a name you already have and returns the +same `SliceNode` shape everything else in this +surface speaks, so going from "who calls this" to a slice needs no translation. + +An empty list is unambiguous, a name matching nothing raises, so ``[]`` means "nothing +calls it". **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `decorators` | `List[str]` | List of decorator names to search for (e.g., ``["property", "staticmethod", "app.route"]``). | - -**Returns:** - -- `Dict[str, List[Dict]]`: Would return a dictionary mapping decorator names to lists of -- `Dict[str, List[Dict]]`: method information dictionaries. +| `name` | `str` | The callable's name, whole or a dotted suffix of its signature. | +| `in_class` | `str \| None` | Disambiguate by owning class. | +| `in_module` | `str \| None` | Disambiguate by module. | **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. - -> **See Also** -> `get_methods`: To manually filter methods by decorators. +- `AmbiguousName`: ``name`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. -##### `PythonAnalysis.get_test_methods` +##### `PythonAnalysis.callees_of` ```python -get_test_methods() -> Dict[str, str] +callees_of(name: str, in_class: str | None = None, in_module: str | None = None) -> List[SliceNode] ``` -Return methods identified as test methods. +Return what ``name`` calls, addressed by name, **including calls out of the project**. -This method is intended to find all test methods in the project, -typically methods starting with ``test_`` or decorated with -``@pytest.mark`` or similar test framework decorators. +An external callee comes back with ``kind="external"`` and a readable dotted name +(``"odoo.exceptions.ValidationError.__init__"``); it has no ``file`` and no ``line``, +because it was never analysed, and ``kind`` is what tells you that rather than leaving +``""`` and ``0`` to be discovered. They are 10% of the call edges on a real application and +usually the ones a caller tracing a sink is looking for, which is why they are not dropped. -**Returns:** +**Parameters:** -- `Dict[str, str]`: Would return a dictionary mapping test method identifiers to -- `Dict[str, str]`: their source code or signatures. +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | The callable's name, whole or a dotted suffix of its signature. | +| `in_class` | `str \| None` | Disambiguate by owning class. | +| `in_module` | `str \| None` | Disambiguate by module. | **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +- `AmbiguousName`: ``name`` matched more than one callable. +- `SelectorNotInGraph`: Nothing matched. -> **See Also** -> `get_methods_with_decorators`: Alternative approach to -> find pytest-decorated methods. - -##### `PythonAnalysis.get_calling_lines` +##### `PythonAnalysis.paths_between` ```python -get_calling_lines(target_method_name: str) -> List[int] +paths_between(src: str, dst: str, src_within: str, dst_within: str, depth: int | None = None, max_paths: int = DEFAULT_MAX_PATHS) -> FlowPaths ``` -Return line numbers where a method is called. +Return how a value reaches another value, the ordered hops, with the evidence for each. + +Where `slice_forward` answers *what a value reaches* as a set, +this answers *how it gets there* as sequences, so a caller can argue a flow rather than +assert one:: + + for path in py.paths_between( + "invoice_id", "invoice_ids", + src_within="PaymentPortal.invoice_transaction", + dst_within="PaymentPortal._process_transaction", + ): + for hop in path.hops: + print(hop.via, hop.var, "->", hop.to.callable, hop.to.kind, hop.to.name) + print("weakest evidence:", path.weakest.via, path.weakest.prov) -This method is intended to find all line numbers in the project -where the specified method is invoked. +Only **shortest** paths come back, and at most ``max_paths`` of them; the result's +``complete`` says whether that was all of them. ``weakest`` on each path names the hop +that caps the claim, the most approximate one (``ssa`` > ``reaching-defs`` > +``points-to``). + +Both callables are required. A value cannot be addressed without the callable it enters, +and ``dst_within`` does not default to ``src_within`` because two values of one callable +are joined only through recursion, a default would make the default call the degenerate +case. ``depth`` is unbounded by default, as on every predicate and path accessor: a bound +turns a real flow into an empty result with nothing to say the bound fired (see +`DEFAULT_DEPTH`). **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `target_method_name` | `str` | The name of the method to find calls to. | - -**Returns:** - -- `List[int]`: Would return a list of line numbers (integers). +| `src` | `str` | The value the flow starts at, named as you would say it (``"invoice_id"``). | +| `dst` | `str` | The value it must reach. | +| `src_within` | `str` | The callable ``src`` enters. | +| `dst_within` | `str` | The callable ``dst`` enters. | +| `depth` | `int \| None` | Most hops a path may take; ``None`` (the default) for no bound. A flow longer than an explicit ``depth`` comes back empty. | +| `max_paths` | `int` | Most paths to return. | **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. +- `AmbiguousName`: A name matched more than one thing. +- `SelectorNotInGraph`: A name matched nothing. +- `ValueError`: ``depth`` is not a positive ``int``, ``max_paths`` is below 1, or ``src`` and ``dst`` are the same position, a path from a node to itself is refused rather than answered ``[]``; ``reaches`` is what asks whether a cycle exists. > **See Also** -> `get_callers`: For finding caller methods instead of lines. +> `slice_forward`: The same reachability as a set, with a ``total``. +> `call_paths_between`: The same shape over the call graph. -##### `PythonAnalysis.get_call_targets` +##### `PythonAnalysis.call_paths_between` ```python -get_call_targets(declared_methods: dict) -> Set[str] +call_paths_between(src: str, dst: str, depth: int | None = None, max_paths: int = DEFAULT_MAX_PATHS) -> FlowPaths ``` -Return call targets using simple name resolution. +Return how one callable reaches another, as ordered call hops. -This method is intended to find all methods that could be called -based on simple name matching, without full semantic analysis. +The evidence-carrying form of `reaches`: that says *whether*, this says *how*:: + + for path in py.call_paths_between("PaymentPortal.invoice_transaction", "AccountMove.write"): + print(" -> ".join(h.to.callable for h in path.hops)) + +Every hop is ``via="call"`` with no ``var`` and no ``prov``, because a call edge carries +neither. Takes no ``within``: a callable is addressed by name alone. **Parameters:** | Name | Type | Description | | ---- | ---- | ----------- | -| `declared_methods` | `dict` | Dictionary of declared method names and signatures. | - -**Returns:** - -- `Set[str]`: Would return a set of method names that are call targets. +| `src` | `str` | The calling callable. | +| `dst` | `str` | The callable it must reach. | +| `depth` | `int \| None` | Most call hops; ``None`` (the default) for no bound, as on `reaches`. | +| `max_paths` | `int` | Most paths to return; the result's ``complete`` says whether that was all. | **Raises:** -- `NotImplementedError`: This functionality is not yet implemented for Python analysis. - -> **See Also** -> `get_call_graph`: For full semantic call resolution. +- `AmbiguousName`: Either name matched more than one callable. +- `SelectorNotInGraph`: Either matched nothing. +- `ValueError`: ``depth`` is not a positive ``int``, ``max_paths`` is below 1, or ``src`` and ``dst`` are the same callable. -##### `PythonAnalysis.get_all_crud_operations` +##### `PythonAnalysis.flows_to_call` ```python -get_all_crud_operations() -> Dict +flows_to_call(src: str, callee: str, within: str, depth: int | None = None) -> bool ``` -Return all CRUD (Create, Read, Update, Delete) operations. +Does ``src`` reach **any** argument of a call to ``callee``? -This method is intended for web application analysis to identify -database operations and REST API endpoints. +A dataflow claim, not a "runs before" one: the target is the set of values that *enter* +``callee``, so ``True`` means the value was passed into a real call. ``within`` scopes +``src`` only, ``callee`` is a callable, addressed by name alone, so there is nothing else +to scope. Unbounded by default, like every predicate here: at five hops this returned +``False`` for a flow that exists, and a bare ``False`` cannot say a bound fired. -**Returns:** +**Parameters:** -- `Dict`: Would return a dictionary of CRUD operations categorized by type. +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The value, named as you would say it. | +| `callee` | `str` | The called callable. | +| `within` | `str` | The callable ``src`` enters. | +| `depth` | `int \| None` | Most hops; ``None`` (the default) for no bound. With an explicit bound, ``False`` means "not within ``depth`` hops", which is why the bound is nameable. | **Raises:** -- `NotImplementedError`: CRUD analysis is not supported for Python. This feature is primarily designed for Java enterprise applications with JPA/Hibernate. +- `AmbiguousName`: A name matched more than one thing. +- `SelectorNotInGraph`: A name matched nothing. > **See Also** -> `get_all_create_operations`: For create operations only. -> `get_all_read_operations`: For read operations only. +> `flows_to_argument`: The narrower question, and a different answer. -##### `PythonAnalysis.get_all_create_operations` +##### `PythonAnalysis.flows_to_argument` ```python -get_all_create_operations() -> Dict +flows_to_argument(src: str, callee: str, arg: str, within: str, depth: int | None = None) -> bool ``` -Return all Create operations from CRUD analysis. +Does ``src`` reach the argument ``arg`` of a call to ``callee``? -**Returns:** +**Not** the same question as `flows_to_call`, which is why it is a separate call: on +odoo-slim-19, ``invoice_id`` of ``PaymentPortal.invoice_transaction`` reaches six of +``_process_transaction``'s seven entering values and not the seventh, so answering the +narrow question with the broad one would over-report. Reaching an argument does imply +reaching the call, and that direction holds by construction. -- `Dict`: Would return a dictionary of create/insert operations. +``arg`` is matched to the parameter **by name**: never by position. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The value the flow starts at. | +| `callee` | `str` | The called callable. | +| `arg` | `str` | The callee's parameter (or global, or capture) by name. | +| `within` | `str` | The callable ``src`` enters; ``arg`` is scoped by ``callee`` itself. | +| `depth` | `int \| None` | Most hops; ``None`` (the default) for no bound, as on `flows_to_call`. | **Raises:** -- `NotImplementedError`: CRUD analysis is not supported for Python. +- `AmbiguousName`: A name matched more than one thing. +- `SelectorNotInGraph`: A name matched nothing, including ``arg`` naming no value of ``callee``, which is a mistake worth stopping on rather than a ``False``. -##### `PythonAnalysis.get_all_read_operations` +##### `PythonAnalysis.describe` ```python -get_all_read_operations() -> Dict +describe(nodes: Sequence[object]) -> List[SliceNode] ``` -Return all Read operations from CRUD analysis. +Fill in ``source`` for these positions, in one round trip. -**Returns:** +A slice, a cone and a path all answer *where*; this answers *what*, and it is a second call +because source is the one field with no size ceiling, a 195,784-node slice carrying text +would be tens of megabytes nobody asked for:: + + sl = py.slice_backward("found_email", within="odoo.tools.mail.email_domain_extract") + for node in py.describe(sl.nodes[:5]): + print(node.file, node.line, node.source) + +Takes anything carrying an address, slice nodes, the ``frm``/``to`` of a path hop, a +``locate()`` result, and gives back the same +`SliceNode` shape with ``source`` filled, so nothing +downstream has to branch on whether a node has been hydrated. -- `Dict`: Would return a dictionary of read/select operations. +Afterwards, ``source=None`` means exactly one thing: **this position exists and there is no +text for it.** A ref that names nothing raises instead. Which positions have no text +depends on the backend, honestly: a callable hydrates on both; a value vertex (a parameter, +global or capture) hydrates on neither, because it is a dataflow position and not a region +of the file; a statement or call site hydrates only on the local backend, because the graph +carries no text below callable granularity. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `nodes` | `Sequence[object]` | The positions to hydrate. An empty sequence costs no round trip. | **Raises:** -- `NotImplementedError`: CRUD analysis is not supported for Python. +- `KeyError`: A ref names nothing in this application, a stale ref, or one minted against a different graph. +- `TypeError`: An element carries no ref at all. -##### `PythonAnalysis.get_all_update_operations` +##### `PythonAnalysis.get_artifacts` ```python -get_all_update_operations() -> Dict +get_artifacts() -> Dict[str, PyArtifact] ``` -Return all Update operations from CRUD analysis. - -**Returns:** - -- `Dict`: Would return a dictionary of update operations. +Return every non-code project artifact (manifest, config file, lockfile, ...), keyed by +its repo-relative path. -**Raises:** +This layer (``Artifact``/``ConfigKey``/``Package`` nodes, ``HAS_ARTIFACT``/ +``DECLARES_DEPENDENCY``/``DEFINES_CONFIG``/``LOCKS`` edges) is the one part of the graph +every ``codeanalyzer-`` projects identically and unprefixed. -- `NotImplementedError`: CRUD analysis is not supported for Python. +> **See Also** +> `get_dependencies`, `get_config_keys`, `get_config_uses`. -##### `PythonAnalysis.get_all_delete_operations` +##### `PythonAnalysis.get_dependencies` ```python -get_all_delete_operations() -> Dict +get_dependencies(direct_only: bool = False, ecosystem: str | None = None, declared_in: str | None = None) -> List[PyDependency] ``` -Return all Delete operations from CRUD analysis. +Return every declared third-party dependency, one entry per declaring manifest, +optionally filtered. -**Returns:** +All three filters default to "don't filter", a pure widening, so existing calls are +unaffected. -- `Dict`: Would return a dictionary of delete operations. +**Parameters:** -**Raises:** +| Name | Type | Description | +| ---- | ---- | ----------- | +| `direct_only` | `bool` | When ``True``, excludes lockfile-only transitive pins. | +| `ecosystem` | `str \| None` | When given, only dependencies from this package ecosystem (e.g. ``"pypi"``). | +| `declared_in` | `str \| None` | When given, only dependencies declared by this artifact id (see `get_artifacts`). | -- `NotImplementedError`: CRUD analysis is not supported for Python. +##### `PythonAnalysis.get_config_keys` -## Schema +```python +get_config_keys() -> Dict[str, PyConfigKey] +``` + +Return every configuration key flattened out of a config-bearing artifact, keyed by its +id (``@key/``), a bare ``key`` (e.g. ``"DB_URL"``) is not unique +across artifacts/namespaces, so the id is the dict key. + +##### `PythonAnalysis.get_config_uses` + +```python +get_config_uses(key: str | None = None) -> List[PyConfigUseEdge] +``` + +Return resolved code-to-config edges: which body node reads which config key. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | `str \| None` | When given, only edges whose target `PyConfigKey` has this bare ``key`` (e.g. ``"DB_URL"``), matched against `get_config_keys`, since `PyConfigUseEdge` itself carries only ``src``/``dst``/``prov``, not the key text. ``None`` (default) returns every edge. | + +> **See Also** +> `get_config_readers`: The same edges, resolved to their reading callables. +> `get_unresolved_config_reads`: The reads this can't show, a match the detector +> found but never closed on a declared key. + +##### `PythonAnalysis.get_unresolved_config_reads` + +```python +get_unresolved_config_reads() -> List[PyConfigRead] +``` + +Return every detector-matched config read that never closed on exactly one declared +key, in one bulk read. + +`get_config_uses` (and `get_config_readers`) can only show reads that +*resolved*; a call the detector matched but couldn't pin to a key (a dynamic key +expression, or a key with no matching declaration) is otherwise invisible, an empty +`get_config_uses` for some key cannot then distinguish "nothing reads this" from "a +read exists but the analyzer couldn't resolve it." This is that missing signal. + +**Returns:** + +- `List[PyConfigRead]`: A list of `PyConfigRead`, each naming *why* resolution +- `List[PyConfigRead]`: failed (``reason="non-literal"`` or ``"undefined-key"``). Over the Neo4j backend, +- `List[PyConfigRead]`: ``site`` always comes back ``""`` and several call sites sharing the same +- `List[PyConfigRead]`: ``(callee, key, reason)`` may collapse into one entry, the graph doesn't carry the +- `List[PyConfigRead]`: call site on this edge (see `get_unresolved_config_reads`'s +- `List[PyConfigRead]`: comment), but "no unresolved reads" here is never a false negative. + +##### `PythonAnalysis.get_config_readers` + +```python +get_config_readers(key: str) -> List[PyCallableOverview] +``` + +Return overviews of every callable reading configuration key ``key``, in one bulk read. + +`get_config_uses` hands back ``PyConfigUseEdge.src``/``dst`` as opaque ordinal ids , +answering "which callable reads this" otherwise means parsing +``codeanalyzer-python``'s id grammar yourself. This does that resolution for you. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | `str` | The bare configuration key (e.g. ``"DB_URL"``), matched the same way `get_config_uses` matches it. | + +**Returns:** + +- `List[PyCallableOverview]`: A list of `PyCallableOverview`, one per distinct reading +- `List[PyCallableOverview]`: callable. Empty means no callable reads this key, see `get_unresolved_config_reads` +- `List[PyCallableOverview]`: if you need to rule out "a read exists but never resolved" too. + +##### `PythonAnalysis.get_classes` + +```python +get_classes(module: str | None = None) -> Dict[str, PyClass] +``` + +Return all classes in the project. + +Retrieves all class definitions discovered during analysis, organized +by their fully qualified names. This includes regular classes, +dataclasses, abstract base classes, and nested classes. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `module` | `str \| None` | Restrict the result to one module's classes, named by symbol-table key (the module's file path, *not* a dotted module name, so it reads the same way as `get_symbol_table`'s ``paths``). A key naming no module raises. ``None`` (the default) returns every class in the application. | + +**Returns:** + +- `Dict[str, PyClass]`: A dictionary mapping fully qualified class names (strings) to +- `Dict[str, PyClass]`: class:`~cldk.models.python.PyClass` objects containing class +- `Dict[str, PyClass]`: metadata, methods, attributes, and inheritance information. + +**Raises:** + +- `SelectorNotInGraph`: ``module`` names no module in this application (``cldk.utils.exceptions``, a ``ValueError``). A mistyped key used to return the same ``{}`` as a module that genuinely declares no classes. + +> **See Also** +> `get_class`: For a single class by name. +> `get_classes_by_criteria`: For filtered class retrieval. + +##### `PythonAnalysis.get_class` + +```python +get_class(qualified_class_name: str) -> PyClass | None +``` + +Return a specific class by its qualified name. + +Retrieves detailed information about a single class, including +its methods, attributes, base classes, and decorators. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `qualified_class_name` | `str` | The fully qualified name of the class (e.g., ``"mypackage.models.User"``). | + +**Returns:** + +- `PyClass \| None`: class:`~cldk.models.python.PyClass` object containing all +- `PyClass \| None`: analyzed information about the class, or ``None`` if the class +- `PyClass \| None`: is not found in the analyzed project. + +> **See Also** +> `get_classes`: For all classes in the project. +> `get_python_file`: To find which file contains a class. + +##### `PythonAnalysis.get_classes_by_criteria` + +```python +get_classes_by_criteria(inclusions: List[str] | None = None, exclusions: List[str] | None = None) -> Dict[str, PyClass] +``` + +Return classes matching inclusion/exclusion filter criteria. + +Filters the project's classes based on substring matching against +their qualified names. Classes are included if their name contains +any inclusion substring AND does not contain any exclusion substring. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `inclusions` | `List[str] \| None` | List of substrings that class names must contain to be included. If ``None`` or empty, no inclusion filtering is applied (effectively includes nothing unless you have at least one inclusion pattern). | +| `exclusions` | `List[str] \| None` | List of substrings that class names must NOT contain. Classes matching any exclusion pattern are filtered out, even if they match an inclusion pattern. | + +**Returns:** + +- `Dict[str, PyClass]`: A dictionary mapping qualified class names to +- `Dict[str, PyClass]`: class:`~cldk.models.python.PyClass` objects for classes +- `Dict[str, PyClass]`: matching the criteria. + +> **Note** +> The filtering uses substring matching (``in`` operator), not +> regular expressions or glob patterns. + +> **See Also** +> `get_classes`: For all classes without filtering. + +##### `PythonAnalysis.get_fields` + +```python +get_fields(qualified_class_name: str) -> List[PyClassAttribute] +``` + +Return class-level attributes (fields) for a specific class. + +Retrieves all class attributes defined in the specified class, +including instance attributes, class attributes, and properties. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `qualified_class_name` | `str` | The fully qualified name of the class (e.g., ``"mypackage.models.User"``). | + +**Returns:** + +- `List[PyClassAttribute]`: A list of `PyClassAttribute` objects, +- `List[PyClassAttribute]`: each containing information about an attribute's name, type +- `List[PyClassAttribute]`: annotation (if present), and default value. + +> **See Also** +> `get_class`: For complete class information. + +##### `PythonAnalysis.get_nested_classes` + +```python +get_nested_classes(qualified_class_name: str) -> List[PyClass] +``` + +Return inner/nested classes defined within a class. + +Retrieves all classes that are defined inside the specified class +(nested class definitions). + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `qualified_class_name` | `str` | The fully qualified name of the outer class (e.g., ``"mypackage.models.Container"``). | + +**Returns:** + +- `List[PyClass]`: A list of `PyClass` objects for each +- `List[PyClass]`: nested class. Returns an empty list if no nested classes exist. + +> **See Also** +> `get_class`: For the outer class information. + +##### `PythonAnalysis.get_sub_classes` + +```python +get_sub_classes(qualified_class_name: str) -> Dict[str, PyClass] +``` + +Return all classes that inherit from the specified class. + +Finds all classes in the project that directly or indirectly extend +the specified base class. This is useful for understanding class +hierarchies and finding implementations of abstract base classes. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `qualified_class_name` | `str` | The fully qualified name of the base class to find subclasses of (e.g., ``"mypackage.base.BaseModel"``). | + +**Returns:** + +- `Dict[str, PyClass]`: A dictionary mapping qualified class names to +- `Dict[str, PyClass]`: class:`~cldk.models.python.PyClass` objects for all classes +- `Dict[str, PyClass]`: that inherit from the specified class. + +> **See Also** +> `get_extended_classes`: For the reverse (what a class extends). + +##### `PythonAnalysis.get_extended_classes` + +```python +get_extended_classes(qualified_class_name: str) -> List[str] +``` + +Return the base class names that a class extends. + +Retrieves the list of parent/base classes for the specified class. +This includes direct base classes from the class definition. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `qualified_class_name` | `str` | The fully qualified name of the class to get base classes for (e.g., ``"mypackage.models.User"``). | + +**Returns:** + +- `List[str]`: A list of base class names (as strings). These may be qualified +- `List[str]`: or unqualified names depending on how they appear in the source. + +> **Note** +> Python does not distinguish between classes and interfaces, +> so all base types are returned here. + +> **See Also** +> `get_sub_classes`: For finding classes that extend this class. + +## Schema Python schema models. @@ -1305,6 +1887,105 @@ Re-exports the canonical Python analysis schema from ``codeanalyzer-python`` so CLDK and the analyzer backend share a single source of truth for the data model. +### `BodyNode` + +```python +class BodyNode(BaseModel) +``` + +A node in a callable's `body`: an AST region (statement/call/branch/…) or +a synthetic analysis vertex (entry/exit/formal_in/out/actual_in/out). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `kind` | `str` | | +| `id` | `str` | | +| `span` | `Optional[Span]` | | +| `callee` | `Optional[str]` | | +| `of` | `Optional[str]` | | +| `parent` | `Optional[str]` | | +| `method_name` | `Optional[str]` | | +| `receiver_expr` | `Optional[str]` | | +| `receiver_type` | `Optional[str]` | | +| `return_type` | `Optional[str]` | | +| `is_constructor_call` | `Optional[bool]` | | +| `arguments` | `List['PyCallArgument']` | | + +### `CdgEdge` + +```python +class CdgEdge(BaseModel) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | + +### `CfgEdge` + +```python +class CfgEdge(BaseModel) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `kind` | `str` | | + +### `DdgEdge` + +```python +class DdgEdge(BaseModel) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `var` | `Optional[str]` | | +| `prov` | `List[str]` | | + +### `ParamEdge` + +```python +class ParamEdge(BaseModel) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | + +### `PyAnalyzerInfo` + +```python +class PyAnalyzerInfo(BaseModel) +``` + +Which analyzer produced this snapshot, and how it was configured. +Lives on the ``Analysis`` envelope (keystone ``analyzer{name,version}``; +``config`` rides additively). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `version` | `str` | | +| `config` | `Dict[str, Any]` | | + ### `PyApplication` ```python @@ -1318,8 +1999,46 @@ Represents a Python application. | Name | Type | Description | | ---- | ---- | ----------- | | `symbol_table` | `Dict[str, PyModule]` | | +| `id` | `str` | | +| `kind` | `str` | | | `call_graph` | `List[PyCallEdge]` | | | `external_symbols` | `Dict[str, PyExternalSymbol]` | | +| `artifacts` | `Dict[str, PyArtifact]` | | +| `dependencies` | `List[PyDependency]` | | +| `unresolved_imports` | `List[PyImportBinding]` | | +| `entrypoint_report` | `PyEntrypointReport` | | +| `repository` | `Optional[PyRepositoryInfo]` | | +| `param_in` | `List[ParamEdge]` | | +| `param_out` | `List[ParamEdge]` | | +| `config_uses` | `List[PyConfigUseEdge]` | | +| `config_reads_unresolved` | `List[PyConfigRead]` | | + +### `PyArtifact` + +```python +class PyArtifact(BaseModel) +``` + +Any non-`.py` project file (config, manifest, CI, container spec, or +plain data/binary) -- never dropped from the walk. Captured broadly (node ++ verbatim ``source``); *meaning* is extracted narrowly -- only +``dependency-manifest`` roles feed ``dependencies`` today. ``id`` is +language-neutral (``can://artifact//``). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `str` | | +| `path` | `str` | | +| `format` | `str` | | +| `roles` | `List[str]` | | +| `size_bytes` | `int` | | +| `sha256` | `str` | | +| `source` | `str` | | +| `extraction` | `str` | | +| `config_keys` | `List[PyConfigKey]` | | ### `PyCallEdge` @@ -1327,11 +2046,11 @@ Represents a Python application. class PyCallEdge(BaseModel) ``` -Identity-only call-graph edge with weight. +Identity-only call-graph edge with weight (keystone shape: the list name +IS the edge type, so there is no ``type`` field). -Mirrors Java's ``CallDependency``. ``source`` and ``target`` are -``PyCallable.signature`` strings, nodes of the graph are the existing -``PyCallable`` entries in the symbol table, not a separate vertex type. +``src`` and ``dst`` are node ids, the caller's ``can://`` id and the +callee's ``can://`` id (a symbol-table callable or an ``@external`` home). Rich per-call metadata (receiver, arguments, location, ...) lives on ``PyCallsite`` inside the source ``PyCallable.call_sites``. @@ -1339,11 +2058,10 @@ Rich per-call metadata (receiver, arguments, location, ...) lives on | Name | Type | Description | | ---- | ---- | ----------- | -| `source` | `str` | | -| `target` | `str` | | -| `type` | `Literal['CALL_DEP']` | | +| `src` | `str` | | +| `dst` | `str` | | | `weight` | `int` | | -| `provenance` | `List[Literal['jedi', 'pycg', 'joern']]` | | +| `prov` | `List[Literal['jedi', 'defuse']]` | | ### `PyCallable` @@ -1360,20 +2078,30 @@ Represents a Python callable (function/method). | `name` | `str` | | | `path` | `str` | | | `signature` | `str` | | +| `id` | `str` | | +| `kind` | `str` | | +| `span` | `Optional[Span]` | | | `comments` | `List[PyComment]` | | -| `decorators` | `List[str]` | | +| `decorators` | `List[PyDecorator]` | | +| `modifiers` | `List[str]` | | +| `entrypoints` | `List[PyEntrypoint]` | | +| `is_entrypoint` | `bool` | | | `parameters` | `List[PyCallableParameter]` | | | `return_type` | `Optional[str]` | | -| `code` | `str` | | | `start_line` | `int` | | | `end_line` | `int` | | | `code_start_line` | `int` | | | `accessed_symbols` | `List[PySymbol]` | | | `call_sites` | `List[PyCallsite]` | | -| `inner_callables` | `Dict[str, 'PyCallable']` | | -| `inner_classes` | `Dict[str, 'PyClass']` | | +| `callables` | `Dict[str, 'PyCallable']` | | +| `types` | `Dict[str, 'PyClass']` | | | `local_variables` | `List[PyVariableDeclaration]` | | | `cyclomatic_complexity` | `int` | | +| `body` | `Dict[str, BodyNode]` | | +| `cfg` | `List[CfgEdge]` | | +| `cdg` | `List[CdgEdge]` | | +| `ddg` | `List[DdgEdge]` | | +| `summary` | `List[SummaryEdge]` | | ### `PyCallableOverview` @@ -1414,8 +2142,10 @@ Represents a parameter of a Python callable (function/method). | Name | Type | Description | | ---- | ---- | ----------- | | `name` | `str` | | +| `id` | `str` | | | `type` | `Optional[str]` | | | `default_value` | `Optional[str]` | | +| `decorators` | `List[PyDecorator]` | | | `start_line` | `int` | | | `end_line` | `int` | | | `start_column` | `int` | | @@ -1437,6 +2167,7 @@ Represents a Python call site (function or method invocation) with contextual me | `receiver_expr` | `Optional[str]` | | | `receiver_type` | `Optional[str]` | | | `argument_types` | `List[str]` | | +| `arguments` | `List[PyCallArgument]` | | | `return_type` | `Optional[str]` | | | `callee_signature` | `Optional[str]` | | | `is_constructor_call` | `bool` | | @@ -1459,12 +2190,17 @@ Represents a Python class. | ---- | ---- | ----------- | | `name` | `str` | | | `signature` | `str` | | +| `id` | `str` | | +| `kind` | `str` | | +| `span` | `Optional[Span]` | | | `comments` | `List[PyComment]` | | -| `code` | `str` | | | `base_classes` | `List[str]` | | -| `methods` | `Dict[str, PyCallable]` | | +| `decorators` | `List[PyDecorator]` | | +| `entrypoints` | `List[PyEntrypoint]` | | +| `is_entrypoint` | `bool` | | +| `callables` | `Dict[str, PyCallable]` | | | `attributes` | `Dict[str, PyClassAttribute]` | | -| `inner_classes` | `Dict[str, 'PyClass']` | | +| `types` | `Dict[str, 'PyClass']` | | | `start_line` | `int` | | | `end_line` | `int` | | @@ -1482,10 +2218,38 @@ Represents a Python class attribute. | ---- | ---- | ----------- | | `name` | `str` | | | `type` | `Optional[str]` | | +| `initializer` | `Optional[str]` | | | `comments` | `List[PyComment]` | | +| `decorators` | `List[PyDecorator]` | | | `start_line` | `int` | | | `end_line` | `int` | | +### `PyClassOverview` + +```python +class PyClassOverview(BaseModel) +``` + +A lightweight projection of one class, the class-level counterpart to +`PyCallableOverview`, for classes the analyzer marked as entrypoints in their own right +(``PyClass.is_entrypoint``), independent of any individual method. + +A class-based view (a Django/Flask class-based view, say) can be marked as an entrypoint at +the class level with none of its methods individually marked, ``get_entrypoints()`` walks +callables only, so it never sees these. Returned by +`get_entrypoint_classes`. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signature` | `str` | | +| `name` | `str` | | +| `path` | `str` | | +| `start_line` | `int` | | +| `end_line` | `int` | | +| `decorators` | `List[str]` | | + ### `PyComment` ```python @@ -1505,6 +2269,169 @@ Represents a Python comment. | `end_column` | `int` | | | `is_docstring` | `bool` | | +### `PyConfigKey` + +```python +class PyConfigKey(BaseModel) +``` + +A configuration key flattened out of a config-bearing ``PyArtifact`` +(#152). Graph vocabulary stays neutral (label ``ConfigKey``, edge +``DEFINES_CONFIG``) -- the ``Py`` prefix here is only the ``PyArtifact`` +naming precedent, not a Python-specific claim. L1 data, identical at +every analysis level; nested under the owning artifact, containment +mirrors ``DEFINES_CONFIG``. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `key` | `str` | | +| `namespace` | `str` | | +| `value` | `Optional[str]` | | +| `span` | `Optional[Span]` | | +| `references` | `List[str]` | | + +### `PyConfigRead` + +```python +class PyConfigRead(BaseModel) +``` + +A detector-matched call whose key did not close on exactly one string +literal -- first-class so a config read nobody can trace is as visible +as one that resolves (#162). ``key`` is the decoded literal text only +when it IS a literal but matches no declared ``PyConfigKey`` +(``reason="undefined-key"``); ``None`` for a key that never closed on a +literal at all (``reason="non-literal"``). ``prov`` lists every tier +that was attempted before giving up. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `site` | `str` | | +| `callee` | `str` | | +| `key` | `Optional[str]` | | +| `reason` | `Literal['non-literal', 'undefined-key']` | | +| `prov` | `List[Literal['literal', 'dataflow']]` | | + +### `PyConfigUseEdge` + +```python +class PyConfigUseEdge(BaseModel) +``` + +One resolved config read (#162): a detector-matched call's key +argument closed on exactly one string literal that matches a declared +``PyConfigKey``. ``src`` is the call's GLOBAL ordinal id +(``@``); ``dst`` is the matched ``PyConfigKey.id`` +-- application scope, mirroring ``param_in`` (endpoints span callables/ +artifacts). Superset-monotonic across levels, same additive contract as +the DDG's ``prov`` widening: literal (``-a 2``+) subset of +dataflow +(``-a 3``/``-a 4``). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `prov` | `List[Literal['literal', 'dataflow']]` | | + +### `PyDecorator` + +```python +class PyDecorator(BaseModel) +``` + +One decorator application, structured rather than a source string (#128). + +``name`` is the spelling as written (``lru_cache``, ``builtins.staticmethod``); +``qualified_name`` is Jedi's resolution of it (``functools.lru_cache``) and is +absent when it cannot be resolved. ``expression`` keeps the full unparsed source +so nothing is lost for decorators too complex to decompose. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `qualified_name` | `Optional[str]` | | +| `positional_arguments` | `List[str]` | | +| `keyword_arguments` | `Dict[str, str]` | | +| `expression` | `str` | | +| `span` | `Optional[Span]` | | + +### `PyDependency` + +```python +class PyDependency(BaseModel) +``` + +One declared third-party dependency, evidence-tagged via ``prov``. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `ecosystem` | `str` | | +| `spec` | `str` | | +| `kind` | `str` | | +| `extras` | `List[str]` | | +| `declared_in` | `str` | | +| `direct` | `bool` | | +| `locked_version` | `Optional[str]` | | +| `provides_imports` | `List[str]` | | +| `prov` | `List[str]` | | + +### `PyEntrypoint` + +```python +class PyEntrypoint(BaseModel) +``` + +One way a callable or class is invoked from outside the application (#27). + +A node may hold several: two ``@app.route`` decorators, or a function that +is both a Celery task and a CLI command. ``confidence`` lets a consumer +threshold on evidence quality rather than inheriting this analyzer's +judgement. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `framework` | `str` | | +| `confidence` | `str` | | +| `rule` | `str` | | +| `ruleset` | `str` | | +| `evidence` | `Optional[str]` | | +| `route` | `Optional[str]` | | +| `http_methods` | `List[str]` | | +| `via` | `Optional[str]` | | + +### `PyExternalSymbol` + +```python +class PyExternalSymbol(BaseModel) +``` + +A call-graph target outside the analyzed project -- an imported library or +builtin member. An edge-endpoint id home, not a tree node: keyed in +``PyApplication.external_symbols`` by its ``can://…/@external/…`` id. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `str` | | +| `name` | `str` | | +| `module` | `Optional[str]` | | + ### `PyImport` ```python @@ -1520,6 +2447,7 @@ Represents a Python import statement. | `module` | `str` | | | `name` | `str` | | | `alias` | `Optional[str]` | | +| `resolved_module` | `Optional[str]` | | | `start_line` | `int` | | | `end_line` | `int` | | | `start_column` | `int` | | @@ -1539,9 +2467,12 @@ Represents a Python module. | ---- | ---- | ----------- | | `file_path` | `str` | | | `module_name` | `str` | | +| `id` | `str` | | +| `kind` | `str` | | +| `source` | `str` | | | `imports` | `List[PyImport]` | | | `comments` | `List[PyComment]` | | -| `classes` | `Dict[str, PyClass]` | | +| `types` | `Dict[str, PyClass]` | | | `functions` | `Dict[str, PyCallable]` | | | `variables` | `List[PyVariableDeclaration]` | | | `content_hash` | `Optional[str]` | | @@ -1591,5 +2522,35 @@ Represents a Python variable declaration. | `start_column` | `int` | | | `end_column` | `int` | | +### `Span` + +```python +class Span(BaseModel) +``` + +Where a node lives in source. `start`/`end` are [line, col] (1-based line, +0-based col, ast semantics); `bytes` are utf-8 offsets into module.source. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `start` | `Tuple[int, int]` | | +| `end` | `Tuple[int, int]` | | +| `bytes` | `Tuple[int, int]` | | + +### `SummaryEdge` + +```python +class SummaryEdge(BaseModel) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | + diff --git a/src/content/docs/reference/python-api/typescript.md b/src/content/docs/reference/python-api/typescript.md index 15e7f84..4d9043d 100644 --- a/src/content/docs/reference/python-api/typescript.md +++ b/src/content/docs/reference/python-api/typescript.md @@ -53,9 +53,9 @@ graph = analysis.get_call_graph() # -> networkx.DiGraph -[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 1.4.0](https://img.shields.io/badge/cldk-1.4.0-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/1.4.0/) +[![Source on GitHub](https://img.shields.io/badge/source-codellm--devkit%2Fpython--sdk-181717?logo=github&logoColor=white)](https://github.com/codellm-devkit/python-sdk) [![cldk 2.0.0rc3](https://img.shields.io/badge/cldk-2.0.0rc3-3776AB?logo=pypi&logoColor=white)](https://pypi.org/project/cldk/2.0.0rc3/) -_API reference generated from cldk 1.4.0._ +_API reference generated from cldk 2.0.0rc3._ ## Analysis @@ -94,6 +94,7 @@ same method surface: | `backend_config` | `TSBackend` | | | `backend` | `TSAnalysisBackend` | | | `application` | `TSApplication` | | +| `has_resolution_edges` | `bool` | Whether :meth:`get_callsites_for` can resolve call sites on this backend right now. | #### Methods @@ -121,8 +122,11 @@ get_modules() -> List[TSModule] get_call_graph() -> nx.DiGraph ``` -NetworkX DiGraph of callable signatures (and phantom external symbols) connected by the -identity-only call edges. +NetworkX DiGraph of the call edges, keyed as every other accessor keys things (module +file key, type/callable signature, ``"."`` for an external), each node +tagged ``kind`` (``module | class | interface | enum | type_alias | namespace | callable | external``) and ``id``. TypeScript's own +endpoints are kept: a module is the caller of its top-level code and a class the callee of +``new X()``; filter on ``kind == "callable"`` for Python's shape. ##### `TypeScriptAnalysis.get_external_symbols` @@ -130,9 +134,10 @@ identity-only call edges. get_external_symbols() -> Dict[str, TSExternalSymbol] ``` -The phantom (external) call targets, imported/required library members the call graph -points at (e.g. ``node:fs.readFileSync``, ``js-yaml.load``). Useful for source→sink -reachability. +The phantom (external) call targets, imported/required library members and builtins +the call graph points at, keyed ``"."`` (e.g. ``node:fs.readFileSync``, +``(builtin).push``) as the call graph keys them, the wire's ``can://`` id on the value. +Useful for source→sink reachability. ##### `TypeScriptAnalysis.get_synthesized_callables` @@ -156,9 +161,11 @@ get_call_graph_json() -> str get_callers(target_class_name: str, target_method_declaration: str | None = None) -> Dict ``` -Callers of a method, with the connecting call-graph edge metadata (``provenance`` / -``tags``). Pass a bare signature as the first argument for module-level functions or -external (phantom) targets. +Callers of a method, with the connecting call-graph edge metadata, ``type``, +``weight`` and ``provenance``, the same three keys `get_call_graph` puts on an edge. +(There is no ``tags``: it was a schema-1.0.0 call-edge field, and schema v2's +``TSCallGraphEdge`` is ``{src, dst, prov, weight}``.) Pass a bare signature as the first +argument for module-level functions or external (phantom) targets. ##### `TypeScriptAnalysis.get_callees` @@ -209,37 +216,6 @@ get_call_targets(source_signature: str) -> Set[str] The call targets invoked from a callable, derived from its call sites. -##### `TypeScriptAnalysis.get_entry_point_methods` - -```python -get_entry_point_methods() -> Dict[str, Dict[str, TSCallable]] -``` - -Return methods identified as application entry points. - -Not yet supported: the codeanalyzer-typescript backend's entrypoint detection is a stub -placeholder, the ``entrypoints`` list on each ``TSCallable``/``TSClass`` is always empty -(level-2 finders are not implemented), so this method exists for API parity with -`PythonAnalysis` / `JavaAnalysis` but raises. - -**Raises:** - -- `NotImplementedError`: Always. - -##### `TypeScriptAnalysis.get_service_entry_point_methods` - -```python -get_service_entry_point_methods(**kwargs) -> Dict[str, Dict[str, TSCallable]] -``` - -Return methods that serve as service entry points (e.g. Express/NestJS routes). - -Not yet supported; see `get_entry_point_methods`. - -**Raises:** - -- `NotImplementedError`: Always. - ##### `TypeScriptAnalysis.get_classes` ```python @@ -374,6 +350,11 @@ get_typescript_module(file_path: str) -> TSModule | None get_nested_classes(qualified_class_name: str) -> List[TSClass] ``` +Always ``[]`` on schema v2, on both backends -- permanently, not for want of data. A +v2 class node holds only ``callables`` and ``fields``: the tree gives a class no ``types`` +bucket, so no class can nest a class. A class declared inside a *callable* is the surviving +case and reads as ``TSCallable.inner_classes``. Kept because the 1.x surface had it (G3). + ##### `TypeScriptAnalysis.get_sub_classes` ```python @@ -427,9 +408,780 @@ get_classes_with_decorators(decorators: List[str]) -> Dict[str, List[str]] Map each requested decorator name to the signatures of classes carrying it. +##### `TypeScriptAnalysis.get_callables_overview` + +```python +get_callables_overview() -> List[TSCallableOverview] +``` + +Return a lightweight overview of every callable in the project, in one bulk read. + +A field-projected alternative to `get_methods` for enumeration: each +`TSCallableOverview` carries the callable's signature, +owning class/interface (if any), native kind, location, and decorators, but not the full +reconstruction (call sites, inner callables, locals). On the Neo4j backend this is a single +Cypher query instead of the per-entity fan-out `get_methods` pays. Body-inspect the +few you need afterwards via `get_method` or `get_method_bodies`. + +**Returns:** + +- `List[TSCallableOverview]`: A flat list of `TSCallableOverview`, one per callable +- `List[TSCallableOverview]`: (class/interface methods, module- and namespace-level functions, and nested/inner +- `List[TSCallableOverview]`: callables). + +> **See Also** +> `get_decorated_callables`: The same projection filtered by decorator. +> `get_method_bodies`: Bulk source-body fetch for chosen signatures. + +> **Note** +> A ``get x()``/``set x()`` accessor pair shares one ``signature``, so this projection +> (and the other bulk accessors) can diverge between the local and Neo4j backends on a +> paired accessor, see `#300 `_. + +##### `TypeScriptAnalysis.get_method_bodies` + +```python +get_method_bodies(signatures: List[str]) -> Dict[str, str] +``` + +Return source bodies for the given callable signatures, in one bulk read. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signatures` | `List[str]` | Callable signatures to fetch bodies for (e.g. from `get_callables_overview`). | + +**Returns:** + +- `Dict[str, str]`: A dict mapping each signature to its source body. Signatures with no matching callable +- `Dict[str, str]`: are omitted, as are callables whose ``code`` is ``None`` (e.g. implicit constructors +- `Dict[str, str]`: the analyzer synthesizes with no source text), every returned value is a real ``str``. + +##### `TypeScriptAnalysis.get_decorated_callables` + +```python +get_decorated_callables(markers: List[str]) -> List[TSCallableOverview] +``` + +Return overviews of callables decorated with any of the given markers, in one bulk read. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `markers` | `List[str]` | Decorator names to match (e.g. ``["Get", "Controller"]``). | + +**Returns:** + +- `List[TSCallableOverview]`: A list of `TSCallableOverview` for every callable +- `List[TSCallableOverview]`: carrying at least one of ``markers`` as a decorator. + +> **See Also** +> `get_callables_overview`: The unfiltered projection. + +##### `TypeScriptAnalysis.get_callsites_for` + +```python +get_callsites_for(signatures: List[str]) -> Dict[str, List[TSCallsite]] +``` + +Return the call sites of the given callables, keyed by signature, in one bulk read. + +Avoids the per-callable reconstruction fan-out when you need call sites for a specific +frontier (e.g. dispatch-edge synthesis or external-reader detection). + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signatures` | `List[str]` | Callable signatures to fetch call sites for. | + +**Returns:** + +- `Dict[str, List[TSCallsite]]`: A dict mapping each existing signature to its list of +- `Dict[str, List[TSCallsite]]`: class:`~cldk.models.typescript.TSCallsite` (empty if the callable has no call sites). +- `Dict[str, List[TSCallsite]]`: Signatures with no matching callable are omitted. + +##### `TypeScriptAnalysis.locate` + +```python +locate(path: str, line: int) -> LocateResult +``` + +Resolve a source position to its enclosing callable, with the source in hand. + +The single most-needed query for triaging a scanner alert: an alert arrives as +``file:line`` and this resolves it to the enclosing callable in one call, rather than +``get_method``, falling back to ``get_callers``, falling back to scanning the symbol table +by hand. Four outcomes stay distinguishable, see +`LocateResult`: inside a callable (``callable`` set, +plus ``body`` when a body node is that precise), at real module scope (``module_scope`` +diagnostic), in the gap between two callables (also module scope, never snapped to the +nearest callable), or in a file the analysis has no module for (``file_not_in_graph``). + +There is no ``col`` parameter. Column-level disambiguation would have to be honoured by +both backends to mean anything, and the Neo4j graph projects only ``start_line`` / +``end_line`` on ``:TSCallable`` and ``:TSBodyNode``, so a ``col`` would work in-process and +be silently ignored over Neo4j. Better absent than documented and inert. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `path` | `str` | The file path. Normalised against the backend's module keys, so a ``./``-prefixed or absolute path resolves rather than reading back as ``file_not_in_graph``. | +| `line` | `int` | The 1-based line number. | + +**Returns:** + +- `LocateResult`: class:`~cldk.analysis.commons.results.LocateResult` carrying the innermost body +- `LocateResult`: node, the enclosing callable, its owning class/interface, its module, and the source +- `LocateResult`: slice, never an ambiguous empty. + +> **See Also** +> `locate_many`: The bulk form, the point, not an optimisation. + +##### `TypeScriptAnalysis.locate_many` + +```python +locate_many(positions: Sequence[Tuple[str, int]]) -> List[LocateResult] +``` + +Resolve many ``(path, line)`` positions in one round trip, in input order. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `positions` | `Sequence[Tuple[str, int]]` | The ``(path, line)`` pairs to resolve, e.g. from a scanner's alert list. | + +**Returns:** + +- `List[LocateResult]`: class:`~cldk.analysis.commons.results.LocateResult` per input position, in the +- `List[LocateResult]`: same order. + +> **See Also** +> `locate`: The single-position form. + +##### `TypeScriptAnalysis.resolve_callable` + +```python +resolve_callable(name: str, in_class: str | None = None, in_module: str | None = None) -> SliceNode +``` + +Resolve a callable name to the one callable it names, in the caller's vocabulary. + +The addressing step every name-taking accessor performs, exposed so a caller can perform it +once and keep the answer:: + + node = ts.resolve_callable("show", in_class="UserController") + node.callable # the full dotted signature, what every other accessor keys by + node.file, node.line + +``name`` matches whole or as a dotted suffix; ``in_class`` is a dotted suffix of the owning +class or interface, ``in_module`` a module key (``"src/controllers.ts"``) or the dotted form +(``"src.controllers"``). An anonymous callable is addressed by its ```` +signature, never by its name, cants calls every one of them ``"(anonymous)"``. Ambiguity +raises with every candidate; nothing is guessed. + +**Raises:** + +- `AmbiguousName`: More than one callable matched. +- `SelectorNotInGraph`: Nothing matched, naming the argument that missed. + +##### `TypeScriptAnalysis.resolve_value` + +```python +resolve_value(name: str, within: str) -> SliceNode +``` + +Resolve a value name inside a callable, in TypeScript, a parameter, to the position +that carries it. + +The same resolution the dataflow accessors perform on their ``src``, exposed so a caller can +check what a name means before asking a question of it:: + + ts.resolve_value("id", within="UserController.show").kind # "parameter" + +**Raises:** + +- `AmbiguousName`: ``within`` named more than one callable, or ``name`` more than one value. +- `SelectorNotInGraph`: No such callable, or no such value in it. + +##### `TypeScriptAnalysis.get_source` + +```python +get_source(node_id: str) -> str +``` + +Return the source text named by ``node_id``, a callable, or one of its body nodes. + +Generalises `get_method_bodies` below callable granularity: ``node_id`` is a +callable's signature, a callable's opaque id, or the body-node id +`node_id` hands back, so a statement or +call site `locate` found can be re-fetched precisely. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `node_id` | `str` | A callable signature, or an id from `locate` / `resolve_callable`, passed back as received, not composed. | + +**Returns:** + +- `str`: The source text, never an ambiguous empty string. + +**Raises:** + +- `KeyError`: Nothing matches ``node_id``, or it has no recoverable source. +- `NotImplementedError`: (Neo4j backend only) ``node_id`` names a body node, the attached graph carries no source text below callable granularity. + +##### `TypeScriptAnalysis.describe` + +```python +describe(nodes: Sequence[object]) -> List[SliceNode] +``` + +Fill in ``source`` for these positions, in one round trip. + +Addressing answers *where*; this answers *what*, and it is a second call because source is +the one field with no size ceiling. Takes anything carrying an address, slice nodes, a +``locate()`` result, and gives back the same +`SliceNode` shape with ``source`` filled. + +Afterwards, ``source=None`` means exactly one thing: **this position exists and there is no +text for it.** A ref that names nothing raises instead. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `nodes` | `Sequence[object]` | The positions to hydrate. An empty sequence costs no round trip. | + +**Returns:** + +- `List[SliceNode]`: The same positions, in the same order, with ``source`` filled where the backend has +- `List[SliceNode]`: text for them. + +**Raises:** + +- `KeyError`: A ref names nothing in this application. +- `TypeError`: An element carries no address to look up. + +##### `TypeScriptAnalysis.get_cfg` + +```python +get_cfg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[TSCfgEdge] +``` + +Return one page of the control flow inside one callable, addressed by name. + +The graph the analyzer built, not one re-derived here: a conditional's two successors stay +two edges discriminated by ``kind``. Endpoints are the body nodes' own opaque ids, which +`get_source` and `describe` both accept:: + + page = ts.get_cfg("show", in_class="UserController") + page.total # the whole graph's size, on every page + page.complete # False when there is more, with page.next_cursor to fetch it + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `callable` | `str` | The callable's name, resolved as by `resolve_callable`. | +| `in_class` | `str \| None` | Disambiguate by owning class or interface. | +| `page_size` | `int` | Most edges to return. | +| `cursor` | `str \| None` | ``next_cursor`` from a previous page; ``None`` starts at the beginning. | + +**Returns:** + +- `EdgePage[TSCfgEdge]`: class:`~cldk.analysis.commons.results.EdgePage` of +- `EdgePage[TSCfgEdge]`: class:`~cldk.models.typescript.TSCfgEdge`. + +**Raises:** + +- `AmbiguousName`: More than one callable matched. +- `SelectorNotInGraph`: Nothing matched. +- `ValueError`: ``page_size`` below 1, or a cursor from another page, callable or accessor. +- `CodeanalyzerUsageException`: (local backend) built below ``analysis_level="program_dependency_graph"``. + +##### `TypeScriptAnalysis.get_cdg` + +```python +get_cdg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[TSCdgEdge] +``` + +Return one page of the control *dependence* inside one callable. + +``src`` is the branching node ``dst`` is control dependent on. Arguments, paging and +failures are `get_cfg`'s. + +##### `TypeScriptAnalysis.get_ddg` + +```python +get_ddg(callable: str, in_class: str | None = None, page_size: int = DEFAULT_PAGE_SIZE, cursor: str | None = None) -> EdgePage[TSDdgEdge] +``` + +Return one page of the data dependence inside one callable. + +Each edge carries the variable it flows and the evidence for it. **TypeScript has a single +provenance tier:** every edge's ``prov`` is ``["reaching-defs"]``, where Python distinguishes +``ssa`` / ``reaching-defs`` / ``points-to``. Arguments, paging and failures are +`get_cfg`'s. + +##### `TypeScriptAnalysis.slice_backward` + +```python +slice_backward(src: str, within: str, depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice +``` + +Return everything the value ``src`` depends on, reverse reachability over the SDG. + +``depth`` defaults to a **finite** bound on purpose: a bounded traversal answers a narrower +question *completely*, and ``total`` says how much was left out. ``depth=None`` asks for the +whole cone:: + + s = ts.slice_backward("id", within="UserController.show") + s.total, s.truncated + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The value's name, in TypeScript, a parameter. | +| `within` | `str` | The callable to look inside. Required: a value name is scoped by its callable. | +| `depth` | `int \| None` | Most hops from the seed; ``None`` for the whole cone. | +| `max_nodes` | `int` | Most nodes in the result; a cap that fires is reported, never silent. | + +**Returns:** + +- `Slice`: class:`~cldk.analysis.commons.results.Slice`, ordered by node id, with ``source`` +- `Slice`: unhydrated (`describe` fills it in). + +**Raises:** + +- `AmbiguousName`: ``within`` or ``src`` matched more than one thing. +- `SelectorNotInGraph`: No such callable, or no such value in it. +- `ValueError`: ``depth`` is not a positive ``int``, or ``max_nodes`` is below 1. + +##### `TypeScriptAnalysis.slice_forward` + +```python +slice_forward(src: str, within: str, depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice +``` + +Return everything the value ``src`` can affect, the same edges read forward. + +Usually the interesting direction for a parameter: nothing flows *into* one except from its +callers. Arguments, bounds and failures are `slice_backward`'s. + +##### `TypeScriptAnalysis.reaches` + +```python +reaches(src: str, dst: str, depth: int | None = None) -> bool +``` + +Return whether control can get from one callable to another over the call graph. + +The cheap check before asking for the paths themselves. **``depth`` is unbounded by +default**, unlike the slices: a bound on a boolean would collapse "there is no path" and +"there is no path within five hops" into the same ``False``. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The calling callable's name. | +| `dst` | `str` | The called callable's name. | +| `depth` | `int \| None` | Most call hops, or ``None`` for any distance. | + +**Raises:** + +- `AmbiguousName`: Either name matched more than one callable. +- `SelectorNotInGraph`: Either matched none. +- `ValueError`: ``depth`` is not a positive ``int``. + +##### `TypeScriptAnalysis.backward_cone` + +```python +backward_cone(sinks: Sequence[str], depth: int | None = DEFAULT_DEPTH, max_nodes: int = DEFAULT_MAX_NODES) -> Slice +``` + +Return every call-graph vertex that can reach any of ``sinks``, "what could get here". + +The accessor to reach for when the sink is a dangerous function and the question is which +entry points lead to it. Its nodes are callables **and modules**: cants makes a module the +caller of its own top-level code, so a cone without them would under-report. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `sinks` | `Sequence[str]` | The callables to walk back from; a bare string is refused. | +| `depth` | `int \| None` | Most call hops back; ``None`` for the whole cone. | +| `max_nodes` | `int` | Most nodes in the result. | + +**Raises:** + +- `AmbiguousName`: A sink matched more than one callable. +- `SelectorNotInGraph`: A sink matched none. +- `TypeError`: ``sinks`` is a bare string. +- `ValueError`: ``sinks`` is empty, or a bound is out of range. + +##### `TypeScriptAnalysis.callers_of` + +```python +callers_of(name: str, in_class: str | None = None, in_module: str | None = None) -> List[SliceNode] +``` + +Return who calls this, one hop back over the call graph, addressed by name. + +The name-based sibling of `get_callers`, returning +`SliceNode` objects rather than raw dicts. A module is +a legitimate caller (``kind="module"``). ``[]`` is unambiguous: a name matching nothing +raises. + +**Raises:** + +- `AmbiguousName`: More than one callable matched. +- `SelectorNotInGraph`: Nothing matched. + +##### `TypeScriptAnalysis.callees_of` + +```python +callees_of(name: str, in_class: str | None = None, in_module: str | None = None) -> List[SliceNode] +``` + +Return what this calls, one hop forward, externals included (``kind="external"``). + +An external was never analysed, so it has no position: ``file=""`` and ``line=0``, with +``kind`` saying why. Its ``callable`` is the readable ``"."``. + +**Raises:** + +- `AmbiguousName`: More than one callable matched. +- `SelectorNotInGraph`: Nothing matched. + +##### `TypeScriptAnalysis.paths_between` + +```python +paths_between(src: str, dst: str, src_within: str, dst_within: str, depth: int | None = None, max_paths: int = DEFAULT_MAX_PATHS) -> FlowPaths +``` + +Return how one value reaches another, the *sequences*, where a slice is the set. + +Each hop says what justified it: the kind of edge (``data`` / ``control`` / ``argument`` / +``return`` / ``summary``), the variable, and the provenance, which in TypeScript is always +``["reaching-defs"]``. Only shortest paths are returned. + +**Two scopes, not one**: a value is addressed by a name plus the callable it enters, and a +single scope could never find the cross-callable path this accessor exists for. ``depth`` is +unbounded by default, for `reaches`'s reason. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The value the flow starts at. | +| `dst` | `str` | The value it must reach. | +| `src_within` | `str` | The callable ``src`` enters. Required. | +| `dst_within` | `str` | The callable ``dst`` enters. Required. | +| `depth` | `int \| None` | Most hops a path may take; ``None`` for no bound. | +| `max_paths` | `int` | Most paths to return; ``complete`` says whether more existed. | + +**Raises:** + +- `AmbiguousName`: A name matched more than one thing. +- `SelectorNotInGraph`: A name matched nothing. +- `ValueError`: A bound is out of range, or the two endpoints are the same position. + +##### `TypeScriptAnalysis.call_paths_between` + +```python +call_paths_between(src: str, dst: str, depth: int | None = None, max_paths: int = DEFAULT_MAX_PATHS) -> FlowPaths +``` + +Return how one callable reaches another, the evidence-carrying form of `reaches`. + +Every hop is ``via="call"`` with no variable and no provenance: a call is a syntactic fact, +and saying so is better than inventing a provenance for it. ``depth`` is unbounded by +default. + +**Raises:** + +- `AmbiguousName`: Either name matched more than one callable. +- `SelectorNotInGraph`: Either matched nothing. +- `ValueError`: A bound is out of range, or ``src`` and ``dst`` name the same callable. + +##### `TypeScriptAnalysis.flows_to_call` + +```python +flows_to_call(src: str, callee: str, within: str, depth: int | None = None) -> bool +``` + +Return whether this value reaches **any** argument of a call to ``callee``. + +A dataflow claim, not a control one: a value that merely runs before a call site and feeds +none of its arguments is not counted. ``depth`` is unbounded by default, a bare ``False`` +carries no signal that a bound fired. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The value, named as a caller would. | +| `callee` | `str` | The called callable. | +| `within` | `str` | The callable ``src`` enters. Required; it scopes ``src`` only. | +| `depth` | `int \| None` | Most hops; ``None`` for no bound. | + +**Raises:** + +- `AmbiguousName`: A name matched more than one thing. +- `SelectorNotInGraph`: A name matched nothing. +- `ValueError`: ``depth`` is not a positive ``int``. + +##### `TypeScriptAnalysis.flows_to_argument` + +```python +flows_to_argument(src: str, callee: str, arg: str, within: str, depth: int | None = None) -> bool +``` + +Return whether this value reaches the argument ``arg`` of a call to ``callee``. + +The narrower question: a tainted value routinely reaches a function without reaching the +parameter that matters. ``arg`` is resolved **by name**, never by position. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | The value the flow starts at. | +| `callee` | `str` | The called callable. | +| `arg` | `str` | The callee's parameter, by name. | +| `within` | `str` | The callable ``src`` enters. Required; ``arg`` is scoped by ``callee``. | +| `depth` | `int \| None` | Most hops; ``None`` for no bound. | + +**Raises:** + +- `AmbiguousName`: A name matched more than one thing. +- `SelectorNotInGraph`: A name matched nothing, including ``arg`` naming no parameter of ``callee``, which is a caller error and not a ``False``. +- `ValueError`: ``depth`` is not a positive ``int``. + +##### `TypeScriptAnalysis.get_entrypoints` + +```python +get_entrypoints() -> List[TSCallableOverview] +``` + +Return overviews of every callable the analyzer marked as an entrypoint. + +A CLI command, a route handler, whatever ruleset the entrypoint pass matched. This is +where an agent starts a taint question: the callables reachable from outside. + +**Returns:** + +- `List[TSCallableOverview]`: One overview per marked callable. Empty means the pass found no entrypoint +- `List[TSCallableOverview]`: *callables*, a real fact about the project, not "cannot tell". + +> **See Also** +> `get_entrypoint_classes`: The class-level sibling this walk never sees. +> `get_entrypoint_coverage`: Whether the detection pass itself had gaps. + +##### `TypeScriptAnalysis.get_entrypoint_classes` + +```python +get_entrypoint_classes() -> List[TSClassOverview] +``` + +Return overviews of every class the analyzer marked as an entrypoint in its own right. + +`get_entrypoints` walks callables only, so a class the rulesets matched with no +individually-marked method is invisible to it. + +**Returns:** + +- `List[TSClassOverview]`: One overview per marked class. Empty means no class carries the mark. + +> **See Also** +> `get_entrypoints`: The callable-level projection. + +##### `TypeScriptAnalysis.get_entrypoint_coverage` + +```python +get_entrypoint_coverage() -> EntrypointCoverage +``` + +Return the entrypoint-detection pass's own coverage and failure record. + +Entrypoint detection under-approximates by design, so silence is its failure mode: +`get_entrypoints` returning ``[]`` cannot say whether the pass ran clean or gave up. +This is what distinguishes them, the frameworks it recognized, the rulesets it consulted, +the near-misses it could not resolve, and the errors it hit. + +**Returns:** + +- `EntrypointCoverage`: The coverage record. A non-empty ``diagnostics`` means the source carries no report at +- `EntrypointCoverage`: all, and the other fields are then not "no gaps found" but "nothing to report from". + +> **See Also** +> `get_entrypoints`: The accessor whose empty result this disambiguates. + +##### `TypeScriptAnalysis.get_artifacts` + +```python +get_artifacts() -> Dict[str, PyArtifact] +``` + +Return every non-code artifact the analyzer indexed, keyed by repo-relative path. + +``package.json``, ``tsconfig.json``, a lockfile, a Dockerfile, the files that say what the +project depends on and how it is configured, which the code itself never states. + +**Returns:** + +- `Dict[str, PyArtifact]`: ``{path: artifact}``. Each artifact carries its roles, its text and the config keys it +- `Dict[str, PyArtifact]`: defines. + +> **See Also** +> `get_dependencies`, `get_config_keys`, `get_config_uses`. + +##### `TypeScriptAnalysis.get_dependencies` + +```python +get_dependencies(direct_only: bool = False, ecosystem: str | None = None, declared_in: str | None = None) -> List[PyDependency] +``` + +Return every declared dependency, optionally filtered. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `direct_only` | `bool` | Keep only dependencies the project declares itself, not transitive ones. | +| `ecosystem` | `str \| None` | Keep only one packaging ecosystem. Every TypeScript dependency is ``npm``. | +| `declared_in` | `str \| None` | Keep only dependencies declared by one artifact (``PyDependency.declared_in``, e.g. from `get_artifacts`). | + +**Returns:** + +- `List[PyDependency]`: The matching dependencies. + +##### `TypeScriptAnalysis.get_config_keys` + +```python +get_config_keys() -> Dict[str, PyConfigKey] +``` + +Return every configuration key the analyzer extracted from the artifacts. + +**Returns:** + +- `Dict[str, PyConfigKey]`: ``{id: key}``. Each value carries the key's dotted name, its namespace and its literal +- `Dict[str, PyConfigKey]`: value as text. + +##### `TypeScriptAnalysis.get_config_uses` + +```python +get_config_uses(key: str | None = None) -> List[PyConfigUseEdge] +``` + +Return the resolved edges from a code read to the configuration key it names. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | `str \| None` | Keep only edges naming this key by its dotted name (e.g. ``"compilerOptions.strict"``), matched against `get_config_keys`, since the edge itself carries ids. | + +**Returns:** + +- `List[PyConfigUseEdge]`: The matching edges. + +> **See Also** +> `get_config_readers`: The same edges, resolved to their reading callables. +> `get_unresolved_config_reads`: The reads this cannot show. + +##### `TypeScriptAnalysis.get_unresolved_config_reads` + +```python +get_unresolved_config_reads() -> List[PyConfigRead] +``` + +Return every detector-matched configuration read that resolved to no declared key. + +`get_config_uses` can only show reads that landed on a key the analyzer extracted. A +read of a key defined somewhere it does not index resolves to nothing, and an empty +`get_config_uses` for some key cannot then distinguish "nothing reads this" from "a +read exists and never resolved". This is that second list. + +**Returns:** + +- `List[PyConfigRead]`: The unresolved reads, each naming the call site and the callee it went through. + +##### `TypeScriptAnalysis.get_config_readers` + +```python +get_config_readers(key: str) -> List[TSCallableOverview] +``` + +Return overviews of every callable that reads configuration key ``key``. + +`get_config_uses` hands back opaque body-node ids; this answers the question a caller +actually has, *which code* reads this setting. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `key` | `str` | The key's dotted name, exactly as `get_config_uses` matches it. | + +**Returns:** + +- `List[TSCallableOverview]`: One overview per reading callable. Empty means no callable reads this key, see +- `List[TSCallableOverview]`: meth:`get_unresolved_config_reads` for the read that never resolved to one. + ## Schema -TypeScript model package, identity-only schema mirror of codeanalyzer-ts/src/schema.ts. +TypeScript model package, pydantic mirror of codeanalyzer-typescript ``src/schema/schema.ts`` (schema v2). + +``TSCallEdge``, ``TSExternalSymbol``, ``TSSynthesizedCallable``, ``TSClassAttribute``, ``TSEnumMember`` and +``TSVariableDeclaration`` are 1.x names kept as aliases of their v2 classes. + +``TSCallsite`` and ``TSSymbol`` are 1.x *shapes* the v2 wire no longer carries, kept importable for +the same reason: ``TSCallsite`` is still the return type of the call-site accessors, rebuilt from a +``body`` node; ``TSSymbol`` has no v2 counterpart at all (accessed symbols left the schema) and no +accessor returns one -- it is an import alias and nothing more. + +### `TSAnalysis` + +```python +class TSAnalysis(_Base) +``` + +The envelope ``analysis.json`` IS. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `schema_version` | `str` | | +| `language` | `str` | | +| `max_level` | `int` | | +| `k_limit` | `Optional[int]` | | +| `analyzer` | `TSAnalyzer` | | +| `application` | `TSApplication` | | + +### `TSAnalyzer` + +```python +class TSAnalyzer(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `version` | `str` | | ### `TSApplication` @@ -437,68 +1189,126 @@ TypeScript model package, identity-only schema mirror of codeanalyzer-ts/src/sch class TSApplication(_Base) ``` -The root analysis object emitted as analysis.json. +The application root: the containment tree plus the app-scope overlays. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `Literal['application']` | | | `symbol_table` | `Dict[str, TSModule]` | | -| `call_graph` | `List[TSCallEdge]` | | -| `external_symbols` | `Dict[str, TSExternalSymbol]` | | -| `synthesized_callables` | `Dict[str, TSSynthesizedCallable]` | | +| `call_graph` | `List[TSCallGraphEdge]` | | +| `param_in` | `List[TSParamEdge]` | | +| `param_out` | `List[TSParamEdge]` | | +| `artifacts` | `Dict[str, TSArtifact]` | | +| `dependencies` | `List[TSDependency]` | | +| `unresolved_imports` | `List[TSImportBinding]` | | +| `config_uses` | `List[TSConfigUse]` | | +| `config_reads` | `List[TSConfigRead]` | | +| `external_symbols` | `Optional[Dict[str, TSExternalNode]]` | | +| `synthesized_callables` | `Optional[Dict[str, TSSynthesizedNode]]` | | +| `entrypoint_report` | `Optional[TSEntrypointReport]` | | -### `TSCallEdge` +### `TSArtifact` ```python -class TSCallEdge(_Base) +class TSArtifact(_Base) ``` -Identity-only call-graph edge. ``source``/``target`` are ``TSCallable.signature`` strings. +A recognized non-code file (config, manifest, CI, container spec). #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `Literal['artifact']` | | +| `path` | `str` | | +| `format` | `str` | | +| `roles` | `List[str]` | | +| `size_bytes` | `int` | | +| `sha256` | `str` | | | `source` | `str` | | -| `target` | `str` | | -| `type` | `Literal['CALL_DEP']` | | +| `extraction` | `str` | | +| `config_keys` | `List[TSConfigKey]` | | + +### `TSBodyNode` + +```python +class TSBodyNode(_Base) +``` + +One entry of a callable's ``body{}`` map, keyed by local id (``L:C`` or ``@tag``). + +``kind`` is open: L1 emits ``call``/``config_access``, L3 adds ``statement``/``entry``/``exit``, +L4 adds ``formal_in``/``formal_out``/``actual_in``/``actual_out``. ``callee`` is the one +sanctioned ``null`` on the wire (a ``call`` node at L1, refined to an id at L2). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `Optional[str]` | | +| `kind` | `str` | | +| `span` | `Optional[TSSpan]` | | +| `callee` | `Optional[str]` | | +| `of` | `Optional[str]` | | +| `parent` | `Optional[str]` | | +| `method_name` | `Optional[str]` | | +| `receiver_expr` | `Optional[str]` | | +| `receiver_type` | `Optional[str]` | | +| `argument_types` | `List[str]` | | +| `type_arguments` | `List[str]` | | +| `return_type` | `Optional[str]` | | +| `is_constructor_call` | `bool` | | +| `is_optional_chain` | `bool` | | +| `root` | `Optional[str]` | | +| `key` | `Optional[str]` | | + +### `TSCallGraphEdge` + +```python +class TSCallGraphEdge(_Base) +``` + +A wire call-graph edge: ``can://`` endpoints, open provenance tokens (``tsc``, ``defuse``, +``import``, …). + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `prov` | `List[str]` | | | `weight` | `int` | | -| `provenance` | `List[str]` | | -| `tags` | `Dict[str, str]` | | ### `TSCallable` ```python -class TSCallable(_Base) +class TSCallable(_Spanned) ``` A function / method / constructor / accessor / arrow function. +``cfg``/``cdg``/``ddg`` are present from L3, ``summary`` from L4; ``None`` means the level did +not compute them, ``[]`` means it did and found none. + #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `str` | | | `name` | `str` | | -| `path` | `str` | | | `signature` | `str` | | | `comments` | `List[TSComment]` | | | `decorators` | `List[TSDecorator]` | | | `parameters` | `List[TSCallableParameter]` | | | `type_parameters` | `List[TSTypeParameter]` | | | `return_type` | `Optional[str]` | | -| `code` | `Optional[str]` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `code_start_line` | `int` | | -| `accessed_symbols` | `List[TSSymbol]` | | -| `call_sites` | `List[TSCallsite]` | | -| `inner_callables` | `Dict[str, 'TSCallable']` | | -| `inner_classes` | `Dict[str, 'TSClass']` | | -| `local_variables` | `List[TSVariableDeclaration]` | | | `cyclomatic_complexity` | `int` | | -| `entrypoints` | `List['TSEntrypoint']` | | -| `kind` | `str` | | | `accessibility` | `Optional[str]` | | | `is_static` | `bool` | | | `is_abstract` | `bool` | | @@ -511,6 +1321,126 @@ A function / method / constructor / accessor / arrow function. | `is_implicit` | `bool` | | | `accessor_kind` | `Optional[str]` | | | `overload_signatures` | `List[TSOverloadSignature]` | | +| `body` | `Dict[str, TSBodyNode]` | | +| `callables` | `Dict[str, 'TSCallable']` | | +| `types` | `Dict[str, 'TSType']` | | +| `cfg` | `Optional[List[TSCfgEdge]]` | | +| `cdg` | `Optional[List[TSCdgEdge]]` | | +| `ddg` | `Optional[List[TSDdgEdge]]` | | +| `summary` | `Optional[List[TSSummaryEdge]]` | | +| `entrypoints` | `Optional[List[TSEntrypoint]]` | | +| `is_entrypoint` | `Optional[bool]` | | +| `inner_callables` | `Dict[str, 'TSCallable']` | | +| `inner_classes` | `Dict[str, 'TSClass']` | | + +### `TSCallableOverview` + +```python +class TSCallableOverview(BaseModel) +``` + +A lightweight projection of one callable, enough to enumerate and filter without the full +`TSCallable` reconstruction (call-sites, inner callables, +locals). + +Returned set-at-a-time by ``TypescriptAnalysis.get_callables_overview`` / +``TypescriptAnalysis.get_decorated_callables``. Body-inspect only the few you need afterwards +via ``TypescriptAnalysis.get_method``/``TypescriptAnalysis.get_method_bodies``. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signature` | `str` | | +| `name` | `str` | | +| `owner_signature` | `Optional[str]` | | +| `owner_kind` | `Optional[str]` | | +| `kind` | `str` | | +| `path` | `str` | | +| `start_line` | `int` | | +| `end_line` | `int` | | +| `decorators` | `List[str]` | | +| `is_exported` | `bool` | | +| `is_async` | `bool` | | +| `is_static` | `bool` | | +| `accessibility` | `Optional[str]` | | + +#### Methods + +##### `TSCallableOverview.from_callable` + +```python +from_callable(c: TSCallable, owner_signature: Optional[str], owner_kind: Optional[str], path: str) -> TSCallableOverview +``` + +Project a `TSCallable` into a +`TSCallableOverview`. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `c` | `TSCallable` | The callable to project. | +| `owner_signature` | `Optional[str]` | Signature of the declaring class/interface, or ``None`` for a module-level function, arrow, or namespace-owned function. | +| `owner_kind` | `Optional[str]` | The owner's node kind (``"class"`` or ``"interface"``), or ``None`` when ``owner_signature`` is ``None``. | +| `path` | `str` | The declaring module's symbol-table key (repo-relative path). The v2 callable does not carry it; the caller iterating ``symbol_table`` does. | + +**Returns:** + +- `TSCallableOverview`: The projected overview. + +### `TSClassOverview` + +```python +class TSClassOverview(BaseModel) +``` + +A lightweight projection of one class, the class-level counterpart to +`TSCallableOverview`, for classes codeanalyzer-typescript marked as entrypoints in +their own right (``TSClass.is_entrypoint``), independently of any individual method. + +Returned by ``TypeScriptAnalysis.get_entrypoint_classes``. It mirrors +`PyClassOverview` field for field, because the accessor that +returns it mirrors ``PythonAnalysis.get_entrypoint_classes``. + +**Classes only, on purpose.** ``is_entrypoint`` is declared on all five TypeScript type kinds +(``TSClass``/``TSInterface``/``TSEnum``/``TSTypeAlias``/``TSNamespace`` all inherit it), but +the Neo4j projection stamps it onto ``:TSCallable`` and ``:TSClass`` nodes only, measured on +the 1.3.0 reference graph, where those are the only two labels carrying the property at all. +Widening this accessor past classes would therefore answer differently on the two backends, +which is the one thing the query surface may not do. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `signature` | `str` | | +| `name` | `str` | | +| `path` | `str` | | +| `start_line` | `int` | | +| `end_line` | `int` | | +| `decorators` | `List[str]` | | + +#### Methods + +##### `TSClassOverview.from_class` + +```python +from_class(c: TSClass, path: str) -> TSClassOverview +``` + +Project a `TSClass` into a `TSClassOverview`. + +**Parameters:** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `c` | `TSClass` | The class to project. | +| `path` | `str` | The declaring module's symbol-table key (repo-relative path). The v2 type does not carry it; the caller iterating ``symbol_table`` does. | + +**Returns:** + +- `TSClassOverview`: The projected overview. ### `TSCallableParameter` @@ -524,6 +1454,7 @@ A function / method parameter. | Name | Type | Description | | ---- | ---- | ----------- | +| `id` | `Optional[str]` | | | `name` | `str` | | | `type` | `Optional[str]` | | | `default_value` | `Optional[str]` | | @@ -543,8 +1474,8 @@ A function / method parameter. class TSCallsite(_Base) ``` -Rich per-call metadata, attached to the caller. ``callee_signature`` is backfilled by the -resolver call graph. +1.x per-call record. Not on the v2 wire (its view is the ``call`` node in ``body{}``); +kept so callers that construct or type-check against it keep importing. #### Attributes @@ -564,60 +1495,57 @@ resolver call graph. | `end_line` | `int` | | | `end_column` | `int` | | -### `TSClass` +### `TSCdgEdge` ```python -class TSClass(_Base) +class TSCdgEdge(_Base) ``` -A class declaration. +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | + +### `TSCfgEdge` + +```python +class TSCfgEdge(_Base) +``` #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `signature` | `str` | | -| `comments` | `List[TSComment]` | | -| `code` | `Optional[str]` | | -| `decorators` | `List[TSDecorator]` | | -| `base_classes` | `List[str]` | | -| `implements_types` | `List[str]` | | -| `type_parameters` | `List[TSTypeParameter]` | | -| `methods` | `Dict[str, TSCallable]` | | -| `attributes` | `Dict[str, TSClassAttribute]` | | -| `inner_classes` | `Dict[str, 'TSClass']` | | -| `entrypoints` | `List['TSEntrypoint']` | | -| `is_abstract` | `bool` | | -| `is_exported` | `bool` | | -| `is_ambient` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | +| `src` | `str` | | +| `dst` | `str` | | +| `kind` | `str` | | -### `TSClassAttribute` +### `TSClass` ```python -class TSClassAttribute(_Base) +class TSClass(_Type) ``` -A class property / field (also covers constructor parameter-properties). +A class declaration. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `type` | `Optional[str]` | | -| `comments` | `List[TSComment]` | | +| `kind` | `Literal['class']` | | +| `callables` | `Dict[str, TSCallable]` | | +| `fields` | `Dict[str, TSField]` | | | `decorators` | `List[TSDecorator]` | | -| `initializer` | `Optional[str]` | | -| `accessibility` | `Optional[str]` | | -| `is_static` | `bool` | | -| `is_readonly` | `bool` | | -| `is_optional` | `bool` | | +| `base_classes` | `List[str]` | | +| `implements_types` | `List[str]` | | | `is_abstract` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | +| `type_parameters` | `List[TSTypeParameter]` | | +| `extends_ids` | `List[str]` | | +| `implements_ids` | `List[str]` | | +| `methods` | `Dict[str, TSCallable]` | | +| `attributes` | `Dict[str, TSField]` | | ### `TSComment` @@ -638,6 +1566,74 @@ A comment or JSDoc block. | `start_column` | `int` | | | `end_column` | `int` | | +### `TSConfigKey` + +```python +class TSConfigKey(_Base) +``` + +A configuration key flattened out of a config-bearing artifact. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `key` | `str` | | +| `namespace` | `str` | | +| `value` | `Optional[Union[str, int, float, bool]]` | | +| `span` | `Optional[TSSpan]` | | +| `references` | `List[str]` | | + +### `TSConfigRead` + +```python +class TSConfigRead(_Base) +``` + +A recognized config read that resolved to no declared key. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `site` | `str` | | +| `callee` | `str` | | +| `key` | `Optional[str]` | | +| `reason` | `str` | | +| `prov` | `List[str]` | | + +### `TSConfigUse` + +```python +class TSConfigUse(_Base) +``` + +A recognized config read (global ordinal ``src``) joined to the ``TSConfigKey`` it names. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `prov` | `List[str]` | | + +### `TSDdgEdge` + +```python +class TSDdgEdge(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `var` | `Optional[str]` | | +| `prov` | `List[str]` | | + ### `TSDecorator` ```python @@ -659,64 +1655,82 @@ A decorator applied to a class / member / parameter (structured, with arguments) | `start_column` | `int` | | | `end_column` | `int` | | +### `TSDependency` + +```python +class TSDependency(_Base) +``` + +One third-party dependency, evidence-tagged via ``prov``. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `str` | | +| `spec` | `str` | | +| `kind` | `str` | | +| `extras` | `List[str]` | | +| `declared_in` | `str` | | +| `direct` | `bool` | | +| `locked_version` | `Optional[str]` | | +| `provides_imports` | `List[str]` | | +| `prov` | `List[str]` | | + ### `TSEntrypoint` ```python class TSEntrypoint(_Base) ``` -A framework entrypoint (populated by level-2 finders; empty for level 1). Embedded on the -owning ``TSCallable``/``TSClass``, so it carries no signature/source_file of its own. +One way a callable or class is invoked from outside the application. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | | `framework` | `str` | | -| `detection_source` | `str` | | -| `route_path` | `Optional[str]` | | +| `confidence` | `str` | | +| `rule` | `str` | | +| `ruleset` | `str` | | +| `evidence` | `Optional[str]` | | +| `route` | `Optional[str]` | | | `http_methods` | `List[str]` | | -| `tags` | `Dict[str, str]` | | +| `via` | `Optional[str]` | | -### `TSEnum` +### `TSEntrypointReport` ```python -class TSEnum(_Base) +class TSEntrypointReport(_Base) ``` -An enum declaration (TS node kind). +Coverage and failure record for the entrypoint pass. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `signature` | `str` | | -| `comments` | `List[TSComment]` | | -| `code` | `Optional[str]` | | -| `members` | `List[TSEnumMember]` | | -| `is_const` | `bool` | | -| `is_exported` | `bool` | | -| `is_ambient` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | +| `frameworks_detected` | `List[str]` | | +| `rulesets` | `List[str]` | | +| `unresolved` | `Dict[str, int]` | | +| `errors` | `List[str]` | | -### `TSEnumMember` +### `TSEnum` ```python -class TSEnumMember(_Base) +class TSEnum(_Type) ``` -A member of an enum. +An enum declaration; its members are ``fields`` carrying ``value``. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `value` | `Optional[str]` | | -| `start_line` | `int` | | -| `end_line` | `int` | | +| `kind` | `Literal['enum']` | | +| `fields` | `Dict[str, TSField]` | | +| `is_const` | `bool` | | +| `members` | `List[TSField]` | | ### `TSExport` @@ -731,6 +1745,7 @@ A TypeScript export / re-export binding. | Name | Type | Description | | ---- | ---- | ----------- | | `module` | `Optional[str]` | | +| `resolved_module` | `Optional[str]` | | | `name` | `str` | | | `alias` | `Optional[str]` | | | `is_type_only` | `bool` | | @@ -740,26 +1755,60 @@ A TypeScript export / re-export binding. | `start_column` | `int` | | | `end_column` | `int` | | -### `TSExternalSymbol` +### `TSExternalNode` ```python -class TSExternalSymbol(_Base) +class TSExternalNode(_Base) ``` -A WALA-style phantom node: a synthetic stub for a call target OUTSIDE the project (an -imported/required library member). An edge's ``target`` byte-matches either a real -``TSCallable.signature`` or a ``TSExternalSymbol.signature``, so the call graph stays -dangling-free while still recording external (e.g. sink) calls. +A call target outside the project (library member / builtin), homed on the application +as ``/@external//``, keyed by that id. -Slim: the map key in ``TSApplication.external_symbols`` IS the signature (e.g. -``commander.parse``), and membership already means external, so neither is repeated here. +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `str` | | +| `module` | `str` | | +| `name` | `str` | | + +### `TSField` + +```python +class TSField(_Base) +``` + +One open shape for a module variable, a class attribute / interface property, a +constructor parameter property and an enum member; each origin sets its own subset. +``span`` is absent for constructor parameter properties, in which case the 1.x line/column +properties return ``-1``. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | +| `id` | `str` | | +| `kind` | `Literal['field']` | | +| `span` | `Optional[TSSpan]` | | | `name` | `str` | | -| `module` | `str` | | +| `type` | `Optional[str]` | | +| `initializer` | `Optional[str]` | | +| `scope` | `Optional[str]` | | +| `declaration_kind` | `Optional[str]` | | +| `is_exported` | `bool` | | +| `comments` | `List[TSComment]` | | +| `decorators` | `List[TSDecorator]` | | +| `accessibility` | `Optional[str]` | | +| `is_static` | `bool` | | +| `is_readonly` | `bool` | | +| `is_optional` | `bool` | | +| `is_abstract` | `bool` | | +| `value` | `Optional[str]` | | +| `start_line` | `int` | | +| `end_line` | `int` | | +| `start_column` | `int` | | +| `end_column` | `int` | | ### `TSImport` @@ -774,6 +1823,7 @@ A TypeScript import binding (one entry per imported name). | Name | Type | Description | | ---- | ---- | ----------- | | `module` | `str` | | +| `resolved_module` | `Optional[str]` | | | `name` | `str` | | | `alias` | `Optional[str]` | | | `is_type_only` | `bool` | | @@ -783,32 +1833,44 @@ A TypeScript import binding (one entry per imported name). | `start_column` | `int` | | | `end_column` | `int` | | +### `TSImportBinding` + +```python +class TSImportBinding(_Base) +``` + +A non-relative import no declared dependency accounts for. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `module` | `str` | | +| `bound_to` | `Optional[str]` | | +| `prov` | `List[str]` | | + ### `TSInterface` ```python -class TSInterface(_Base) +class TSInterface(_Type) ``` -An interface declaration (TS node kind). +An interface declaration. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `signature` | `str` | | -| `comments` | `List[TSComment]` | | -| `code` | `Optional[str]` | | +| `kind` | `Literal['interface']` | | +| `callables` | `Dict[str, TSCallable]` | | +| `fields` | `Dict[str, TSField]` | | | `base_classes` | `List[str]` | | | `type_parameters` | `List[TSTypeParameter]` | | -| `methods` | `Dict[str, TSCallable]` | | -| `properties` | `Dict[str, TSClassAttribute]` | | | `call_signatures` | `List[str]` | | | `index_signatures` | `List[str]` | | -| `is_exported` | `bool` | | -| `is_ambient` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | +| `extends_ids` | `List[str]` | | +| `methods` | `Dict[str, TSCallable]` | | +| `properties` | `Dict[str, TSField]` | | ### `TSModule` @@ -816,56 +1878,55 @@ An interface declaration (TS node kind). class TSModule(_Base) ``` -A compilation unit (a .ts/.tsx file). +A compilation unit (one ``.ts``/``.tsx``/``.js`` file). The symbol-table key is its +repo-relative path; the wire carries no ``file_path``/``module_name``. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `file_path` | `str` | | -| `module_name` | `str` | | +| `id` | `str` | | +| `kind` | `Literal['module']` | | +| `span` | `TSSpan` | | +| `source` | `str` | | | `imports` | `List[TSImport]` | | | `exports` | `List[TSExport]` | | | `comments` | `List[TSComment]` | | +| `types` | `Dict[str, TSType]` | | +| `functions` | `Dict[str, TSCallable]` | | +| `fields` | `Dict[str, TSField]` | | +| `is_tsx` | `bool` | | +| `is_declaration_file` | `bool` | | +| `content_hash` | `Optional[str]` | | | `classes` | `Dict[str, TSClass]` | | | `interfaces` | `Dict[str, TSInterface]` | | | `enums` | `Dict[str, TSEnum]` | | | `type_aliases` | `Dict[str, TSTypeAlias]` | | -| `functions` | `Dict[str, TSCallable]` | | | `namespaces` | `Dict[str, TSNamespace]` | | -| `variables` | `List[TSVariableDeclaration]` | | -| `is_tsx` | `bool` | | -| `is_declaration_file` | `bool` | | -| `content_hash` | `Optional[str]` | | -| `last_modified` | `Optional[float]` | | -| `file_size` | `Optional[int]` | | +| `variables` | `List[TSField]` | | ### `TSNamespace` ```python -class TSNamespace(_Base) +class TSNamespace(_Type) ``` -A namespace / module block (TS node kind), recursive container. +A namespace / module block, a nested scope with the same buckets as a module. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `signature` | `str` | | -| `comments` | `List[TSComment]` | | +| `kind` | `Literal['namespace']` | | +| `types` | `Dict[str, 'TSType']` | | +| `functions` | `Dict[str, TSCallable]` | | +| `fields` | `Dict[str, TSField]` | | | `classes` | `Dict[str, TSClass]` | | | `interfaces` | `Dict[str, TSInterface]` | | | `enums` | `Dict[str, TSEnum]` | | | `type_aliases` | `Dict[str, TSTypeAlias]` | | -| `functions` | `Dict[str, TSCallable]` | | -| `variables` | `List[TSVariableDeclaration]` | | | `namespaces` | `Dict[str, 'TSNamespace']` | | -| `is_exported` | `bool` | | -| `is_ambient` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | +| `variables` | `List[TSField]` | | ### `TSOverloadSignature` @@ -885,13 +1946,60 @@ An overload signature attached to the implementation callable. | `start_line` | `int` | | | `end_line` | `int` | | +### `TSParamEdge` + +```python +class TSParamEdge(_Base) +``` + +An L4 ``param_in``/``param_out`` edge with **global** ordinal endpoints. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `var` | `Optional[str]` | | + +### `TSSpan` + +```python +class TSSpan(_Base) +``` + +``start``/``end`` are ``[line, column]`` (1-based); ``bytes`` are ``[from, to]`` offsets into +the owning module's ``source``. + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `start` | `Tuple[int, int]` | | +| `end` | `Tuple[int, int]` | | +| `bytes` | `Tuple[int, int]` | | + +### `TSSummaryEdge` + +```python +class TSSummaryEdge(_Base) +``` + +#### Attributes + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `src` | `str` | | +| `dst` | `str` | | +| `var` | `Optional[str]` | | + ### `TSSymbol` ```python class TSSymbol(_Base) ``` -A symbol referenced or declared in code. +1.x accessed-symbol record. Not on the v2 wire; kept importable. #### Attributes @@ -906,52 +2014,41 @@ A symbol referenced or declared in code. | `lineno` | `int` | | | `col_offset` | `int` | | -### `TSSynthesizedCallable` +### `TSSynthesizedNode` ```python -class TSSynthesizedCallable(_Base) +class TSSynthesizedNode(_Base) ``` -A first-party anonymous callback that Jelly resolves as a call-graph endpoint but the symbol -table never names (the canonicalizer returns ``null`` for anonymous functions). Materialized so -that ``call_graph`` edges to anonymous callbacks don't dangle. - -Slim, like `TSExternalSymbol`: the map key in ``TSApplication.synthesized_callables`` IS -the synthesized signature ``:``, so an edge's -``source``/``target`` byte-matches it just like a real ``TSCallable.signature``. The ``tsc`` -resolver emits an empty map; Jelly (the union default) populates it. +An entry of the anonymous-callable compatibility index: the map key is the **older** id +(``@:``) and ``id`` the tree id that replaced it; a residual fallback +node (no tree home) has key == ``id`` and carries ``name``/``path``/``span``. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `path` | `str` | | -| `start_line` | `int` | | -| `start_column` | `int` | | +| `id` | `str` | | +| `kind` | `str` | | +| `name` | `Optional[str]` | | +| `path` | `Optional[str]` | | +| `span` | `Optional[TSSpan]` | | ### `TSTypeAlias` ```python -class TSTypeAlias(_Base) +class TSTypeAlias(_Type) ``` -A type-alias declaration (TS node kind). +A type-alias declaration. #### Attributes | Name | Type | Description | | ---- | ---- | ----------- | -| `name` | `str` | | -| `signature` | `str` | | -| `comments` | `List[TSComment]` | | -| `code` | `Optional[str]` | | +| `kind` | `Literal['type_alias']` | | | `aliased_type` | `str` | | | `type_parameters` | `List[TSTypeParameter]` | | -| `is_exported` | `bool` | | -| `is_ambient` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | ### `TSTypeParameter` @@ -969,30 +2066,5 @@ A generic type parameter, e.g. ``T extends Base = Default``. | `constraint` | `Optional[str]` | | | `default` | `Optional[str]` | | -### `TSVariableDeclaration` - -```python -class TSVariableDeclaration(_Base) -``` - -A variable / const / let declaration. - -#### Attributes - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `name` | `str` | | -| `type` | `Optional[str]` | | -| `initializer` | `Optional[str]` | | -| `value` | `Optional[Any]` | | -| `scope` | `str` | | -| `declaration_kind` | `str` | | -| `is_readonly` | `bool` | | -| `is_exported` | `bool` | | -| `start_line` | `int` | | -| `end_line` | `int` | | -| `start_column` | `int` | | -| `end_column` | `int` | | -