An AST node can be a container. This determines the kinds of SymbolTables the Node and associated Symbol will have. Container is an abstract concept (i.e. has no associated data structure). The concept is driven by a few things, one being the ContainerFlags enum. The function getContainerFlags (in binder.ts) drives this flag and is presented below:
functiongetContainerFlags(node:Node):ContainerFlags {switch (node.kind) {caseSyntaxKind.ClassExpression:caseSyntaxKind.ClassDeclaration:caseSyntaxKind.InterfaceDeclaration:caseSyntaxKind.EnumDeclaration:caseSyntaxKind.TypeLiteral:caseSyntaxKind.ObjectLiteralExpression:returnContainerFlags.IsContainer;caseSyntaxKind.CallSignature:caseSyntaxKind.ConstructSignature:caseSyntaxKind.IndexSignature:caseSyntaxKind.MethodDeclaration:caseSyntaxKind.MethodSignature:caseSyntaxKind.FunctionDeclaration:caseSyntaxKind.Constructor:caseSyntaxKind.GetAccessor:caseSyntaxKind.SetAccessor:caseSyntaxKind.FunctionType:caseSyntaxKind.ConstructorType:caseSyntaxKind.FunctionExpression:caseSyntaxKind.ArrowFunction:caseSyntaxKind.ModuleDeclaration:caseSyntaxKind.SourceFile:caseSyntaxKind.TypeAliasDeclaration:returnContainerFlags.IsContainerWithLocals;caseSyntaxKind.CatchClause:caseSyntaxKind.ForStatement:caseSyntaxKind.ForInStatement:caseSyntaxKind.ForOfStatement:caseSyntaxKind.CaseBlock:returnContainerFlags.IsBlockScopedContainer;caseSyntaxKind.Block:// do not treat blocks directly inside a function as a block-scoped-container.// Locals that reside in this block should go to the function locals. Otherwise 'x'// would not appear to be a redeclaration of a block scoped local in the following// example://// function foo() {// var x;// let x;// }//// If we placed 'var x' into the function locals and 'let x' into the locals of// the block, then there would be no collision.//// By not creating a new block-scoped-container here, we ensure that both 'var x'// and 'let x' go into the Function-container's locals, and we do get a collision// conflict.returnisFunctionLike(node.parent) ?ContainerFlags.None :ContainerFlags.IsBlockScopedContainer; }returnContainerFlags.None;}
It is only invoked from the binder's bindChildren function which sets up a node as a container and/or a blockScopedContainer depending upon the evaluation of the getContainerFlags function. The function bindChildren is presented below:
// All container nodes are kept on a linked list in declaration order. This list is used by// the getLocalNameOfContainer function in the type checker to validate that the local name// used for a container is unique.functionbindChildren(node:Node) {// Before we recurse into a node's children, we first save the existing parent, container// and block-container. Then after we pop out of processing the children, we restore// these saved values.let saveParent = parent;let saveContainer = container;let savedBlockScopeContainer = blockScopeContainer;// This node will now be set as the parent of all of its children as we recurse into them. parent = node;// Depending on what kind of node this is, we may have to adjust the current container// and block-container. If the current node is a container, then it is automatically// considered the current block-container as well. Also, for containers that we know// may contain locals, we proactively initialize the .locals field. We do this because// it's highly likely that the .locals will be needed to place some child in (for example,// a parameter, or variable declaration).//// However, we do not proactively create the .locals for block-containers because it's// totally normal and common for block-containers to never actually have a block-scoped// variable in them. We don't want to end up allocating an object for every 'block' we// run into when most of them won't be necessary.//// Finally, if this is a block-container, then we clear out any existing .locals object// it may contain within it. This happens in incremental scenarios. Because we can be// reusing a node from a previous compilation, that node may have had 'locals' created// for it. We must clear this so we don't accidentally move any stale data forward from// a previous compilation.let containerFlags =getContainerFlags(node);if (containerFlags &ContainerFlags.IsContainer) { container = blockScopeContainer = node;if (containerFlags &ContainerFlags.HasLocals) {container.locals = {}; }addToContainerChain(container); }elseif (containerFlags &ContainerFlags.IsBlockScopedContainer) { blockScopeContainer = node;blockScopeContainer.locals =undefined; }forEachChild(node, bind); container = saveContainer; parent = saveParent; blockScopeContainer = savedBlockScopeContainer;}
As you might recall from the section on binder functions : bindChildren is called from the bind function. So we have the recursive binding setup : bind calls bindChildren calls bind for each child.