diff --git a/docs/schemas/draft/hierarchy.md b/docs/schemas/draft/hierarchy.md index e2c5056..5cbf934 100644 --- a/docs/schemas/draft/hierarchy.md +++ b/docs/schemas/draft/hierarchy.md @@ -1,195 +1,241 @@ -# Unified Hierarchy API +# Hierarchy APIs -The Unified Hierarchy API provides a single, consistent interface for tracing hierarchical relationships in code. It traces two types of hierarchies: +Two separate APIs for tracing hierarchical relationships in code. -- **Call Hierarchy**: Function/method call relationships (who calls whom) -- **Type Hierarchy**: Class/interface inheritance relationships (parent-child) +--- -Both hierarchies are modeled as **directed graph traversal** with generic "incoming"/"outgoing" direction terminology. +## Call Hierarchy API -## HierarchyRequest +The Call Hierarchy API traces function/method call relationships. -Traces hierarchical relationships starting from a symbol. +### CallHierarchyRequest -| Field | Type | Default | Description | -| :----------------- | :--------------------------------------- | :------- | :---------------------------------------------- | -| `locate` | [`Locate`](../locate.md) | Required | The symbol to start tracing from. | -| `hierarchy_type` | `"call"` \| `"type"` | Required | Type of hierarchy to trace. | -| `direction` | `"incoming"` \| `"outgoing"` \| `"both"` | `"both"` | Graph traversal direction. | -| `depth` | `number` | `2` | Maximum traversal depth (number of hops). | -| `include_external` | `boolean` | `false` | Whether to include external library references. | +| Field | Type | Default | Description | +| :----------------- | :--------------------------------------- | :------- | :--------------------------------- | +| `locate` | [`Locate`](../locate.md) | Required | The function/method to trace from. | +| `direction` | `"incoming"` \| `"outgoing"` \| `"both"` | `"both"` | Traversal direction. | +| `depth` | `number` | `2` | Maximum traversal depth. | +| `include_external` | `boolean` | `false` | Include external library calls. | -### Direction Parameter +### CallHierarchyResponse -Direction uses **generic graph terminology**, not hierarchy-specific terms: +| Field | Type | Description | +| :--------------- | :--------------------------------- | :-------------------------- | +| `root` | `HierarchyNode` | The starting symbol. | +| `nodes` | `Map` | All nodes in the graph. | +| `edges_incoming` | `Map` | Caller edges. | +| `edges_outgoing` | `Map` | Callee edges. | +| `items_incoming` | `HierarchyItem[]` | Flattened caller tree. | +| `items_outgoing` | `HierarchyItem[]` | Flattened callee tree. | +| `direction` | `string` | The direction used. | +| `depth` | `number` | The depth used. | -- `"incoming"`: Trace predecessors in the graph - - For call hierarchy: find **callers** (who calls this function?) - - For type hierarchy: find **parent classes/interfaces** (what does this inherit from?) +### Example Usage -- `"outgoing"`: Trace successors in the graph - - For call hierarchy: find **callees** (what does this function call?) - - For type hierarchy: find **child classes** (what inherits from this?) +#### Request -- `"both"`: Trace both directions - -### Usage Examples +```json +{ + "locate": { + "file_path": "src/utils.py", + "scope": { "symbol_path": ["process_data"] } + }, + "direction": "both", + "depth": 2 +} +``` -**Example 1: Find who calls a function** +#### Markdown Rendered for LLM -```python -HierarchyRequest( - hierarchy_type="call", - locate=Locate( - file_path="src/main.py", - scope=LineScope(line=10), - find="process_data" - ), - direction="incoming", # Find callers - depth=2 -) -``` +````markdown +# Call Hierarchy: `process_data` -**Example 2: Find what a function calls** - -```python -HierarchyRequest( - hierarchy_type="call", - locate=Locate( - file_path="src/main.py", - scope=LineScope(line=10), - find="process_data" - ), - direction="outgoing", # Find callees - depth=2 -) -``` +Root: `process_data` (`Function`) at `src/utils.py` +Detail: Process and transform input data -**Example 3: Find parent classes** - -```python -HierarchyRequest( - hierarchy_type="type", - locate=Locate( - file_path="src/models.py", - scope=LineScope(line=5), - find="UserModel" - ), - direction="incoming", # Find parents - depth=2 -) -``` +## Callers (incoming) -**Example 4: Find child classes** - -```python -HierarchyRequest( - hierarchy_type="type", - locate=Locate( - file_path="src/models.py", - scope=LineScope(line=5), - find="BaseModel" - ), - direction="outgoing", # Find children - depth=2 -) -``` +### `main` +main -> process_data +- Kind: `Function` +- File: `src/main.py` +- Detail: Application entry point -## HierarchyResponse +#### `cli_handler` +cli_handler -> main -> process_data +- Kind: `Function` +- File: `src/cli.py` +- Detail: CLI argument handler -Contains the hierarchy graph and flattened tree for rendering. +### `batch_runner` +batch_runner -> process_data +- Kind: `Function` +- File: `src/batch.py` +- Detail: Batch processing entry -| Field | Type | Description | -| :--------------- | :----------------------------- | :----------------------------------------------------------- | -| `hierarchy_type` | `"call"` \| `"type"` | Type of hierarchy returned. | -| `root` | `HierarchyNode` | The starting node. | -| `nodes` | `Map` | All nodes in the hierarchy graph. | -| `edges_incoming` | `Map` | Incoming edges for each node (predecessors). | -| `edges_outgoing` | `Map` | Outgoing edges for each node (successors). | -| `items_incoming` | `HierarchyItem[]` | Flattened list of incoming relationships for tree rendering. | -| `items_outgoing` | `HierarchyItem[]` | Flattened list of outgoing relationships for tree rendering. | -| `direction` | `string` | The direction that was used. | -| `depth` | `number` | The depth that was used. | +## Callees (outgoing) -### HierarchyNode +### `validate` +process_data -> validate +- Kind: `Function` +- File: `src/validation.py` +- Detail: Validate input format -Represents a symbol in the hierarchy graph. +#### `check_schema` +process_data -> validate -> check_schema +- Kind: `Function` +- File: `src/validation.py` +- Detail: Check JSON schema -| Field | Type | Description | -| :------------ | :--------------- | :--------------------------------------- | -| `id` | `string` | Unique identifier for the node. | -| `name` | `string` | Name of the symbol. | -| `kind` | `string` | Symbol kind (e.g., `Function`, `Class`). | -| `file_path` | `string` | Path to the file. | -| `range_start` | `Position` | Start position of the symbol definition. | -| `detail` | `string \| null` | Optional detail about the symbol. | +### `transform` +process_data -> transform +- Kind: `Function` +- File: `src/transform.py` +- Detail: Transform data format +```` + +--- + +## Type Hierarchy API + +The Type Hierarchy API traces class/interface inheritance relationships. + +### TypeHierarchyRequest + +| Field | Type | Default | Description | +| :---------- | :----------------------------------------- | :------- | :--------------------------------- | +| `locate` | [`Locate`](../locate.md) | Required | The class/interface to trace from. | +| `direction` | `"supertypes"` \| `"subtypes"` \| `"both"` | `"both"` | Traversal direction. | +| `depth` | `number` | `2` | Maximum traversal depth. | + +### TypeHierarchyResponse + +| Field | Type | Description | +| :----------------- | :--------------------------------- | :-------------------------- | +| `root` | `HierarchyNode` | The starting symbol. | +| `nodes` | `Map` | All nodes in the graph. | +| `edges_supertypes` | `Map` | Parent edges. | +| `edges_subtypes` | `Map` | Child edges. | +| `items_supertypes` | `HierarchyItem[]` | Flattened supertype tree. | +| `items_subtypes` | `HierarchyItem[]` | Flattened subtype tree. | +| `direction` | `string` | The direction used. | +| `depth` | `number` | The depth used. | + +### Example Usage + +#### Request + +```json +{ + "locate": { + "file_path": "src/models.py", + "scope": { "symbol_path": ["UserModel"] } + }, + "direction": "both", + "depth": 2 +} +``` -### HierarchyItem +#### Markdown Rendered for LLM -Represents an item in the flattened hierarchy tree for rendering. +````markdown +# Type Hierarchy: `UserModel` -| Field | Type | Description | -| :---------- | :--------------- | :------------------------------------ | -| `name` | `string` | Name of the symbol. | -| `kind` | `string` | Symbol kind. | -| `file_path` | `string` | Path to the file. | -| `level` | `number` | Nesting level in the tree (0 = root). | -| `detail` | `string \| null` | Optional detail. | -| `is_cycle` | `boolean` | Whether this represents a cycle. | +Root: `UserModel` (`Class`) at `src/models.py` +Detail: User data model with validation -### HierarchyEdge +## Supertypes (parents) -Represents a directed edge in the hierarchy graph. +### `BaseModel` +UserModel <- BaseModel +- Kind: `Class` +- File: `src/base.py` +- Detail: Base model with serialization -The `metadata` field contains hierarchy-specific information: +#### `object` +UserModel <- BaseModel <- object +- Kind: `Class` +- File: `builtins` -| Field | Type | Description | -| :------------- | :--------------------------------------------- | :-------------------------------- | -| `from_node_id` | `string` | ID of the source node. | -| `to_node_id` | `string` | ID of the target node. | -| `metadata` | `CallEdgeMetadata \| TypeEdgeMetadata \| null` | Hierarchy-specific edge metadata. | +### `Serializable` +UserModel <- Serializable +- Kind: `Interface` +- File: `src/interfaces.py` +- Detail: JSON serialization interface -**CallEdgeMetadata** (for call hierarchy): +## Subtypes (children) -- `call_sites`: `Position[]` - Exact positions where the call occurs +### `AdminUser` +UserModel -> AdminUser +- Kind: `Class` +- File: `src/admin.py` +- Detail: Admin user with elevated permissions -**TypeEdgeMetadata** (for type hierarchy): +### `GuestUser` +UserModel -> GuestUser +- Kind: `Class` +- File: `src/guest.py` +- Detail: Guest user with limited access -- `relationship`: `"extends" \| "implements"` - Type of inheritance relationship +#### `AnonymousUser` +UserModel -> GuestUser -> AnonymousUser +- Kind: `Class` +- File: `src/guest.py` +- Detail: Unauthenticated user +```` -## Output Format +--- -The response includes a markdown template that adapts to the hierarchy type: +## Shared Models -```markdown -# process_data Hierarchy (call, depth: 2) +### HierarchyNode -## Incoming +| Field | Type | Description | +| :------------ | :--------------- | :--------------- | +| `id` | `string` | Unique ID. | +| `name` | `string` | Symbol name. | +| `kind` | `string` | Symbol kind. | +| `file_path` | `string` | File path. | +| `range_start` | `Position` | Start position. | +| `detail` | `string \| null` | Optional detail. | -### main +### HierarchyItem -- Kind: `Function` -- File: `src/main.py` +| Field | Type | Description | +| :---------- | :--------------- | :------------------------------------------------ | +| `name` | `string` | Symbol name. | +| `kind` | `string` | Symbol kind. | +| `file_path` | `string` | File path. | +| `level` | `number` | Depth level (1 = direct child of root). | +| `chain` | `string[]` | Full path from this node to root. | +| `detail` | `string \| null` | Optional detail. | +| `is_cycle` | `boolean` | Whether this node creates a cycle. | -## Outgoing +### CallHierarchyEdge -### validate_input +| Field | Type | Description | +| :------------- | :----------- | :------------------------------- | +| `from_node_id` | `string` | ID of the caller. | +| `to_node_id` | `string` | ID of the callee. | +| `call_sites` | `CallSite[]` | Positions where the call occurs. | -- Kind: `Function` -- File: `src/utils.py` -``` +### CallSite -## Design Principles +| Field | Type | Description | +| :--------- | :--------------- | :-------------------- | +| `position` | `Position` | Position of the call. | +| `snippet` | `string \| null` | Optional code snippet.| -The API is designed around these principles: +### TypeHierarchyEdge -1. **Graph abstraction**: Both hierarchies are directed graphs with generic "incoming"/"outgoing" terminology -2. **Polymorphic metadata**: Edge metadata uses Union types, not conditional null fields -3. **Consistent naming**: Same field names for both hierarchy types (`edges_incoming`/`edges_outgoing`, `items_incoming`/`items_outgoing`) -4. **Semantic control**: `hierarchy_type` parameter controls interpretation, not structure +| Field | Type | Description | +| :------------- | :------------- | :--------------------------------- | +| `from_node_id` | `string` | ID of the child class. | +| `to_node_id` | `string` | ID of the parent class. | +| `relation` | `TypeRelation` | The inheritance relationship type. | -## Related APIs +### TypeRelation -- [`Locate`](../locate.md) - For specifying symbol location -- [`Reference`](../reference.md) - For finding all references to a symbol -- [`Symbol`](../symbol.md) - For inspecting symbol details +| Field | Type | Description | +| :----- | :---------------------------- | :-------------------- | +| `kind` | `"extends"` \| `"implements"` | Extends or implements.| diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..d8cfc63 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,62 @@ +target-version = "py312" +line-length = 88 + +[lint] +select = [ + "F", # Pyflakes - catches logic errors + "E", "W", # pycodestyle - PEP 8 style checking + "I", # isort - import sorting + "B", # flake8-bugbear - common gotchas + "UP", # pyupgrade - modern Python syntax + "SIM", # flake8-simplify - suggests simpler logic + "PL", # Pylint - comprehensive code quality + "ANN", # flake8-annotations - type hints (ANN401 bans Any) + "RUF", # Ruff-specific rules + "PERF", # Perflint - performance anti-patterns + "BLE", # flake8-blind-except - exception handling + "FA", # flake8-future-annotations + "TRY", # tryceratops - clean exception handling + "RSE", # flake8-raise - raise statement best practices + "C4", # flake8-comprehensions - better comprehensions + "C90", # mccabe - cyclomatic complexity + "S", # flake8-bandit - security checks + "PT", # flake8-pytest-style - pytest best practices + "DTZ", # flake8-datetimez - timezone-aware datetime + "PIE", # flake8-pie - misc code improvements + "RET", # flake8-return - return statement improvements + "PTH", # flake8-use-pathlib - prefer pathlib over os.path + "TCH", # flake8-type-checking - TYPE_CHECKING optimization + "LOG", # flake8-logging - logging best practices + "PGH", # pygrep-hooks - ban blanket type: ignore (PGH003) +] + +ignore = [ + "E501", # Line length (handled by formatter) + "COM812", # Trailing comma (conflicts with formatter) + "ISC001", # Implicit string concatenation (conflicts with formatter) + "PLR0913", # Too many arguments - sometimes necessary + "PLR2004", # Magic value comparison - sometimes reasonable + "TRY003", # Long exception message - custom error messages are common +] + +[lint.mccabe] +max-complexity = 10 + +[lint.pylint] +max-args = 6 +max-branches = 12 + +[lint.isort] +force-single-line = false +lines-after-imports = 2 +known-first-party = ["lsap"] + +[lint.per-file-ignores] +"tests/**/*.py" = ["ANN", "S101", "PLR2004"] +"**/test_*.py" = ["ANN", "S101", "PLR2004"] +"conftest.py" = ["ANN"] +"__init__.py" = ["F401"] # Allow unused imports for re-export + +[format] +quote-style = "double" +indent-style = "space" diff --git a/src/lsap/schema/draft/hierarchy.py b/src/lsap/schema/draft/hierarchy.py index 3b3c3e3..9419580 100644 --- a/src/lsap/schema/draft/hierarchy.py +++ b/src/lsap/schema/draft/hierarchy.py @@ -1,18 +1,15 @@ from pathlib import Path -from typing import Annotated, Final, Literal, Union +from typing import Final, Literal from pydantic import BaseModel, ConfigDict, Field from lsap.schema.abc import Response -from lsap.schema.locate import LocateRequest, Position +from lsap.schema.locate import LocateRequest +from lsap.schema.models import Position class HierarchyNode(BaseModel): - """ - Represents a node in a hierarchy graph. - - Applicable to any hierarchical relationship: function calls, type inheritance, etc. - """ + """Represents a symbol node in a hierarchy graph.""" id: str name: str @@ -23,204 +20,242 @@ class HierarchyNode(BaseModel): class HierarchyItem(BaseModel): - """ - Represents an item in a flattened hierarchy tree for rendering. - - Applicable to any hierarchical relationship. - """ + """A node in the flattened hierarchy tree with its path from root.""" name: str kind: str file_path: Path level: int + """Depth level (1 = direct caller/callee of root)""" + chain: list[str] + """Full path from this node to root, e.g. ["caller", "root"]""" detail: str | None = None is_cycle: bool = False -class CallEdgeMetadata(BaseModel): - """Metadata specific to call relationships""" - - call_sites: list[Position] - """Positions where the call occurs""" +# ============================================================================= +# Call Hierarchy +# ============================================================================= -class TypeEdgeMetadata(BaseModel): - """Metadata specific to type inheritance relationships""" +class CallSite(BaseModel): + """A specific location where a call occurs.""" - relationship: Literal["extends", "implements"] - """Type of inheritance relationship""" + position: Position + snippet: str | None = None -class HierarchyEdge(BaseModel): - """ - Represents a directed edge in the hierarchy graph. - - The edge connects two nodes and may carry metadata specific to the relationship type. - """ +class CallHierarchyEdge(BaseModel): + """An edge in the call hierarchy graph.""" from_node_id: str to_node_id: str - metadata: ( - Annotated[ - Union[CallEdgeMetadata, TypeEdgeMetadata], - Field( - discriminator="relationship" - if hasattr(TypeEdgeMetadata, "relationship") - else None - ), - ] - | None - ) = None - - -class HierarchyRequest(LocateRequest): - """ - Traces hierarchical relationships in a directed graph of symbols. - - This API traces two types of hierarchies: - - "call": Function/method call relationships (who calls whom) - - "type": Class/interface inheritance relationships (parent-child) - - Usage Examples: - - 1. Find who calls a function (incoming calls): - HierarchyRequest( - hierarchy_type="call", - locate=Locate(file_path="src/main.py", scope=LineScope(line=10), find="process_data"), - direction="incoming", - depth=2 - ) - - 2. Find what a function calls (outgoing calls): - HierarchyRequest( - hierarchy_type="call", - locate=Locate(file_path="src/main.py", scope=LineScope(line=10), find="process_data"), - direction="outgoing", - depth=2 - ) - - 3. Find parent classes (incoming in type hierarchy): - HierarchyRequest( - hierarchy_type="type", - locate=Locate(file_path="src/models.py", scope=LineScope(line=5), find="UserModel"), - direction="incoming", - depth=2 - ) - - 4. Find child classes (outgoing in type hierarchy): - HierarchyRequest( - hierarchy_type="type", - locate=Locate(file_path="src/models.py", scope=LineScope(line=5), find="BaseModel"), - direction="outgoing", - depth=2 - ) - - Direction is in graph terms (not hierarchy-specific): - - "incoming": predecessors (callers for calls, parent classes for types) - - "outgoing": successors (callees for calls, child classes for types) - - "both": explore both directions + call_sites: list[CallSite] = Field(default_factory=list) + + +class CallHierarchyRequest(LocateRequest): """ + Traces function/method call relationships. - hierarchy_type: Literal["call", "type"] - """Type of hierarchical relationship to trace""" + Direction: + - "incoming": Find callers (who calls this?) + - "outgoing": Find callees (what does this call?) + - "both": Both directions + """ direction: Literal["incoming", "outgoing", "both"] = "both" - """Graph traversal direction""" - depth: int = 2 - """Maximum traversal depth""" - include_external: bool = False - """Whether to include external references (applicable to certain hierarchy types)""" -markdown_template: Final = """ -# {{ root.name }} Hierarchy ({{ hierarchy_type }}, depth: {{ depth }}) +call_hierarchy_template: Final = """\ +# Call Hierarchy: `{{ root.name }}` + +Root: `{{ root.name }}` (`{{ root.kind }}`) at `{{ root.file_path }}` +{%- if root.detail %} +Detail: {{ root.detail }} +{%- endif %} -{% if direction == "incoming" or direction == "both" %} -## Incoming +{% if direction == "incoming" or direction == "both" -%} +## Callers (incoming) -{% for item in items_incoming %} -{% for i in (1..item.level) %}#{% endfor %}## {{ item.name }} +{% if items_incoming.size == 0 -%} +No callers found. +{% else -%} +{% for item in items_incoming -%} +{% for i in (1..item.level) %}#{% endfor %}## `{{ item.name }}` +{{ item.chain | join: " -> " }} - Kind: `{{ item.kind }}` - File: `{{ item.file_path }}` -{%- if item.detail != nil %} +{%- if item.detail %} - Detail: {{ item.detail }} {%- endif %} {%- if item.is_cycle %} -- ⚠️ Cycle detected +- (cycle detected) {%- endif %} -{% endfor %} +{% endfor -%} {% endif %} +{% endif -%} -{% if direction == "outgoing" or direction == "both" %} -## Outgoing +{% if direction == "outgoing" or direction == "both" -%} +## Callees (outgoing) -{% for item in items_outgoing %} -{% for i in (1..item.level) %}#{% endfor %}## {{ item.name }} +{% if items_outgoing.size == 0 -%} +No callees found. +{% else -%} +{% for item in items_outgoing -%} +{% for i in (1..item.level) %}#{% endfor %}## `{{ item.name }}` +{{ item.chain | join: " -> " }} - Kind: `{{ item.kind }}` - File: `{{ item.file_path }}` -{%- if item.detail != nil %} +{%- if item.detail %} - Detail: {{ item.detail }} {%- endif %} {%- if item.is_cycle %} -- ⚠️ Cycle detected +- (cycle detected) {%- endif %} -{% endfor %} +{% endfor -%} {% endif %} +{% endif -%} """ -class HierarchyResponse(Response): +class CallHierarchyResponse(Response): + """Response containing the call hierarchy.""" + + root: HierarchyNode + nodes: dict[str, HierarchyNode] + edges_incoming: dict[str, list[CallHierarchyEdge]] + edges_outgoing: dict[str, list[CallHierarchyEdge]] + items_incoming: list[HierarchyItem] = Field(default_factory=list) + items_outgoing: list[HierarchyItem] = Field(default_factory=list) + direction: Literal["incoming", "outgoing", "both"] + depth: int + + model_config = ConfigDict( + json_schema_extra={ + "markdown": call_hierarchy_template, + } + ) + + +# ============================================================================= +# Type Hierarchy +# ============================================================================= + + +class TypeRelation(BaseModel): + """Describes the inheritance relationship type.""" + + kind: Literal["extends", "implements"] + + +class TypeHierarchyEdge(BaseModel): + """An edge in the type hierarchy graph.""" + + from_node_id: str + to_node_id: str + relation: TypeRelation + + +class TypeHierarchyRequest(LocateRequest): """ - Response containing the hierarchy graph and flattened tree. + Traces class/interface inheritance relationships. - The response uses generic graph terminology: - - edges_incoming: edges pointing to nodes (callers or supertypes) - - edges_outgoing: edges pointing from nodes (callees or subtypes) - - items_incoming: flattened list of incoming relationships - - items_outgoing: flattened list of outgoing relationships + Direction: + - "supertypes": Find parent classes (what does this inherit?) + - "subtypes": Find child classes (what inherits from this?) + - "both": Both directions """ - hierarchy_type: Literal["call", "type"] - """Type of hierarchical relationship""" + direction: Literal["supertypes", "subtypes", "both"] = "both" + depth: int = 2 - root: HierarchyNode - """The starting node""" - nodes: dict[str, HierarchyNode] - """All nodes in the hierarchy graph""" +type_hierarchy_template: Final = """\ +# Type Hierarchy: `{{ root.name }}` - edges_incoming: dict[str, list[HierarchyEdge]] - """Incoming edges for each node (predecessors in the graph)""" +Root: `{{ root.name }}` (`{{ root.kind }}`) at `{{ root.file_path }}` +{%- if root.detail %} +Detail: {{ root.detail }} +{%- endif %} - edges_outgoing: dict[str, list[HierarchyEdge]] - """Outgoing edges for each node (successors in the graph)""" +{% if direction == "supertypes" or direction == "both" -%} +## Supertypes (parents) - items_incoming: list[HierarchyItem] = [] - """Flattened list of incoming relationships for tree rendering""" +{% if items_supertypes.size == 0 -%} +No supertypes found. +{% else -%} +{% for item in items_supertypes -%} +{% for i in (1..item.level) %}#{% endfor %}## `{{ item.name }}` +{{ item.chain | join: " <- " }} +- Kind: `{{ item.kind }}` +- File: `{{ item.file_path }}` +{%- if item.detail %} +- Detail: {{ item.detail }} +{%- endif %} +{%- if item.is_cycle %} +- (cycle detected) +{%- endif %} + +{% endfor -%} +{% endif %} +{% endif -%} - items_outgoing: list[HierarchyItem] = [] - """Flattened list of outgoing relationships for tree rendering""" +{% if direction == "subtypes" or direction == "both" -%} +## Subtypes (children) - direction: str +{% if items_subtypes.size == 0 -%} +No subtypes found. +{% else -%} +{% for item in items_subtypes -%} +{% for i in (1..item.level) %}#{% endfor %}## `{{ item.name }}` +{{ item.chain | join: " -> " }} +- Kind: `{{ item.kind }}` +- File: `{{ item.file_path }}` +{%- if item.detail %} +- Detail: {{ item.detail }} +{%- endif %} +{%- if item.is_cycle %} +- (cycle detected) +{%- endif %} + +{% endfor -%} +{% endif %} +{% endif -%} +""" + + +class TypeHierarchyResponse(Response): + """Response containing the type hierarchy.""" + + root: HierarchyNode + nodes: dict[str, HierarchyNode] + edges_supertypes: dict[str, list[TypeHierarchyEdge]] + edges_subtypes: dict[str, list[TypeHierarchyEdge]] + items_supertypes: list[HierarchyItem] = Field(default_factory=list) + items_subtypes: list[HierarchyItem] = Field(default_factory=list) + direction: Literal["supertypes", "subtypes", "both"] depth: int model_config = ConfigDict( json_schema_extra={ - "markdown": markdown_template, + "markdown": type_hierarchy_template, } ) __all__ = [ - "HierarchyNode", + "CallHierarchyEdge", + "CallHierarchyRequest", + "CallHierarchyResponse", + "CallSite", "HierarchyItem", - "HierarchyEdge", - "CallEdgeMetadata", - "TypeEdgeMetadata", - "HierarchyRequest", - "HierarchyResponse", + "HierarchyNode", + "TypeHierarchyEdge", + "TypeHierarchyRequest", + "TypeHierarchyResponse", + "TypeRelation", ]