JavaScript examples
CLDK has no separate JavaScript factory: call CLDK.typescript(project_path=...) on the JavaScript project. The TypeScript backend reads .js, .jsx, .mjs, and .cjs files as first-class input, with or without a tsconfig.json. These examples analyze a plain JavaScript project called my_app, the recurring sample on this page. It has a package.json, no tsconfig.json, and five source files: src/index.js, src/lib/greeter.js, src/util.mjs, src/legacy.cjs, and src/App.jsx.
Set up the analysis
Section titled “Set up the analysis”Build a CLDK analysis object for your JavaScript project with the TypeScript factory. By default it extracts the symbol table: modules, classes, functions, and imports. For call-graph queries, add analysis_level=AnalysisLevel.call_graph.
from cldk import CLDKfrom cldk.analysis import AnalysisLevel
# Symbol table only (default)analysis = CLDK.typescript(project_path="my_app")
# Symbol table + call graph (required for callers/callees)analysis = CLDK.typescript( project_path="my_app", analysis_level=AnalysisLevel.call_graph,)The call-graph examples depend on src/index.js, which calls into two other files and into console.log. src/lib/greeter.js exports the class Greeter with a constructor and a greet method.
import { Greeter } from "./lib/greeter.js";import { slug } from "./util.mjs";export function main() { const g = new Greeter("world"); console.log(g.greet(), slug("Hello World"));}main();Symbol table: modules, classes, functions
Section titled “Symbol table: modules, classes, functions”List all modules
Section titled “List all modules”Each JavaScript file becomes a TSModule in the symbol table. The key is the file path with its extension. The module id uses the javascript namespace.
# Get all modules as a dictionary: file_path -> TSModulesymbol_table = analysis.get_symbol_table()for file_path, module in symbol_table.items(): print(file_path, "->", module.id)# src/App.jsx -> can://my_app/javascript/src/App.jsx# src/index.js -> can://my_app/javascript/src/index.js# src/legacy.cjs -> can://my_app/javascript/src/legacy.cjs# src/lib/greeter.js -> can://my_app/javascript/src/lib/greeter.js# src/util.mjs -> can://my_app/javascript/src/util.mjs
# Or get a flat listmodules = analysis.get_modules()print(len(modules), "modules analyzed")# 5 modules analyzedList all classes
Section titled “List all classes”Get all classes by signature. A signature is the file path without its extension, then the class name. get_typescript_file maps a signature back to its file.
classes = analysis.get_classes()for signature, tsclass in classes.items(): print(f"{signature}: {list(tsclass.methods.keys())}")# src/lib/greeter.Greeter: ['greet', 'constructor']
print(analysis.get_typescript_file("src/lib/greeter.Greeter"))# src/lib/greeter.jsGet a specific method
Section titled “Get a specific method”Get the exact method object by class signature and method name. It includes the signature, the inferred return type, and the source body.
method = analysis.get_method("src/lib/greeter.Greeter", "greet")if method: print("Signature:", method.signature) print("Return type:", method.return_type) print("Source:", method.code)# Signature: src/lib/greeter.Greeter.greet# Return type: string# Source: greet() { return `hello ${this.name}`; }List all functions
Section titled “List all functions”Get module-level functions by signature. The sample has one function in each of four files, and every one has kind function.
functions = analysis.get_functions()for signature, fn in functions.items(): print(f"{signature}: {fn.kind}, parameters {[p.name for p in fn.parameters]}")# src/App.App: function, parameters ['{ name }']# src/index.main: function, parameters []# src/legacy.add: function, parameters ['a', 'b']# src/util.slug: function, parameters ['s']List all imports
Section titled “List all imports”Get imports for every module. resolved_module names the symbol-table key that an import resolves to. Only src/index.js has imports in the sample.
imports = analysis.get_imports()for file_path, import_list in imports.items(): for imp in import_list: print(f"{file_path}: {imp.name} from {imp.module} -> {imp.resolved_module}")# src/index.js: Greeter from ./lib/greeter.js -> src/lib/greeter.js# src/index.js: slug from ./util.mjs -> src/util.mjsCall graph: callers and callees
Section titled “Call graph: callers and callees”Build a call graph
Section titled “Build a call graph”The call graph is a networkx.DiGraph. Its nodes are the endpoints of call edges, and each node carries a kind attribute. A module node is the caller of its top-level code. An external node is a library or builtin target. eager=True forces a fresh backend run, because the setup step filled the default cache at the symbol-table level.
from cldk import CLDKfrom cldk.analysis import AnalysisLevel
analysis = CLDK.typescript( project_path="my_app", analysis_level=AnalysisLevel.call_graph, eager=True,)
cg = analysis.get_call_graph()print(f"{cg.number_of_nodes()} nodes, {cg.number_of_edges()} edges")for node in sorted(cg.nodes): print(f"{cg.nodes[node]['kind']}: {node}")# 6 nodes, 5 edges# external: (builtin).log# module: src/index.js# callable: src/index.main# callable: src/lib/greeter.Greeter.constructor# callable: src/lib/greeter.Greeter.greet# callable: src/util.slugFind who calls a method
Section titled “Find who calls a method”Use get_callers to find every caller of a method. The caller src/index.main is in a different file from the class.
callers = analysis.get_callers( target_class_name="src/lib/greeter.Greeter", target_method_declaration="greet",)print("Target:", callers["target_method"])for detail in callers["caller_details"]: print(detail["caller_signature"], detail["edge"]["provenance"])# Target: src/lib/greeter.Greeter.greet# src/index.main ('tsc',)Find what a function calls
Section titled “Find what a function calls”Use get_callees with a bare signature for a module-level function. The console.log call becomes the external target (builtin).log, and its edge has import provenance.
callees = analysis.get_callees("src/index.main")for detail in sorted(callees["callee_details"], key=lambda d: d["callee_signature"]): print(detail["callee_signature"], detail["edge"]["provenance"])# (builtin).log ('import',)# src/lib/greeter.Greeter.constructor ('tsc',)# src/lib/greeter.Greeter.greet ('tsc',)# src/util.slug ('tsc',)List external targets
Section titled “List external targets”Get every external target that the call graph points at. The key is the call-graph node key, and the value carries the can:// id.
externals = analysis.get_external_symbols()for key, ext in externals.items(): print(key, "->", ext.id)# (builtin).log -> can://my_app/@external/(builtin)/logLocate a source position 2.0
Section titled “Locate a source position ”locate resolves a file path and a 1-based line to the enclosing callable. Line 4 of src/lib/greeter.js is the greet method.
loc = analysis.locate("src/lib/greeter.js", 4)print("Callable:", loc.callable.signature)print("Class:", loc.callable.class_signature)print("Module:", loc.module.path)print("Source:", loc.source)# Callable: src/lib/greeter.Greeter.greet# Class: src/lib/greeter.Greeter# Module: src/lib/greeter.js# Source: greet() { return `hello ${this.name}`; }What is different for JavaScript
Section titled “What is different for JavaScript”The backend runs the same pipeline on JavaScript as on TypeScript. These points are where the result differs.
- Type facets are weak. The TypeScript checker infers types from untyped JavaScript, so many facets read
any. Forsrc/util.slug, the parametersand the return type both readany. The backend keeps JSDoc blocks as comments withis_docstringset toTrue, but it has no JSDoc type support of its own. - Destructured parameters surface as raw text. The parameter of
App({ name })insrc/App.jsxhas the name'{ name }'. - The backend does not model dynamic patterns. The backend README describes the analysis as sound-leaning and over-approximate. Its recorded unsoundness covers dynamic
evalandFunction, reflection and monkey patches, dynamic property names, and npm-internal global effects. - The backend reads no embedded-script formats. Discovery reads only the eight TypeScript and JavaScript extensions. It does not analyze the script inside
.vue,.svelte, or other single-file components. is_tsxstaysFalseon.jsx. The backend finds.jsxfiles, but the flag marks TSX files only.- The backend skips tests. The SDK always passes
--skip-tests, and there is no SDK argument to include tests. The backend excludes thetest,tests,spec,__tests__,__test__, and__mocks__directories at any depth. It also excludes files named*.test.jsor*.spec.js, with the same rule for the other seven extensions. The directory walk skipsnode_modules,.git,.codeanalyzer,dist,build,out,coverage,.next,.turbo,.cache, andvendor. - The backend needs npm for library targets. If the project has a
package.jsonand nonode_modules, the backend installs dependencies first. It runsnpm ci --ignore-scriptswhen apackage-lock.jsonexists, andnpm install --ignore-scriptswhen no lockfile exists. Apnpm-lock.yamlor ayarn.lockselects pnpm or yarn instead. The SDK does not pass--no-build. If the install fails, for example becausenpmis not onPATH, the backend continues with partial types and records a note. - Entrypoint rules need a present framework. The shipped rules detect a framework when first-party source imports its package or when
package.jsondeclares it as a dependency. Formy_app,get_entrypoint_coverage()2.0 reportsframeworks_detected: [].
Mixed projects
Section titled “Mixed projects”A project can hold TypeScript and JavaScript files together. This example analyzes mixed_app. Its tsconfig.json has include: ["src/**/*.ts"]. Its sources are src/index.ts, src/legacy.js outside that include, and src/helper.ts beside a compiled src/helper.js. Two discovery rules decide what the symbol table holds.
- Discovery ignores tsconfig
include. The backend analyzessrc/legacy.jsunder thejavascriptnamespace. The call fromsrc/index.runtosrc/legacy.legacyAddresolves across languages. The functionunreferencedinsrc/legacy.js, which no TypeScript file imports, is also in the symbol table. - The backend skips a compiled sibling. A
.jsfile with the same prefix as a real (non-declaration).ts,.tsx,.mts, or.ctsfile counts as compiled output. The backend skips it, sosrc/helper.jsis absent from the symbol table. The import./helperinsrc/index.tsresolves tosrc/helper.ts. A.d.tsfile beside an analyzed.jsfile is not a module, but the checker still reads it.
analysis = CLDK.typescript( project_path="mixed_app", analysis_level=AnalysisLevel.call_graph,)
symbol_table = analysis.get_symbol_table()print(list(symbol_table))# ['src/helper.ts', 'src/index.ts', 'src/legacy.js']print(symbol_table["src/legacy.js"].id)# can://mixed_app/javascript/src/legacy.jsprint("src/helper.js" in symbol_table)# False
print(list(analysis.get_functions()))# ['src/helper.helper', 'src/index.run', 'src/legacy.legacyAdd', 'src/legacy.unreferenced']
callees = analysis.get_callees("src/index.run")print(sorted(d["callee_signature"] for d in callees["callee_details"]))# ['src/helper.helper', 'src/legacy.legacyAdd']