Tern LogoTern

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 file
  • lineStart: Starting line (1-indexed), or undefined for whole file
  • lineEnd: 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.).