mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-12 18:37:12 +00:00
869aaf7502
`namespace Foo {}` parses as `internal_module` and `module Bar {}` (and
ambient `declare module "pkg" {}`) as a named `module` node. Neither kind
was in `class_types`/`function_types` nor handled by an extra-walk, so the
container produced no node — its members were still reached by the default
recurse, but the namespace/module itself was invisible to the graph and its
members lost their namespace context.
Add `_ts_extra_walk`, dispatched for TypeScript after `_js_extra_walk`,
mirroring `_csharp_extra_walk`: it emits a container node + a file→container
`contains` edge and recurses the body, leaving members file-contained as
before. `internal_module` exposes `name`/`body` fields; `module` exposes
none, so name (identifier / nested_identifier / quote-stripped string) and
body (`statement_block`) are found positionally. The `is_named` guard skips
the anonymous `module` keyword token, which shares the `module` type string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""Regression tests: TypeScript namespace/module container nodes.
|
|
|
|
`namespace Foo {}` parses as `internal_module`, `module Bar {}` and ambient
|
|
`declare module "pkg" {}` as `module`. Neither was in `class_types`/`function_types`
|
|
nor handled by an extra-walk, so the container produced no node. Members were
|
|
still reached by the default recurse but the namespace itself was invisible.
|
|
"""
|
|
from pathlib import Path
|
|
|
|
from graphify.extract import _file_stem, _make_id, extract
|
|
|
|
|
|
def _write(path: Path, text: str) -> Path:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text, encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def _node_label(result: dict, file: str, sym: str):
|
|
nid = _make_id(_file_stem(Path(file)), sym)
|
|
return next((n["label"] for n in result["nodes"] if n["id"] == nid), None)
|
|
|
|
|
|
def _has_node(result: dict, file: str, sym: str) -> bool:
|
|
return _node_label(result, file, sym) is not None
|
|
|
|
|
|
def test_namespace_is_node(tmp_path):
|
|
f = _write(tmp_path / "src" / "n.ts",
|
|
"export namespace Geometry { export const PI = 3.14; }\n")
|
|
r = extract([f], cache_root=tmp_path)
|
|
assert _has_node(r, "src/n.ts", "Geometry")
|
|
|
|
|
|
def test_module_keyword_is_node(tmp_path):
|
|
f = _write(tmp_path / "src" / "m.ts",
|
|
"module Legacy { export class Thing {} }\n")
|
|
r = extract([f], cache_root=tmp_path)
|
|
assert _has_node(r, "src/m.ts", "Legacy")
|
|
|
|
|
|
def test_nested_namespace_name(tmp_path):
|
|
f = _write(tmp_path / "src" / "nn.ts",
|
|
"namespace App.Core.Util { export const v = 1; }\n")
|
|
r = extract([f], cache_root=tmp_path)
|
|
assert _node_label(r, "src/nn.ts", "App.Core.Util") == "App.Core.Util"
|
|
|
|
|
|
def test_namespace_members_still_extracted(tmp_path):
|
|
"""The container node must not cost us the members the default recurse reached."""
|
|
f = _write(tmp_path / "src" / "n.ts",
|
|
"namespace Shapes {\n"
|
|
" export class Circle {}\n"
|
|
" export function area() { return 0; }\n"
|
|
"}\n")
|
|
r = extract([f], cache_root=tmp_path)
|
|
assert _has_node(r, "src/n.ts", "Shapes")
|
|
assert _has_node(r, "src/n.ts", "Circle")
|
|
assert _has_node(r, "src/n.ts", "area")
|
|
|
|
|
|
def test_ambient_string_module_quotes_stripped(tmp_path):
|
|
f = _write(tmp_path / "src" / "amb.ts",
|
|
'declare module "pkg-name" { export const z = 3; }\n')
|
|
r = extract([f], cache_root=tmp_path)
|
|
assert _node_label(r, "src/amb.ts", "pkg-name") == "pkg-name"
|
|
|
|
|
|
def test_namespace_node_not_emitted_in_js(tmp_path):
|
|
"""The handler is TS-only; plain JS has no namespace syntax to confuse it."""
|
|
f = _write(tmp_path / "src" / "p.js", "function ok() {}\n")
|
|
r = extract([f], cache_root=tmp_path)
|
|
assert _has_node(r, "src/p.js", "ok")
|