Skip to content

TypeScript examples

These examples run against Zod, a TypeScript schema declaration library, and its published package zod/packages/zod. Each example calls one method of the CLDK Python SDK. A real run of CLDK 2.0.0rc7 over Zod produced every output comment on this page. A section with the 2.0 badge needs the pre-release, so install it with pip install --pre cldk. The stable release on PyPI is 1.5.0. Some examples carry no output comment, because that run did not record one.

Clone Zod first.

Terminal window
git clone https://github.com/colinhacks/zod.git

Then point CLDK at the published package.

from cldk import CLDK
from cldk.analysis import AnalysisLevel
analysis = CLDK.typescript(
project_path="zod/packages/zod",
analysis_level=AnalysisLevel.call_graph,
)

The zod/packages/zod directory holds both the v3 and the v4 source trees. A signature under one of those trees carries a src/v3 or a src/v4 prefix. The default analysis level builds the symbol table only. The call_graph level adds the caller and callee edges.

The backend reuses a cached analysis.json at any level. If an earlier run wrote that cache at the symbol-table level, pass eager=True once.

analysis = CLDK.typescript(
project_path="zod/packages/zod",
analysis_level=AnalysisLevel.call_graph,
eager=True,
)

Symbol table: modules, classes, and methods

Section titled “Symbol table: modules, classes, and methods”

The symbol table holds one TSModule per source file. Each key is the package-relative file path with its extension.

modules = analysis.get_modules()
print(len(modules), "modules")
symbol_table = analysis.get_symbol_table()
for file_path in sorted(symbol_table)[:5]:
print(file_path)
# expected output:
# 135 modules
# src/compile.ts
# src/index.ts
# src/locales/index.ts
# src/mini/index.ts
# src/v3/ZodError.ts

A class signature is the file path without its extension, then the dotted member path.

classes = analysis.get_classes()
print(len(classes), "classes")
for signature in sorted(classes)[:6]:
print(signature)
# expected output:
# 53 classes
# src/v3/ZodError.ZodError
# src/v3/helpers/parseUtil.ParseStatus
# src/v3/types.Class
# src/v3/types.ParseInputLazyPath
# src/v3/types.ZodAny
# src/v3/types.ZodArray

Get one class by its signature. Read methods and base_classes from the TSClass that comes back.

tsclass = analysis.get_class("src/v3/ZodError.ZodError")
print(tsclass.signature)
print(sorted(tsclass.methods))
print(tsclass.base_classes)

Get every field of one class. Each field is a TSField with a name and a type.

fields = analysis.get_fields("src/v3/types.ZodArray")
for field in fields:
print(field.name, field.type)

Get one method by the class signature and the method name. Read its source from code.

methods = analysis.get_methods_in_class("src/v3/ZodError.ZodError")
name = sorted(methods)[0]
method = analysis.get_method("src/v3/ZodError.ZodError", name)
print(method.signature)
print(method.return_type)
print(method.code)

Match an inclusion pattern and an exclusion pattern against the class signature.

matches = analysis.get_classes_by_criteria(
inclusions=["ZodError"],
exclusions=["v4"],
)
for signature in sorted(matches):
print(signature)

Get the imports of every module. Each entry is a TSImport with the imported name and the resolved module path.

imports = analysis.get_imports()
for file_path, import_list in sorted(imports.items())[:3]:
print(file_path, len(import_list), "imports")

Type declarations: interfaces, enums, and type aliases

Section titled “Type declarations: interfaces, enums, and type aliases”

TypeScript has type declarations that Python does not have. Zod declares its error types in src/v3/ZodError.ts.

Zod declares an interface for each kind of validation issue.

interfaces = analysis.get_interfaces()
for signature in sorted(interfaces)[:4]:
print(signature)
# expected output:
# src/v3/ZodError.ZodCustomIssue
# src/v3/ZodError.ZodInvalidArgumentsIssue
# src/v3/ZodError.ZodInvalidDateIssue
# src/v3/ZodError.ZodInvalidEnumValueIssue

Zod declares two enums, one in each source tree.

src/v3/types.ZodFirstPartyTypeKind
for signature in sorted(analysis.get_enums()):
print(signature)
# expected output:
# src/v4/classic/compat.ZodFirstPartyTypeKind

To read the members of one enum, call get_enum_members with the enum signature. Each member holds a name and a value.

for member in analysis.get_enum_members("src/v3/types.ZodFirstPartyTypeKind"):
print(member.name, "=", member.value)

A type alias keeps the same signature shape as a class or an interface.

type_aliases = analysis.get_type_aliases()
for signature in sorted(type_aliases)[:6]:
print(signature)
# expected output:
# src/v3/ZodError.DenormalizedError
# src/v3/ZodError.ErrorMapCtx
# src/v3/ZodError.IssueData
# src/v3/ZodError.StringValidation
# src/v3/ZodError.ZodErrorMap
# src/v3/ZodError.ZodFormattedError

The backend captures each decorator with its arguments. Pass a class signature to get_class_decorators. Pass a list of decorator names to get_methods_with_decorators.

