codeanalyzer-typescript 1.4.0__py3-none-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ """Prebuilt ``cants`` (codeanalyzer-typescript) backend binary for CLDK.
2
+
3
+ This package carries the platform-specific, self-contained ``cants``
4
+ executable (built from this repo with ``bun build --compile``) and exposes its
5
+ filesystem path. CLDK's Python SDK depends on this package and calls
6
+ :func:`bin_path` to locate the analyzer, exactly as it imports ``codeanalyzer-python``
7
+ for the Python backend.
8
+
9
+ Each published wheel is platform-tagged and contains the single binary for that
10
+ platform; pip resolves the correct wheel at install time.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import stat
17
+ import sys
18
+ from importlib import resources
19
+ from pathlib import Path
20
+
21
+ __version__ = "1.4.0"
22
+
23
+ __all__ = ["bin_path", "schema_path", "__version__"]
24
+
25
+ _BINARY_NAME = "cants" + (".exe" if sys.platform == "win32" else "")
26
+ _SCHEMA_NAME = "schema.json"
27
+
28
+
29
+ def bin_path() -> Path:
30
+ """Return the absolute path to the bundled ``cants`` binary.
31
+
32
+ Raises:
33
+ FileNotFoundError: if the wheel for this platform did not include a binary
34
+ (e.g. an unsupported platform, or a source/dev install with no build run).
35
+ """
36
+ resource = resources.files("codeanalyzer_typescript") / "_bin" / _BINARY_NAME
37
+ with resources.as_file(resource) as extracted:
38
+ path = Path(extracted)
39
+
40
+ if not path.exists():
41
+ raise FileNotFoundError(
42
+ f"Bundled cants binary not found at {path}. "
43
+ "This usually means there is no prebuilt wheel for your platform; "
44
+ "build the binary with `bun build --compile` and point CLDK at it via "
45
+ "analysis_backend_path or $CODEANALYZER_TS_BIN."
46
+ )
47
+
48
+ # Wheels may not preserve the executable bit on POSIX; restore it best-effort.
49
+ if os.name == "posix":
50
+ mode = path.stat().st_mode
51
+ path.chmod(mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
52
+
53
+ return path
54
+
55
+
56
+ def schema_path() -> Path:
57
+ """Return the absolute path to the bundled Neo4j ``schema.json`` contract.
58
+
59
+ The schema is version-locked to this binary's build (its ``schema_version`` is stamped onto the
60
+ ``:Application`` node of every emitted graph), so a consumer can read it to validate
61
+ producer/consumer compatibility without invoking the binary.
62
+
63
+ Raises:
64
+ FileNotFoundError: if the wheel for this platform did not include the schema.
65
+ """
66
+ resource = resources.files("codeanalyzer_typescript") / "_bin" / _SCHEMA_NAME
67
+ with resources.as_file(resource) as extracted:
68
+ path = Path(extracted)
69
+
70
+ if not path.exists():
71
+ raise FileNotFoundError(
72
+ f"Bundled schema.json not found at {path}. Regenerate it with `cants --emit schema` "
73
+ "or `bun gen:schema`."
74
+ )
75
+
76
+ return path
@@ -0,0 +1,34 @@
1
+ """Console-script entry point: run the bundled ``cants`` binary.
2
+
3
+ ``pip install codeanalyzer-typescript`` installs a ``cants`` launcher (see
4
+ ``[project.scripts]`` in ``pyproject.toml``) that calls :func:`main`. We locate
5
+ the platform binary via :func:`bin_path` and hand off the process to it, passing
6
+ through every CLI argument and the exit code unchanged.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import subprocess
13
+ import sys
14
+
15
+ from . import bin_path
16
+
17
+
18
+ def main() -> "int | None":
19
+ """Exec the bundled ``cants`` binary with the current argv (minus argv[0])."""
20
+ binary = str(bin_path())
21
+ args = [binary, *sys.argv[1:]]
22
+
23
+ if os.name == "posix":
24
+ # Replace this process with the binary: no extra Python process lingers,
25
+ # and signals/exit codes are handled by cants directly.
26
+ os.execv(binary, args)
27
+ # os.execv never returns on success.
28
+
29
+ # Windows has no execv that behaves like POSIX; spawn and propagate the code.
30
+ return subprocess.call(args)
31
+
32
+
33
+ if __name__ == "__main__":
34
+ sys.exit(main())
@@ -0,0 +1,5 @@
1
+ # The compiled binary is produced per-platform at build time and bundled into
2
+ # the wheel via `artifacts` in pyproject.toml. Never commit it (platform-specific,
3
+ # ~70 MB). Keep this directory present so the package is importable in dev.
4
+ *
5
+ !.gitignore
Binary file
@@ -0,0 +1,621 @@
1
+ {
2
+ "schema_version": "2.0.0",
3
+ "generator": "codeanalyzer-typescript",
4
+ "marker_labels": [
5
+ "TSCanNode",
6
+ "JSCanNode"
7
+ ],
8
+ "node_labels": [
9
+ {
10
+ "label": "TSApplication",
11
+ "mergeLabel": "Application",
12
+ "key": "id",
13
+ "properties": {
14
+ "id": "string",
15
+ "schema_version": "string",
16
+ "language": "string",
17
+ "max_level": "integer",
18
+ "k_limit": "integer",
19
+ "analyzer_name": "string",
20
+ "analyzer_version": "string",
21
+ "entrypoint_frameworks": "string[]",
22
+ "entrypoint_report_json": "string"
23
+ }
24
+ },
25
+ {
26
+ "label": "Artifact",
27
+ "mergeLabel": "Artifact",
28
+ "key": "id",
29
+ "properties": {
30
+ "id": "string",
31
+ "kind": "string",
32
+ "path": "string",
33
+ "format": "string",
34
+ "roles": "string[]",
35
+ "size_bytes": "integer",
36
+ "sha256": "string",
37
+ "extraction": "string",
38
+ "source": "string"
39
+ }
40
+ },
41
+ {
42
+ "label": "Package",
43
+ "mergeLabel": "Package",
44
+ "key": "id",
45
+ "properties": {
46
+ "id": "string",
47
+ "ecosystem": "string",
48
+ "name": "string"
49
+ }
50
+ },
51
+ {
52
+ "label": "TSDecorator",
53
+ "mergeLabel": "TSDecorator",
54
+ "key": "name",
55
+ "properties": {
56
+ "name": "string",
57
+ "qualified_name": "string"
58
+ }
59
+ },
60
+ {
61
+ "label": "ConfigKey",
62
+ "mergeLabel": "ConfigKey",
63
+ "key": "id",
64
+ "properties": {
65
+ "id": "string",
66
+ "key": "string",
67
+ "namespace": "string",
68
+ "value": "string",
69
+ "references": "string[]"
70
+ }
71
+ },
72
+ {
73
+ "label": "TSModule",
74
+ "mergeLabel": "CanNode",
75
+ "key": "id",
76
+ "properties": {
77
+ "id": "string",
78
+ "kind": "string",
79
+ "name": "string",
80
+ "is_tsx": "boolean",
81
+ "is_declaration_file": "boolean",
82
+ "content_hash": "string",
83
+ "exports_json": "string",
84
+ "start_line": "integer",
85
+ "end_line": "integer"
86
+ }
87
+ },
88
+ {
89
+ "label": "TSClass",
90
+ "mergeLabel": "CanNode",
91
+ "key": "id",
92
+ "properties": {
93
+ "id": "string",
94
+ "kind": "string",
95
+ "signature": "string",
96
+ "name": "string",
97
+ "base_classes": "string[]",
98
+ "implements_types": "string[]",
99
+ "is_abstract": "boolean",
100
+ "is_exported": "boolean",
101
+ "is_ambient": "boolean",
102
+ "code": "string",
103
+ "start_line": "integer",
104
+ "end_line": "integer",
105
+ "is_entrypoint": "boolean",
106
+ "entrypoint_frameworks": "string[]"
107
+ }
108
+ },
109
+ {
110
+ "label": "TSInterface",
111
+ "mergeLabel": "CanNode",
112
+ "key": "id",
113
+ "properties": {
114
+ "id": "string",
115
+ "kind": "string",
116
+ "signature": "string",
117
+ "name": "string",
118
+ "base_classes": "string[]",
119
+ "is_exported": "boolean",
120
+ "is_ambient": "boolean",
121
+ "code": "string",
122
+ "start_line": "integer",
123
+ "end_line": "integer"
124
+ }
125
+ },
126
+ {
127
+ "label": "TSEnum",
128
+ "mergeLabel": "CanNode",
129
+ "key": "id",
130
+ "properties": {
131
+ "id": "string",
132
+ "kind": "string",
133
+ "signature": "string",
134
+ "name": "string",
135
+ "is_const": "boolean",
136
+ "is_exported": "boolean",
137
+ "is_ambient": "boolean",
138
+ "code": "string",
139
+ "start_line": "integer",
140
+ "end_line": "integer"
141
+ }
142
+ },
143
+ {
144
+ "label": "TSTypeAlias",
145
+ "mergeLabel": "CanNode",
146
+ "key": "id",
147
+ "properties": {
148
+ "id": "string",
149
+ "kind": "string",
150
+ "signature": "string",
151
+ "name": "string",
152
+ "aliased_type": "string",
153
+ "is_exported": "boolean",
154
+ "is_ambient": "boolean",
155
+ "code": "string",
156
+ "start_line": "integer",
157
+ "end_line": "integer"
158
+ }
159
+ },
160
+ {
161
+ "label": "TSNamespace",
162
+ "mergeLabel": "CanNode",
163
+ "key": "id",
164
+ "properties": {
165
+ "id": "string",
166
+ "kind": "string",
167
+ "signature": "string",
168
+ "name": "string",
169
+ "is_exported": "boolean",
170
+ "is_ambient": "boolean",
171
+ "code": "string",
172
+ "start_line": "integer",
173
+ "end_line": "integer"
174
+ }
175
+ },
176
+ {
177
+ "label": "TSCallable",
178
+ "mergeLabel": "CanNode",
179
+ "key": "id",
180
+ "properties": {
181
+ "id": "string",
182
+ "kind": "string",
183
+ "signature": "string",
184
+ "name": "string",
185
+ "return_type": "string",
186
+ "cyclomatic_complexity": "integer",
187
+ "accessibility": "string",
188
+ "accessor_kind": "string",
189
+ "is_static": "boolean",
190
+ "is_abstract": "boolean",
191
+ "is_async": "boolean",
192
+ "is_generator": "boolean",
193
+ "is_exported": "boolean",
194
+ "is_ambient": "boolean",
195
+ "is_implicit": "boolean",
196
+ "code": "string",
197
+ "start_line": "integer",
198
+ "end_line": "integer",
199
+ "is_entrypoint": "boolean",
200
+ "entrypoint_frameworks": "string[]",
201
+ "parameters_json": "string"
202
+ }
203
+ },
204
+ {
205
+ "label": "TSField",
206
+ "mergeLabel": "CanNode",
207
+ "key": "id",
208
+ "properties": {
209
+ "id": "string",
210
+ "kind": "string",
211
+ "name": "string",
212
+ "type": "string",
213
+ "start_line": "integer",
214
+ "end_line": "integer"
215
+ }
216
+ },
217
+ {
218
+ "label": "TSBodyNode",
219
+ "mergeLabel": "CanNode",
220
+ "key": "id",
221
+ "properties": {
222
+ "id": "string",
223
+ "kind": "string",
224
+ "of": "string",
225
+ "parent": "string",
226
+ "callee": "string",
227
+ "start_line": "integer",
228
+ "end_line": "integer"
229
+ }
230
+ },
231
+ {
232
+ "label": "TSExternal",
233
+ "mergeLabel": "CanNode",
234
+ "key": "id",
235
+ "properties": {
236
+ "id": "string",
237
+ "kind": "string",
238
+ "name": "string",
239
+ "module": "string"
240
+ }
241
+ },
242
+ {
243
+ "label": "TSAnonymousCallable",
244
+ "mergeLabel": "CanNode",
245
+ "key": "id",
246
+ "properties": {
247
+ "id": "string",
248
+ "kind": "string",
249
+ "signature": "string",
250
+ "name": "string",
251
+ "return_type": "string",
252
+ "cyclomatic_complexity": "integer",
253
+ "accessibility": "string",
254
+ "accessor_kind": "string",
255
+ "is_static": "boolean",
256
+ "is_abstract": "boolean",
257
+ "is_async": "boolean",
258
+ "is_generator": "boolean",
259
+ "is_exported": "boolean",
260
+ "is_ambient": "boolean",
261
+ "is_implicit": "boolean",
262
+ "path": "string",
263
+ "start_column": "integer",
264
+ "start_line": "integer",
265
+ "end_line": "integer"
266
+ }
267
+ }
268
+ ],
269
+ "relationship_types": [
270
+ {
271
+ "type": "TS_HAS_MODULE",
272
+ "from": [
273
+ "TSApplication"
274
+ ],
275
+ "to": [
276
+ "TSModule"
277
+ ],
278
+ "properties": {}
279
+ },
280
+ {
281
+ "type": "HAS_ARTIFACT",
282
+ "from": [
283
+ "TSApplication"
284
+ ],
285
+ "to": [
286
+ "Artifact"
287
+ ],
288
+ "properties": {}
289
+ },
290
+ {
291
+ "type": "DECLARES_DEPENDENCY",
292
+ "from": [
293
+ "Artifact"
294
+ ],
295
+ "to": [
296
+ "Package"
297
+ ],
298
+ "properties": {
299
+ "spec": "string",
300
+ "kind": "string",
301
+ "direct": "boolean",
302
+ "extras": "string[]",
303
+ "prov": "string[]"
304
+ }
305
+ },
306
+ {
307
+ "type": "LOCKS",
308
+ "from": [
309
+ "Artifact"
310
+ ],
311
+ "to": [
312
+ "Package"
313
+ ],
314
+ "properties": {
315
+ "version": "string"
316
+ }
317
+ },
318
+ {
319
+ "type": "TS_PROVIDES",
320
+ "from": [
321
+ "Package"
322
+ ],
323
+ "to": [
324
+ "TSExternal"
325
+ ],
326
+ "properties": {}
327
+ },
328
+ {
329
+ "type": "TS_UNRESOLVED_IMPORT",
330
+ "from": [
331
+ "TSApplication"
332
+ ],
333
+ "to": [
334
+ "TSExternal"
335
+ ],
336
+ "properties": {
337
+ "prov": "string[]"
338
+ }
339
+ },
340
+ {
341
+ "type": "DEFINES_CONFIG",
342
+ "from": [
343
+ "Artifact"
344
+ ],
345
+ "to": [
346
+ "ConfigKey"
347
+ ],
348
+ "properties": {}
349
+ },
350
+ {
351
+ "type": "TS_USES_CONFIG",
352
+ "from": [
353
+ "TSBodyNode"
354
+ ],
355
+ "to": [
356
+ "ConfigKey"
357
+ ],
358
+ "properties": {
359
+ "prov": "string[]"
360
+ }
361
+ },
362
+ {
363
+ "type": "TS_READS_CONFIG_UNRESOLVED",
364
+ "from": [
365
+ "TSApplication"
366
+ ],
367
+ "to": [
368
+ "TSExternal",
369
+ "TSCallable"
370
+ ],
371
+ "properties": {
372
+ "key": "string",
373
+ "reason": "string",
374
+ "prov": "string[]",
375
+ "_k": "string"
376
+ }
377
+ },
378
+ {
379
+ "type": "TS_IMPORTS",
380
+ "from": [
381
+ "TSModule"
382
+ ],
383
+ "to": [
384
+ "TSModule",
385
+ "TSExternal"
386
+ ],
387
+ "properties": {
388
+ "spellings": "string[]",
389
+ "imported_names": "string[]",
390
+ "aliases": "string[]",
391
+ "type_only_names": "string[]"
392
+ }
393
+ },
394
+ {
395
+ "type": "TS_RE_EXPORTS",
396
+ "from": [
397
+ "TSModule"
398
+ ],
399
+ "to": [
400
+ "TSModule",
401
+ "TSExternal"
402
+ ],
403
+ "properties": {
404
+ "spellings": "string[]",
405
+ "exported_names": "string[]",
406
+ "aliases": "string[]",
407
+ "type_only_names": "string[]"
408
+ }
409
+ },
410
+ {
411
+ "type": "TS_DECLARES",
412
+ "from": [
413
+ "TSModule",
414
+ "TSNamespace",
415
+ "TSCallable"
416
+ ],
417
+ "to": [
418
+ "TSClass",
419
+ "TSInterface",
420
+ "TSEnum",
421
+ "TSTypeAlias",
422
+ "TSNamespace",
423
+ "TSCallable"
424
+ ],
425
+ "properties": {}
426
+ },
427
+ {
428
+ "type": "TS_HAS_METHOD",
429
+ "from": [
430
+ "TSClass",
431
+ "TSInterface"
432
+ ],
433
+ "to": [
434
+ "TSCallable"
435
+ ],
436
+ "properties": {}
437
+ },
438
+ {
439
+ "type": "TS_HAS_FIELD",
440
+ "from": [
441
+ "TSModule",
442
+ "TSClass",
443
+ "TSInterface",
444
+ "TSEnum",
445
+ "TSNamespace"
446
+ ],
447
+ "to": [
448
+ "TSField"
449
+ ],
450
+ "properties": {}
451
+ },
452
+ {
453
+ "type": "TS_DECORATED_BY",
454
+ "from": [
455
+ "TSClass",
456
+ "TSInterface",
457
+ "TSEnum",
458
+ "TSNamespace",
459
+ "TSCallable",
460
+ "TSField"
461
+ ],
462
+ "to": [
463
+ "TSDecorator"
464
+ ],
465
+ "properties": {
466
+ "positional_arguments": "string[]",
467
+ "keyword_arguments_json": "string"
468
+ }
469
+ },
470
+ {
471
+ "type": "TS_HAS_BODY_NODE",
472
+ "from": [
473
+ "TSCallable",
474
+ "TSAnonymousCallable"
475
+ ],
476
+ "to": [
477
+ "TSBodyNode"
478
+ ],
479
+ "properties": {}
480
+ },
481
+ {
482
+ "type": "TS_RESOLVES_TO",
483
+ "from": [
484
+ "TSBodyNode"
485
+ ],
486
+ "to": [
487
+ "TSCallable",
488
+ "TSExternal",
489
+ "TSAnonymousCallable"
490
+ ],
491
+ "properties": {}
492
+ },
493
+ {
494
+ "type": "TS_CALLS",
495
+ "from": [
496
+ "TSCallable",
497
+ "TSAnonymousCallable"
498
+ ],
499
+ "to": [
500
+ "TSCallable",
501
+ "TSExternal",
502
+ "TSAnonymousCallable"
503
+ ],
504
+ "properties": {
505
+ "weight": "integer",
506
+ "prov": "string[]"
507
+ }
508
+ },
509
+ {
510
+ "type": "TS_CFG_NEXT",
511
+ "from": [
512
+ "TSBodyNode"
513
+ ],
514
+ "to": [
515
+ "TSBodyNode"
516
+ ],
517
+ "properties": {
518
+ "kind": "string",
519
+ "_k": "string"
520
+ }
521
+ },
522
+ {
523
+ "type": "TS_CDG",
524
+ "from": [
525
+ "TSBodyNode"
526
+ ],
527
+ "to": [
528
+ "TSBodyNode"
529
+ ],
530
+ "properties": {}
531
+ },
532
+ {
533
+ "type": "TS_DDG",
534
+ "from": [
535
+ "TSBodyNode"
536
+ ],
537
+ "to": [
538
+ "TSBodyNode"
539
+ ],
540
+ "properties": {
541
+ "var": "string",
542
+ "prov": "string[]",
543
+ "_k": "string"
544
+ }
545
+ },
546
+ {
547
+ "type": "TS_SUMMARY",
548
+ "from": [
549
+ "TSBodyNode"
550
+ ],
551
+ "to": [
552
+ "TSBodyNode"
553
+ ],
554
+ "properties": {
555
+ "var": "string"
556
+ }
557
+ },
558
+ {
559
+ "type": "TS_PARAM_IN",
560
+ "from": [
561
+ "TSBodyNode"
562
+ ],
563
+ "to": [
564
+ "TSBodyNode"
565
+ ],
566
+ "properties": {
567
+ "var": "string"
568
+ }
569
+ },
570
+ {
571
+ "type": "TS_PARAM_OUT",
572
+ "from": [
573
+ "TSBodyNode"
574
+ ],
575
+ "to": [
576
+ "TSBodyNode"
577
+ ],
578
+ "properties": {
579
+ "var": "string"
580
+ }
581
+ },
582
+ {
583
+ "type": "TS_EXTENDS",
584
+ "from": [
585
+ "TSClass",
586
+ "TSInterface"
587
+ ],
588
+ "to": [
589
+ "TSClass",
590
+ "TSInterface"
591
+ ],
592
+ "properties": {}
593
+ },
594
+ {
595
+ "type": "TS_IMPLEMENTS",
596
+ "from": [
597
+ "TSClass"
598
+ ],
599
+ "to": [
600
+ "TSInterface",
601
+ "TSClass"
602
+ ],
603
+ "properties": {}
604
+ }
605
+ ],
606
+ "constraints": [
607
+ "CREATE CONSTRAINT application_id IF NOT EXISTS FOR (x:Application) REQUIRE x.id IS UNIQUE",
608
+ "CREATE CONSTRAINT artifact_id IF NOT EXISTS FOR (x:Artifact) REQUIRE x.id IS UNIQUE",
609
+ "CREATE CONSTRAINT package_id IF NOT EXISTS FOR (x:Package) REQUIRE x.id IS UNIQUE",
610
+ "CREATE CONSTRAINT tsdecorator_name IF NOT EXISTS FOR (x:TSDecorator) REQUIRE x.name IS UNIQUE",
611
+ "CREATE CONSTRAINT configkey_id IF NOT EXISTS FOR (x:ConfigKey) REQUIRE x.id IS UNIQUE",
612
+ "CREATE CONSTRAINT cannode_id IF NOT EXISTS FOR (x:CanNode) REQUIRE x.id IS UNIQUE"
613
+ ],
614
+ "indexes": [
615
+ "CREATE INDEX callable_name IF NOT EXISTS FOR (c:TSCallable) ON (c.name)",
616
+ "CREATE FULLTEXT INDEX ts_code_fts IF NOT EXISTS FOR (c:TSCallable) ON EACH [c.code]",
617
+ "CREATE INDEX cannode_kind IF NOT EXISTS FOR (n:CanNode) ON (n.kind)",
618
+ "CREATE INDEX tscannode_id IF NOT EXISTS FOR (n:TSCanNode) ON (n.id)",
619
+ "CREATE INDEX jscannode_id IF NOT EXISTS FOR (n:JSCanNode) ON (n.id)"
620
+ ]
621
+ }
@@ -0,0 +1,389 @@
1
+ Metadata-Version: 2.5
2
+ Name: codeanalyzer-typescript
3
+ Version: 1.4.0
4
+ Summary: Prebuilt codeanalyzer-typescript backend binary for CLDK (codellm-devkit).
5
+ Project-URL: Homepage, https://codellm-devkit.info
6
+ Project-URL: Repository, https://github.com/codellm-devkit/codeanalyzer-ts
7
+ Author-email: Rahul Krishna <i.m.ralk@gmail.com>
8
+ License: Apache-2.0
9
+ Keywords: cldk,code analysis,codellm-devkit,typescript
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+
17
+ <div align="center">
18
+
19
+ <img src="https://github.com/codellm-devkit/codeanalyzer-python/blob/main/docs/assets/logo.png?raw=true" alt="CodeLLM-DevKit" />
20
+
21
+ # codeanalyzer-typescript (`cants`)
22
+
23
+ **A TypeScript/JavaScript static-analysis toolkit — the CLDK backend that emits the canonical schema-v2 Code Property Graph (symbol table → call graph → intraprocedural dataflow → interprocedural SDG), as `analysis.json` or a Neo4j property graph.**
24
+
25
+ [![PyPI](https://img.shields.io/pypi/v/codeanalyzer-typescript?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/codeanalyzer-typescript/)
26
+ [![Python](https://img.shields.io/pypi/pyversions/codeanalyzer-typescript?style=for-the-badge&logo=python&logoColor=white)](https://pypi.org/project/codeanalyzer-typescript/)
27
+ [![Release](https://img.shields.io/github/actions/workflow/status/codellm-devkit/codeanalyzer-typescript/release.yml?style=for-the-badge&label=release&logo=github)](https://github.com/codellm-devkit/codeanalyzer-typescript/actions/workflows/release.yml)
28
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue?style=for-the-badge)](./LICENSE)
29
+
30
+ </div>
31
+
32
+ ---
33
+
34
+ `cants` is a static analyzer for TypeScript/JavaScript built on the TypeScript compiler (via
35
+ [ts-morph](https://ts-morph.com/)). It produces the canonical CodeLLM-DevKit (CLDK) **schema v2**
36
+ — one additive Code Property Graph, built up level by level (symbol table → call graph →
37
+ intraprocedural dataflow → interprocedural SDG) — as `analysis.json` and can project that same
38
+ structure into a **Neo4j property graph**. It is the TypeScript backend behind
39
+ [CLDK](https://github.com/codellm-devkit/python-sdk), mirroring its
40
+ [Python](https://github.com/codellm-devkit/codeanalyzer-python) and
41
+ [Java](https://github.com/codellm-devkit/codeanalyzer-java) siblings.
42
+
43
+ The call graph is the TypeScript compiler's resolver plus a **defuse linker** — a deterministic,
44
+ per-callable pass that backfills the edges the resolver misses (alias chains, decorator
45
+ invocations, callbacks handed to library calls, parameter-flow calls) with no whole-program
46
+ fixpoint. Edges keep a `provenance` tag (`tsc` / `defuse` / `import`), so you can tell the layers
47
+ apart, and the output is byte-identical across runs.
48
+
49
+ ## Table of Contents
50
+
51
+ - [Features](#features)
52
+ - [Installation](#installation)
53
+ - [Prerequisites](#prerequisites)
54
+ - [Install via shell script](#install-via-shell-script)
55
+ - [Install via Homebrew](#install-via-homebrew)
56
+ - [Install via pip (PyPI)](#install-via-pip-pypi)
57
+ - [Build from source](#build-from-source)
58
+ - [Usage](#usage)
59
+ - [Options](#options)
60
+ - [Examples](#examples)
61
+ - [Output targets](#output-targets)
62
+ - [`analysis.json` (default)](#analysisjson-default)
63
+ - [Neo4j graph](#neo4j-graph)
64
+ - [Schema contract](#schema-contract)
65
+ - [Development](#development)
66
+ - [License](#license)
67
+
68
+ ## Features
69
+
70
+ - **Symbol table** — modules, classes, interfaces, enums, type aliases, namespaces, functions,
71
+ methods, variables, decorators, and JSDoc, with precise source spans.
72
+ - **Call graph** — the TypeScript compiler's resolver plus Rapid Type Analysis (RTA), with
73
+ **phantom (external) nodes** for calls into imported libraries and Node builtins.
74
+ - **Defuse linker** — a deterministic per-callable pass over the resolver's leftovers: alias
75
+ chains, decorator invocations, library-callback edges, and bounded interprocedural votes —
76
+ validated as a strict superset of Joern's real call pairs on the reference corpus.
77
+ - **Repository-artifact layer** — every non-code file (manifests, lockfiles, CI, containers, env)
78
+ inventoried with flat, evidence-tagged dependencies (direct and transitive) and a `ConfigKey`
79
+ family joined to the code that reads it via a level-graded `config_use` edge; see
80
+ `docs/skills/analyzing-cants-graphs/`.
81
+ - **Neo4j output** — project the analysis into a labeled property graph: a self-contained
82
+ `graph.cypher` snapshot, or an **incremental** push to a live database over Bolt.
83
+ - **Versioned schema** — a machine-readable, version-stamped Neo4j schema contract
84
+ (`--emit schema`), bundled in every release and enforced by a conformance test.
85
+ - **Self-contained binary** — no Bun or Node required at runtime; install via `pip`, Homebrew, or a
86
+ one-line shell script.
87
+ - **Incremental** — content-hash caching so re-analyzing (and re-loading the graph) only touches
88
+ what changed.
89
+
90
+ ## Installation
91
+
92
+ ### Prerequisites
93
+
94
+ Running a prebuilt `cants` binary requires **nothing** — it is fully self-contained. To *analyze* a
95
+ project, that project should be a normal Node/TypeScript project (so the compiler can resolve types
96
+ and imports). Building `cants` from source requires [Bun](https://bun.sh/) 1.0+.
97
+
98
+ ### Install via shell script
99
+
100
+ Download and install the prebuilt binary for your platform from the latest release:
101
+
102
+ ```sh
103
+ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-typescript/releases/latest/download/cants-installer.sh | sh
104
+ ```
105
+
106
+ The installer drops `cants` into `~/.local/bin` (override with `CANTS_INSTALL_DIR`) and can pin a
107
+ version with `CANTS_VERSION=vX.Y.Z`. Supports macOS (arm64/x86_64) and Linux (x86_64/aarch64).
108
+
109
+ ### Install via Homebrew
110
+
111
+ ```sh
112
+ brew install codellm-devkit/homebrew-tap/codeanalyzer-typescript
113
+ ```
114
+
115
+ ### Install via pip (PyPI)
116
+
117
+ The wheel bundles the prebuilt, self-contained binary for your platform (no Bun or Node required):
118
+
119
+ ```sh
120
+ pip install codeanalyzer-typescript
121
+ cants --help
122
+ ```
123
+
124
+ This is also the package CLDK's Python SDK depends on to locate the analyzer backend; it exposes
125
+ `codeanalyzer_typescript.bin_path()` and `schema_path()`.
126
+
127
+ ### Build from source
128
+
129
+ ```sh
130
+ # Install Bun, then:
131
+ git clone https://github.com/codellm-devkit/codeanalyzer-typescript
132
+ cd codeanalyzer-typescript
133
+ bun install
134
+ bun run build # → dist/cants (standalone native binary)
135
+ ```
136
+
137
+ You can also run the analyzer directly from source without compiling:
138
+
139
+ ```sh
140
+ bun run start -- --input /path/to/typescript/project
141
+ ```
142
+
143
+ ## Usage
144
+
145
+ ```sh
146
+ cants --input /path/to/typescript/project
147
+ ```
148
+
149
+ With no `--output`, the analysis is printed to stdout as compact JSON; with `--output <dir>` it is
150
+ written to `analysis.json` (or `graph.cypher` for `--emit neo4j`) in that directory.
151
+
152
+ ### Options
153
+
154
+ <!-- BEGIN cants-help -->
155
+
156
+ ```text
157
+ Usage: cants [options]
158
+
159
+ CLDK TypeScript analyzer — emits the canonical schema-v2 CPG (symbol table →
160
+ call graph → dataflow → SDG) as analysis.json, or a Neo4j graph.
161
+
162
+ Options:
163
+ -i, --input <path> project root to analyze (not required for
164
+ --emit schema)
165
+ -o, --output <dir> output directory (omit ⇒ compact output to
166
+ stdout)
167
+ --emit <target> output target: json (analysis.json, default) |
168
+ neo4j (graph.cypher or live push) | schema (the
169
+ Neo4j schema.json contract) (default: "json")
170
+ --app-name <name> logical application name for the graph
171
+ :Application anchor (default: input dir name)
172
+ --neo4j-uri <uri> push the graph to a live Neo4j over Bolt
173
+ (incremental); omit to write graph.cypher (env:
174
+ NEO4J_URI)
175
+ --neo4j-user <user> Neo4j username (default: "neo4j", env:
176
+ NEO4J_USERNAME)
177
+ --neo4j-password <password> Neo4j password (prefer the env var; a flag is
178
+ visible in shell history / process list)
179
+ (default: "neo4j", env: NEO4J_PASSWORD)
180
+ --neo4j-database <db> Neo4j database name (env: NEO4J_DATABASE)
181
+ -a, --analysis-level <n> analysis depth: 1 = symbol table (default); 2 =
182
+ + resolver call graph; 3 = + intraprocedural
183
+ dataflow (cfg/cdg/ddg); 4 = + interprocedural
184
+ SDG (param_in/param_out/summary) (default: "1")
185
+ --graphs <list> dataflow sections to emit, comma-separated: cfg
186
+ | dfg | pdg (require -a 3) | sdg (requires -a
187
+ 4); default: all rungs at or below the level
188
+ --graph-field-depth <k> access-path depth bound (k-limit) for level-3
189
+ dataflow (default: "3")
190
+ -j, --jobs <n> worker parallelism for level-3 graphs (default:
191
+ sequential; opt in with N ≥ 2 on large projects
192
+ — each worker loads its own copy of the
193
+ program)
194
+ -t, --target-files <paths...> restrict analysis to specific files
195
+ (incremental)
196
+ --skip-tests skip test trees (default)
197
+ --include-tests include test trees
198
+ --eager force a clean rebuild instead of reusing the
199
+ cache
200
+ --lazy reuse the cache (default)
201
+ --no-build skip dependency materialization (use a prepared
202
+ node_modules)
203
+ --no-phantoms disable phantom (external) nodes for
204
+ imported/required library calls
205
+ --resolve-installed probe node_modules metadata for import→package
206
+ binding (default: repo files only)
207
+ --no-artifact-text keep the artifact inventory but drop captured
208
+ raw text
209
+ --entrypoint-rules <yaml...> extra entrypoint rules file(s), merged with the
210
+ shipped set; repeatable
211
+ --program <tsconfig...> restrict the run to these programs, named by
212
+ scope dir relative to --input ('<root>' for the
213
+ root program); repeatable
214
+ --list-programs list the discovered programs, one per line, and
215
+ exit
216
+ --emit-ir persist this shard's graph IR for a later
217
+ cross-shard stitch
218
+ --no-repo-sections skip artifacts/dependencies/unresolved_imports
219
+ (repo-scoped; compute them once per repository,
220
+ not once per shard)
221
+ -c, --cache-dir <dir> cache/intermediate directory
222
+ -v, --verbose increase verbosity (repeatable)
223
+ -h, --help display help for command
224
+ ```
225
+
226
+ <!-- END cants-help -->
227
+
228
+ ### Examples
229
+
230
+ 1. **Basic analysis to stdout, or to a file:**
231
+ ```sh
232
+ cants --input ./my-ts-project # compact JSON on stdout
233
+ cants --input ./my-ts-project --output ./out # → ./out/analysis.json
234
+ ```
235
+
236
+ 2. **Emit a Neo4j snapshot, or push to a live database:**
237
+ ```sh
238
+ cants --input ./my-ts-project --emit neo4j --output ./out # → ./out/graph.cypher
239
+ cants --input ./my-ts-project --emit neo4j \
240
+ --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-password secret
241
+ ```
242
+
243
+ 3. **Incremental analysis of specific files:**
244
+ ```sh
245
+ cants --input ./my-ts-project --target-files src/a.ts src/b.ts
246
+ ```
247
+
248
+ 4. **Force a clean rebuild with a custom cache directory:**
249
+ ```sh
250
+ cants --input ./my-ts-project --eager --cache-dir /path/to/custom-cache
251
+ ```
252
+
253
+ 5. **Program graphs (level 3): CFG/PDG/SDG in `analysis.json`:**
254
+ ```sh
255
+ cants --input ./my-ts-project -a 3 # full program_graphs section
256
+ cants --input ./my-ts-project -a 3 --graphs cfg,pdg # scope the emitted graphs
257
+ ```
258
+
259
+ ## Output targets
260
+
261
+ `cants` builds one analysis in memory and can emit it three ways (`--emit`):
262
+
263
+ ### `analysis.json` (default)
264
+
265
+ The **canonical schema v2** — one additive Code Property Graph: a containment tree of nodes
266
+ (`id` / `kind` / `span` / children) with typed edge overlays. Analysis **levels** populate it more
267
+ deeply; each level only ever *adds*.
268
+
269
+ ```jsonc
270
+ {
271
+ "schema_version": "2.0.0", "language": "typescript", "max_level": 4, "k_limit": 3,
272
+ "application": {
273
+ "id": "can://typescript/<app>", "kind": "application",
274
+ "symbol_table": { // L1: the tree, keyed by file path
275
+ "<file>": { "kind": "module", "source": "…",
276
+ "types": { /* class | interface | enum | type_alias | namespace nodes */ },
277
+ "functions": { /* callable nodes: { id, kind, span, body{}, cfg[], cdg[], ddg[], summary[] } */ },
278
+ "fields": { /* module-level bindings */ } } },
279
+ "call_graph": [ /* L2: { src, dst, prov, weight } — callable → callable, can:// ids */ ],
280
+ "param_in": [ /* L4: actual_in → formal_in, fully-qualified can://…@local ids */ ],
281
+ "param_out": [ /* L4: formal_out → actual_out */ ]
282
+ }
283
+ }
284
+ ```
285
+
286
+ Each callable's `body{}` is keyed by local id (`line:col`, or `@entry`/`@formal_in:N`/… for
287
+ synthetic vertices); intra-callable edge lists (`cfg`/`cdg`/`ddg`/`summary`) use those bare local
288
+ ids, cross-callable lists use fully-qualified `can://…@local` ids. A single signature canonicalizer
289
+ underlies every `can://` id, so call edges, dataflow edges, and tree nodes all join. The full model
290
+ is `.claude/SCHEMA_DECISIONS.md` (§ "Schema v2 migration") and the CLDK `canonical-schema.md`.
291
+
292
+ ### Dataflow (`-a 3` intraprocedural, `-a 4` interprocedural)
293
+
294
+ Native dependence graphs, built in-process from the same ts-morph AST (no external engine), grown
295
+ **into the tree** (not a separate section):
296
+
297
+ - **`-a 3`** completes each callable's `body{}` with statement nodes and hangs the intra-callable
298
+ edge lists `cfg` (exceptional control flow), `cdg` (control dependence), and `ddg` (data
299
+ dependence via reaching-definitions, `prov:["reaching-defs"]`) on the callable.
300
+ - **`-a 4`** adds the synthetic `@formal_in:N` / `@formal_out` / `<L>/actual_in:N` / `<L>/actual_out`
301
+ vertices, the intra-caller `summary` edges, and the application-scope `param_in` / `param_out`
302
+ lists — the whole-program System Dependence Graph.
303
+
304
+ `-a 3` implies `-a 2`; `-a 4` implies `-a 3`. `--graphs cfg,dfg,pdg,sdg` scopes which rungs emit
305
+ (`cfg`/`dfg`/`pdg` require `-a 3`, `sdg` requires `-a 4`). `L1 ⊆ L2 ⊆ L3 ⊆ L4` is a monotonicity
306
+ gate. Every node is addressed by its `can://…@local` id, so dataflow edges, call edges, and tree
307
+ nodes all join.
308
+
309
+ **Substrate (locked in [issue #2](https://github.com/codellm-devkit/codeanalyzer-typescript/issues/2)):**
310
+ the CFG and reaching-definitions are hand-built from the ts-morph AST; the call-graph oracle is
311
+ the provenance-merged tsc + defuse graph; aliasing is a flow-insensitive copy-alias MVP
312
+ (points-to-backed propagation is a staged upgrade). Function summaries are composed
313
+ bottom-up over the SCC condensation of the call graph, with k-limited access paths; module
314
+ globals ride the SDG as extra parameters. The analysis is deliberately sound-leaning and
315
+ over-approximate; known unsoundness (dynamic `eval`, reflection/monkey-patching, npm-internal
316
+ effects) is recorded in `.claude/SCHEMA_DECISIONS.md`. The analyzer is a **pure graph provider**:
317
+ it emits the dependence-graph substrate (CFG/PDG/SDG + `summary` edges) and stops — backward
318
+ slicing and taint are reachability *queries* over the SDG that live in the frontend SDK, not here.
319
+
320
+ **Parallelism (`-j/--jobs`).** The pipeline implements the level-3 parallel execution model:
321
+ stage-1–4 extraction fans out per callable over a Bun worker pool (partitioned by file) and is
322
+ posted *before* the call-graph solve so the two overlap; summary composition runs as a
323
+ Kahn-style ready-queue wavefront over the SCC condensation DAG (the SCC is the atomic unit).
324
+ `--jobs N` output is **byte-identical** to `--jobs 1` (node ids are span-ordered, all edge lists
325
+ are collect-then-sorted, and the SCC fixpoint is a pure function of its inputs) — enforced by a
326
+ differential test. It is off by default and worth opting into only on large codebases: ts-morph
327
+ ASTs cannot cross the worker boundary, so each extraction worker loads its own copy of the
328
+ program, which dominates the parallelizable graph math on small/mid repos (self-analysis runs
329
+ 2.5× slower at `-j 14`). Worker failure at any stage degrades to the sequential path with a
330
+ warning — never to wrong or missing output.
331
+
332
+ Levels 1/2 are unaffected: nothing in level 3 runs unless `-a 3` is requested.
333
+
334
+ ### Neo4j graph
335
+
336
+ `--emit neo4j` projects the **same v2 tree** into a labeled property graph: every node keyed by its
337
+ `can://` id under a shared `:CanNode` merge label (+ a `TS`-prefixed specific kind label, e.g.
338
+ `:TSModule`, `:TSCallable`), containment as `TS_HAS_MODULE`/`TS_DECLARES`/`TS_HAS_METHOD`/
339
+ `TS_HAS_FIELD`/`TS_HAS_BODY_NODE` edges, and the overlays (`TS_CALLS`, `TS_CFG_NEXT`, `TS_CDG`,
340
+ `TS_DDG`, `TS_SUMMARY`, `TS_PARAM_IN`, `TS_PARAM_OUT`) as typed relationships. The graph is
341
+ **always full-depth** — analysis levels gate the JSON path only, so combining `-a`/`--graphs` with
342
+ `--emit neo4j` is an error:
343
+
344
+ - **Without `--neo4j-uri`** — writes a self-contained `graph.cypher` (constraints + indexes, a
345
+ scoped wipe, then batched `MERGE`s). Load it with `cypher-shell < graph.cypher`.
346
+ - **With `--neo4j-uri`** — pushes to a live Neo4j over Bolt **incrementally**: only modules whose
347
+ content hash changed are rewritten, and on a full run modules whose source file vanished are
348
+ pruned. Every graph carries a `schema_version` on its `:Application` node.
349
+
350
+ The connection options also read the standard Neo4j environment variables — `NEO4J_URI`,
351
+ `NEO4J_USERNAME`, `NEO4J_PASSWORD`, `NEO4J_DATABASE` — when the corresponding flag is omitted (an
352
+ explicit flag wins). Prefer the env var for the password so it doesn't land in shell history or the
353
+ process list:
354
+
355
+ ```sh
356
+ export NEO4J_URI=bolt://localhost:7687
357
+ export NEO4J_PASSWORD=secret
358
+ cants --input ./my-ts-project --emit neo4j # credentials picked up from the environment
359
+ ```
360
+
361
+ ### Schema contract
362
+
363
+ `--emit schema` writes the machine-readable, version-stamped Neo4j schema (`schema.json`: node
364
+ labels, relationships, properties, constraints, and indexes). It needs no project and is bundled in
365
+ every release (as a wheel asset and a GitHub Release asset), so a consumer can validate
366
+ producer/consumer compatibility without invoking the binary.
367
+
368
+ ```sh
369
+ cants --emit schema # print to stdout
370
+ cants --emit schema --output ./out # → ./out/schema.json
371
+ ```
372
+
373
+ ## Development
374
+
375
+ This project uses [Bun](https://bun.sh/) as its toolchain.
376
+
377
+ ```sh
378
+ bun install
379
+ bun run start -- --input /path/to/project # run from source
380
+ bun run typecheck # type-check
381
+ bun test # tests (the Neo4j bolt test is opt-in; see below)
382
+ bun run test:container # Neo4j bolt tests — needs Docker/Podman (opt-in)
383
+ bun run gen:schema # regenerate schema.neo4j.json
384
+ bun run gen:readme # regenerate the cants --help block above
385
+ ```
386
+
387
+ ## License
388
+
389
+ Apache 2.0 — see [LICENSE](./LICENSE).
@@ -0,0 +1,9 @@
1
+ codeanalyzer_typescript/__init__.py,sha256=yjjO6E6NHQH_VDGqD_Zsyx8XR4psjlzrQVQFENtSFUg,2734
2
+ codeanalyzer_typescript/__main__.py,sha256=f8C3GgPB5RutSyYkwjas31c3M0zae5sKeudYWXHuQIg,1054
3
+ codeanalyzer_typescript/_bin/.gitignore,sha256=_mZ_61eDWQ65V05VrmLP33rSeppQmwCwV8_qsgHUOHM,251
4
+ codeanalyzer_typescript/_bin/cants,sha256=fc-eed0xnqIA7mqu4QALrwh4E3m4aZtAiojN-xglkHI,76972146
5
+ codeanalyzer_typescript/_bin/schema.json,sha256=5eX2C8DxsZQoN3UXa5NptZZItqbGeqh_Of484JR02Sw,13379
6
+ codeanalyzer_typescript-1.4.0.dist-info/METADATA,sha256=XsBFOyjNbWBv8VdlaAwursFpFhtc3pEtM1KXqDIPlXc,19798
7
+ codeanalyzer_typescript-1.4.0.dist-info/WHEEL,sha256=DaKw4g5QhaGEJdGocOieQE7NrIV0L6BFOhZh6JlDrJk,102
8
+ codeanalyzer_typescript-1.4.0.dist-info/entry_points.txt,sha256=-kWzUa53_kUEbiwup53Q5GqeSOdQM1iIRJHzPIwV7S0,64
9
+ codeanalyzer_typescript-1.4.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-macosx_11_0_arm64
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cants = codeanalyzer_typescript.__main__:main