microsoft/TypeScript
Binder
The binder walks each parsed SourceFile and computes:
- Symbol tables for every container (modules, blocks, classes, functions).
- Flow nodes — a control-flow graph used by the checker for narrowing and reachability.
- Per-file metadata the checker depends on (
commonJsModuleIndicator,externalModuleIndicator,bindDiagnostics, …).
It runs once per SourceFile, eagerly, before the checker sees the file.
Source
Single file: src/compiler/binder.ts (~3,900 lines).
Purpose
The parser produces an unannotated tree. The binder is the first pass that understands the tree as a program: it decides which declarations are alive in which scopes, merges interface declarations that share a name, classifies class members vs. instance members, and constructs the flow graph that powers if (typeof x === "string")-style narrowing.
The binder also detects the kind of file:
- ES module (has
export/import) - CommonJS-augmented JS (assigns to
module.exports) - Script (no module syntax)
- Plain
.jsonimport
Each detection sets a flag on the SourceFile that the program uses to drive module resolution and emit.
Key abstractions
| Symbol | Role |
|---|---|
bindSourceFile(file, options) |
Public entry — binds one file. Idempotent: repeated calls no-op |
Symbol, SymbolFlags |
The unit of declaration; see primitives/symbol |
SymbolTable |
A Map<__String, Symbol> keyed on escaped names |
FlowNode, FlowFlags |
Edges in the flow graph: assignments, branches, calls |
Container (predicates canHaveLocals, canHaveSymbol, canHaveFlowNode) |
Nodes that own a SymbolTable and a flow graph |
bindContainer |
The recursive walk |
addToContainerChain |
Attaches symbol-bearing nodes to the active container |
SymbolFlags is a packed bit-set — Variable | Function | Class | Interface | TypeAlias | Property | Method | …. Multiple bits can be combined when declarations merge (e.g., a class Foo and an interface Foo produce a Symbol with both flags).
How it works
graph TD
Bind["bindSourceFile(file)"] --> Walk["bindEachChild"]
Walk --> Decl{"Is declaration?"}
Decl -->|yes| AddSym["declareSymbol → container.locals/exports/members"]
Decl -->|no| Cont["Continue walking"]
Walk --> Flow{"Is control-flow significant?"}
Flow -->|yes| FN["createFlowNode → set node.flowNode"]
Walk --> Diag["report bind diagnostics (duplicate identifier, etc.)"]
Walk --> Detect["set externalModuleIndicator etc."]Symbol declaration
declareSymbol is the workhorse. It walks up to the active container, looks up the name in the appropriate SymbolTable (locals for value scope, exports for module scope, members for class instance/static, etc.), and either creates a new Symbol or merges with an existing one. Merging is what makes interface/namespace/class merging work in TypeScript.
Flow graph
Every "interesting" expression — if, while, &&, ||, switch, ternary, assignment, function call, type guard — gets a FlowNode describing how control reaches it. The checker walks this graph backwards from a use site to compute the narrowed type at that point. Flow nodes are stored on the AST nodes themselves (node.flowNode) and survive incremental edits as long as the surrounding tree is reused.
Module / script detection
The binder watches for import, export, and CommonJS module.exports = ... patterns. Setting externalModuleIndicator flips a SourceFile from script to module mode, which changes how the program treats top-level declarations (private vs. exported) and which lib globals are visible.
commonJsModuleIndicator and assignmentDeclarationKind
For --allowJs, the binder recognises common JS idioms that look like declarations: Foo.prototype.bar = function () {}, module.exports.foo = ..., exports.bar = ..., Foo.X = 0. Each pattern is given an AssignmentDeclarationKind so the checker can synthesise types for them.
Diagnostics
The binder emits a small but important set of diagnostics:
Duplicate identifierCannot redeclare block-scoped variable'this' implicitly has type 'any' because it does not have a type annotation'super' must be called before accessing 'this' in the constructor of a derived class- A handful of
withand reserved-word errors
These appear on SourceFile.bindDiagnostics. Most type errors come later in the checker.
Integration points
- Called from
src/compiler/program.tson demand — the program lazily binds files when the checker first asks for them. - Reads
CompilerOptions.target,CompilerOptions.module,CompilerOptions.allowJsto decide JS-aware behaviours. - Outputs are consumed almost entirely by
src/compiler/checker.ts. - The flow graph is used by control-flow analysis (search for
getFlowTypeOfReferenceinchecker.ts).
Entry points for modification
If you're touching the binder you almost certainly are:
- Adding a new declaration kind — extend
declareSymbolto recognise it, give it aSymbolFlagsvalue, and ensurenodeTests.tspredicates classify it correctly. - Adding a new flow construct — add a
FlowFlagsvalue, build the flow node where the construct is bound, and teachgetFlowTypeOfReferenceinchecker.tsto read it. - Adding a new JS assignment idiom — extend
getAssignmentDeclarationKindandbindCommonJsModuleAssignment/ siblings.
See systems/checker for what consumes the binder's output, and primitives/symbol for the full Symbol model.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.