print(analysis.get_class_decorators("src/v3/types.ZodArray"))
print(analysis.get_methods_with_decorators(["Get"]))

Call graph: callers, callees, and reachability

Section titled “Call graph: callers, callees, and reachability”

Every call-graph query needs analysis_level=AnalysisLevel.call_graph. The default level builds the symbol table only, and it resolves no call targets.

The call graph is a networkx.DiGraph, and its edges point from the caller to the callee.

cg = analysis.get_call_graph()
print(cg.number_of_nodes(), "nodes,", cg.number_of_edges(), "edges")
# expected output:
# 2255 nodes, 4610 edges

An external symbol is a call target that the backend never analyzed. Its key is the module name and the symbol name. A runtime built-in has the module name (builtin).

externals = sorted(analysis.get_external_symbols())
print(externals[:4])
# expected output:
# ['(builtin).abs', '(builtin).add', '(builtin).all', '(builtin).apply']

The result holds target_method and a list of caller_details. Each entry in caller_details holds a caller_signature and an edge with type, weight, and provenance. The provenance tuple names the resolver that found the edge.

target = sorted(analysis.get_methods_in_class("src/v3/ZodError.ZodError"))[0]
callers = analysis.get_callers(
target_class_name="src/v3/ZodError.ZodError",
target_method_declaration=target,
)
print(callers["target_method"])
for detail in callers["caller_details"]:
print(detail["caller_signature"], detail["edge"]["provenance"])

The result holds source_method and a list of callee_details, with the same edge shape.

source = sorted(analysis.get_methods_in_class("src/v3/types.ZodArray"))[0]
callees = analysis.get_callees(
source_class_name="src/v3/types.ZodArray",
source_method_declaration=source,
)
for detail in callees["callee_details"]:
print(detail["callee_signature"], detail["edge"]["provenance"])

Get every call edge reachable from the methods of one class. To start from one method, pass its full signature as the second argument.

edges = analysis.get_class_call_graph("src/v3/types.ZodArray")
print(len(edges), "edges")
for caller, callee in edges[:4]:
print(caller, "->", callee)

Reachability is a networkx query over the call graph. The key of a callable node is its signature, so pass the key to nx.has_path() directly.

import networkx as nx
cg = analysis.get_call_graph()
# Pick a source node and a target node from the graph itself
source = sorted(n for n in cg.nodes if n.startswith("src/v3/types.ZodArray."))[0]
target = sorted(n for n in cg.nodes if n.startswith("src/v3/ZodError.ZodError."))[0]
if nx.has_path(cg, source, target):
print(" -> ".join(nx.shortest_path(cg, source, target)))

Find the call paths between two callables 2.0

Section titled “Find the call paths between two callables ”

reaches() and call_paths_between() take a callable name. A name matches a whole signature, or a dotted suffix of one. Each path is a list of hops, and each hop has a frm node and a to node.

methods = analysis.get_methods_in_class("src/v3/ZodError.ZodError")
source = analysis.get_method("src/v3/ZodError.ZodError", sorted(methods)[0]).signature
target = "src/v3/ZodError.quotelessJson"
print(analysis.reaches(source, target))
result = analysis.call_paths_between(source, target)
for path in result.paths:
nodes = [path.hops[0].frm.callable]
nodes += [hop.to.callable for hop in path.hops]
print(" -> ".join(nodes))

CLDK returns typed model objects, not plain strings. The run for this page did not record the field values of these objects for Zod. For the full field list of each model, read the TypeScript API reference.

A TSClass holds the methods, the fields, the base classes, the implemented interfaces, and the line span of a class.

tsclass = analysis.get_class("src/v3/types.ZodArray")
print(tsclass.name)
print(tsclass.signature)
print(tsclass.base_classes)
print(tsclass.start_line, "to", tsclass.end_line)

A TSCallable represents a function, a method, a constructor, an accessor, or an arrow function. Its kind is one of function, method, constructor, getter, setter, arrow, and function_expression.

methods = analysis.get_methods_in_class("src/v3/types.ZodArray")
method = methods[sorted(methods)[0]]
print(method.kind, "->", method.return_type)
print("Async:", method.is_async)
print("Complexity:", method.cyclomatic_complexity)
print(method.code)

Read the base classes of a class. Find the direct subclasses of a class. get_sub_classes() also accepts an interface signature. get_nested_classes() always returns [] on schema v2, because a class node has no bucket for nested types.

print(analysis.get_extended_classes("src/v3/types.ZodArray"))
print(analysis.get_implemented_interfaces("src/v3/types.ZodArray"))
print(list(analysis.get_sub_classes("src/v3/types.ZodAny").keys()))
print(analysis.get_nested_classes("src/v3/types.ZodArray"))

locate() resolves a file path and a 1-based line number to the callable that encloses that line. The result also names the type that owns the callable, the module, and the innermost body node. If the line sits at module scope, callable is None and diagnostics holds a module_scope code.

loc = analysis.locate("src/v3/ZodError.ts", 100)
print(loc.callable)
print(loc.module.path)
print([d.code for d in loc.diagnostics])

get_source() returns the source text of one callable signature.

print(analysis.get_source("src/v3/ZodError.quotelessJson"))