SDK Reference
Tern provides a Typescript SDK embedded in the CLI to quickly compute attributes of hits in your migration. It can be used to explore codeowners, file complexity, and more.
Overview
A few examples:
// Count imports in a file
return typescriptImports().length;
// Check if this is a test file
return filename().includes(".test.") || filename().includes(".spec.");Context
Every script runs with an implicit context:
filepath: Absolute path to the filelineStart: Starting line (1-indexed), or undefined for whole filelineEnd: Ending line (1-indexed), or undefined for whole file
Path Functions
Pure string operations on the file path. Always available.
| Function | Returns | Description |
|---|---|---|
filepath() |
string |
Absolute file path |
directory() |
string |
Directory containing the file |
filename() |
string |
File name with extension |
extension() |
string |
File extension (e.g., .tsx) |
filepath(); // → "/project/src/components/Button.tsx"
directory(); // → "/project/src/components"
filename(); // → "Button.tsx"
extension(); // → ".tsx"File Content Functions
Read from the filesystem.
| Function | Returns | Description |
|---|---|---|
fileContent() |
string | null |
Entire file content |
lineContent() |
string | null |
Content for context line range |
Line Range Functions
| Function | Returns | Description |
|---|---|---|
lineStart() |
number | undefined |
Starting line (1-indexed) from context |
lineEnd() |
number | undefined |
Ending line (1-indexed) from context |
Git Functions
Shell out to git. Return null if not in a git repo.
| Function | Returns | Description |
|---|---|---|
codeowners() |
string[] |
CODEOWNERS entries matching this file |
gitBlame() |
BlameInfo[] | null |
Blame info per line |
gitLastModified() |
Date | null |
Last modification date |
codeowners();
// → ["@frontend-platform", "@design-systems"]
gitLastModified();
// → Date("2025-12-23T17:29:50.000Z")
// Days since last modified:
const d = gitLastModified();
return d ? Math.floor((Date.now() - d.getTime()) / 86400000) : null;TypeScript Functions
Parse TypeScript/JavaScript files (.ts, .tsx, .js, .jsx, .mjs, .cjs). Return empty arrays for non-TS/JS files.
| Function | Returns | Description |
|---|---|---|
typescriptImports() |
TypeScriptImport[] |
All imports in the file |
typescriptSymbols() |
TypeScriptSymbol[] |
All symbols (functions, classes, types, etc.) |
typescriptExports() |
TypeScriptExport[] |
All exports from the file |
typescriptCalls(filter?) |
TypeScriptCall[] |
All function/method calls (filter: {method?, func?}) |
typescriptImports();
// → [
// {source: "react", specifiers: [{name: "useState"}], default: "React", line: 1},
// {source: "./utils", specifiers: [{name: "formatDate", alias: "format"}], line: 2}
// ]
typescriptSymbols();
// → [
// {name: "Button", kind: "function", line: 14, endLine: 45, exported: true},
// {name: "ButtonProps", kind: "interface", line: 5, endLine: 12, exported: true}
// ]
typescriptExports();
// → [
// {name: "Button", kind: "function", line: 14},
// {name: "default", kind: "default", line: 100, originalName: "App"}
// ]Scope Analysis Functions
Navigate the AST to find enclosing scopes. Only available for TypeScript/JavaScript files.
| Function | Returns | Description |
|---|---|---|
enclosingScope() |
ScopeEntry[] |
Full scope stack for the current line, outermost first |
enclosingComponent() |
string | null |
Nearest exported PascalCase function (React component) |
enclosingNamedFunction() |
string | null |
Nearest named function, skipping anonymous callbacks |
enclosingClass() |
string | null |
Enclosing class name, if any |
scopeDepth() |
number |
Depth of nesting (how many scopes contain the line) |
namedScopeAt(n) |
string | null |
Nth named scope from outermost (0-indexed) |
All scope functions accept an optional line parameter (1-indexed). If not provided, they default to lineStart() from context.
// Given this code structure:
// 1: export function BenefitsList() {
// 2: const formatIcon = (item) => {
// 3: return items.map((x) => {
// 4: // lineStart = 4
// 5: });
// 6: };
// 7: }
enclosingScope();
// → [
// {name: "BenefitsList", kind: "function", line: 1, endLine: 7, exported: true},
// {name: "formatIcon", kind: "arrow_function", line: 2, endLine: 6, exported: false},
// {name: null, kind: "arrow_function", line: 3, endLine: 5, exported: false}
// ]
enclosingComponent();
// → "BenefitsList"
enclosingNamedFunction();
// → "formatIcon" (skips the anonymous .map callback)
scopeDepth();
// → 3
namedScopeAt(0);
// → "BenefitsList" (outermost named scope)
namedScopeAt(1);
// → "formatIcon" (next level)Python Functions
Parse Python files (.py, .pyi, .pyw). Return empty arrays for non-Python files.
| Function | Returns | Description |
|---|---|---|
pythonImports() |
PythonImport[] |
All imports in the file |
pythonSymbols() |
PythonSymbol[] |
All symbols (functions, classes, variables) |
pythonCalls(filter?) |
PythonCall[] |
All function/method calls (filter: {method?, func?}) |
pythonImports();
// → [
// {module: "os", names: [], isFrom: false, line: 1},
// {module: "typing", names: [{name: "List"}, {name: "Dict"}], isFrom: true, line: 2}
// ]
pythonSymbols();
// → [
// {name: "MyClass", kind: "class", line: 5, endLine: 18, private: false},
// {name: "process_data", kind: "function", line: 20, endLine: 32, private: false, decorators: ["staticmethod"]}
// ]Go Functions
Parse Go files (.go). Return empty arrays for non-Go files.
| Function | Returns | Description |
|---|---|---|
goImports() |
GoImport[] |
All imports in the file |
goSymbols() |
GoSymbol[] |
All symbols (functions, types, structs, etc.) |
goCalls(filter?) |
GoCall[] |
All function/method calls (filter: {method?, func?}) |
goImports();
// → [
// {path: "fmt", line: 3},
// {path: "github.com/pkg/errors", alias: "errors", line: 4}
// ]
goSymbols();
// → [
// {name: "MyStruct", kind: "struct", line: 10, endLine: 15, exported: true},
// {name: "ProcessData", kind: "function", line: 20, endLine: 30, exported: true},
// {name: "String", kind: "method", line: 40, endLine: 45, exported: true, receiver: "*MyStruct"}
// ]
goCalls({ method: "Println" });
// → [
// {callee: "fmt.Println", object: "fmt", method: "Println", line: 25, args: [...]}
// ]Types
TypeScriptImport
interface TypeScriptImport {
source: string; // Module specifier
specifiers: Array<{ name: string; alias?: string }>; // Named imports
default?: string; // Default import name
namespace?: string; // Namespace import (import * as X)
line: number; // Line number (1-indexed)
}TypeScriptSymbol
interface TypeScriptSymbol {
name: string;
kind:
| "function"
| "class"
| "variable"
| "interface"
| "type"
| "enum"
| "const";
line: number;
endLine: number;
exported: boolean;
}TypeScriptExport
interface TypeScriptExport {
name: string;
kind:
| "function"
| "class"
| "variable"
| "interface"
| "type"
| "enum"
| "const"
| "default"
| "re-export";
line: number;
originalName?: string; // Original name if aliased or default export
source?: string; // Source module if re-export
}PythonImport
interface PythonImport {
module: string; // Module being imported
names: Array<{ name: string; alias?: string }>; // Specific names imported
isFrom: boolean; // "from X import Y" style
alias?: string; // Module alias (import X as Y)
line: number;
}PythonSymbol
interface PythonSymbol {
name: string;
kind: "function" | "class" | "variable" | "async_function";
line: number;
endLine: number;
private: boolean; // Starts with _
decorators?: string[]; // Applied decorators
}GoImport
interface GoImport {
path: string; // Import path
alias?: string; // Alias if any
line: number;
}GoSymbol
interface GoSymbol {
name: string;
kind:
| "function"
| "type"
| "const"
| "var"
| "method"
| "interface"
| "struct";
line: number;
endLine: number;
exported: boolean; // Starts with uppercase
receiver?: string; // Receiver type for methods
}ScopeEntry
interface ScopeEntry {
name: string | null; // Symbol name, or null if anonymous
kind:
| "function"
| "arrow_function"
| "class"
| "method"
| "interface"
| "object_method";
line: number;
endLine: number;
exported: boolean;
}BlameInfo
interface BlameInfo {
commit: string; // Commit hash
author: string; // Author name
email: string; // Author email
date: Date; // Commit timestamp
line: string; // Line content
}Common Patterns
Is this a test file?
return filename().includes(".test.") || filename().includes(".spec.");Days since last modified
const d = gitLastModified();
return d ? Math.floor((Date.now() - d.getTime()) / 86400000) : null;Count external dependencies
return typescriptImports().filter((i) => !i.source.startsWith(".")).length;Find React components
return typescriptExports().filter(
(e) => e.kind === "function" && /^[A-Z]/.test(e.name),
);Get primary owner
const owners = codeowners();
return owners.length > 0 ? owners[0] : null;File has side effects (bare imports)
return typescriptImports().some(
(i) => i.specifiers.length === 0 && !i.default && !i.namespace,
);Group by React component
return enclosingComponent() ?? filename();Get the function containing this line
return enclosingNamedFunction();Check if inside a class
return enclosingClass() !== null;Caching Expensive Computations
Scripts run once per row, but the module loads only once. Code outside the default function runs once and is shared across all rows. Use this for expensive operations like loading large JSON files.
import * as fs from "fs";
import { filepath } from "tern-sdk";
// Runs ONCE when module loads
const graph = JSON.parse(fs.readFileSync("/path/to/graph.json", "utf-8"));
// Runs PER ROW
export default () => {
return graph.targets[filepath()]?.deps.length ?? 0;
};Build an index once, use it per row
import * as fs from "fs";
import { filepath } from "tern-sdk";
// Load and index once
const data = JSON.parse(fs.readFileSync("/repo/deps.json", "utf-8"));
const reverseDeps = new Map<string, string[]>();
for (const [target, info] of Object.entries(data.targets)) {
for (const dep of (info as any).deps || []) {
if (!reverseDeps.has(dep)) reverseDeps.set(dep, []);
reverseDeps.get(dep)!.push(target);
}
}
// Query per row
export default () => reverseDeps.get(filepath())?.length ?? 0;When to use this pattern
- Loading large JSON files (dependency graphs, coverage data)
- Building indexes or lookup tables
- Any computation that’s the same for all rows
The JSON file must exist before the script runs (generate via CI, bazel, etc.).
