UNPKG

microvium

Version:

A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.

843 lines (841 loc) 41 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.pass1_findScopesAndBindings = void 0; const utils_1 = require("../../utils"); const common_1 = require("../common"); const traverse_ast_1 = require("../traverse-ast"); const B = __importStar(require("../supported-babel-types")); function pass1_findScopesAndBindings({ file, cur, importBindings, model, }) { /* (See analyzeScopes for a description of this pass) This function is implemented as a single tree-traversal pass using `traverseAST`. It maintains a `scopeStack` to keep track of what lexical scope the cursor is in. When it encounters a new scope AST node (e.g. function or block scope), it will push the scope onto the stack and enumerate the local bindings. When it encounters a reference node (e.g. a variable or parameter reference), it iterates up the stack to find the binding or falls back to creating a free variable (`freeVariableNames`). A `this` reference can either resolve to a local argument (if using the caller-passed this) or to the `this` value in the parent (if using lexical this, as in arrow functions). The `this` value in the parent may again resolve to a parameter or to _its_ parent's `this` value, etc. */ const { references, bindings, scopes } = model; const scopeStack = []; const currentScope = () => (0, utils_1.notUndefined)(scopeStack[scopeStack.length - 1]); const ilFunctionNames = new Set(); traverse(file.program); function traverse(node_, context) { const node = node_; (0, common_1.visitingNode)(cur, node); switch (node.type) { // Scope nodes case 'Program': return traverseModuleScope(node); case 'FunctionDeclaration': return traverseFunctionDeclarationScope(node); case 'ClassDeclaration': return traverseClassDeclaration(node); case 'ClassMethod': (0, utils_1.unexpected)(); // These are iterated inside traverseClassDeclaration case 'ArrowFunctionExpression': return traverseFunctionExpressionScope(cur, node); case 'FunctionExpression': return traverseFunctionExpressionScope(cur, node); case 'BlockStatement': return traverseBlockScope(node); case 'ForStatement': return traverseForStatement(node); case 'TryStatement': return traverseTryStatement(node); // Reference nodes case 'Identifier': return createVariableReference(node); case 'ThisExpression': return createVariableReference(node); // Mutating nodes case 'AssignmentExpression': return handleAssignmentExpression(node); case 'UpdateExpression': return handleUpdateExpression(node); case 'AwaitExpression': return handleAwaitExpression(node); default: (0, traverse_ast_1.traverseChildren)(cur, node, traverse); } function traverseModuleScope(node) { const scope = pushModuleScope(node); model.moduleScope = scope; const body = node.body; findImportsAndExports(node); // Find variables in the root scope and nested blocks findVarDeclarations(body); // Lexical variables are also found upfront because nested functions can // reference variables that are declared further down than the nested // function (TDZ). (But `findLexicalVariables` isn't recursive) findBlockScopeDeclarations(body); // Iterate through the function/program body to find variable usage (0, traverse_ast_1.traverseChildren)(cur, node, traverse); popScope(scope); } function traverseFunctionDeclarationScope(node, className) { const isAsync = node.async === true; const scope = pushFunctionScope(node, true, isAsync, className); registerWithEmbeddingLocation(cur, scope); createParameterBindings(scope, node.params); const body = node.body.body; findVarDeclarations(body); // Note: we don't do `findBlockScopeDeclarations` because the traversal // will find these declarations (let and const) in the function body which // is a a "block" // Iterate through the body to find variable usage (0, traverse_ast_1.traverseChildren)(cur, node, traverse); popScope(scope); return scope; } function traverseClassDeclaration(node) { // Note: this function runs multiple passes over the class !node.superClass || (0, common_1.featureNotSupported)(cur, 'extends', node); const className = node.id.name; const classScope = pushClassScope(node); node.body.body.forEach(n => B.isClassField(n) || (0, common_1.featureNotSupported)(cur, n.type, n)); const fields = node.body.body.filter(B.isClassField); // Pass 1: methods and computed member names. These are evaluated in the // parent scope because `this` refers to the same thing as the outer // scope, whatever that is. for (const decl of fields) { if (decl.computed) { (0, common_1.featureNotSupported)(cur, 'Computed names for class members', decl.key); // traverse(decl.key) } if (decl.type === 'ClassMethod' && !B.isConstructor(decl)) { traverseFunctionDeclarationScope(decl, className); } } // Pass 2: static property initializers. These are evaluated in a scope // where `this` refers to the class itself classScope.staticConstructorScope = createBlockScope(undefined, true); pushScope(classScope.staticConstructorScope); classScope.staticConstructorScope.thisBinding = createBinding('#this', 'this', undefined, false, classScope.staticConstructorScope); for (const decl of fields) { if (decl.static && decl.type === 'ClassProperty' && decl.value) { // For efficiency reasons, the static constructor of a class is inline // rather than in a separate function. Normally `this` refers to // arg[0], but nested inside a class static property initializer, // `this` actually refers to the class itself. But it will be a pain // to implement that and it gives almost now value, so I'm just // disallowing it for the moment. A user can always just refer to the // class name instead. checkNoThis(cur, decl.value, 'static property initializer'); traverse(decl.value); } } popScope(classScope.staticConstructorScope); // Pass 3: non-static property initializers. These are evaluated in a // scope where `this` refers to the class instance. classScope.physicalConstructorScope = createFunctionScope(undefined, true, false, className); pushScope(classScope.physicalConstructorScope); for (const decl of fields) { if (!decl.static && decl.type === 'ClassProperty' && decl.value) { traverse(decl.value); } } // Pass 4: the user-provided constructor itself is evaluated in a scope // that contains both the `this` of the instance and the parameters of the // constructor. Note: The virtual constructor is created as a child to the // physical constructor of pass 3. const userProvidedConstructor = fields.find(B.isConstructor); if (userProvidedConstructor) { classScope.virtualConstructorScope = createBlockScope(userProvidedConstructor, true); scopes.set(userProvidedConstructor, classScope.virtualConstructorScope); pushScope(classScope.virtualConstructorScope); createParameterBindings(classScope.virtualConstructorScope, userProvidedConstructor.params); const body = userProvidedConstructor.body; findVarDeclarations(body.body); (0, traverse_ast_1.traverseChildren)(cur, userProvidedConstructor, traverse); popScope(classScope.virtualConstructorScope); } popScope(classScope.physicalConstructorScope); popScope(classScope); } function traverseFunctionExpressionScope(cur, node) { const hasThisBinding = node.type === 'FunctionExpression'; const isAsync = node.async === true; const scope = pushFunctionScope(node, hasThisBinding, isAsync); registerWithEmbeddingLocation(cur, scope); createParameterBindings(scope, node.params); const body = node.body; if (node.type === 'FunctionExpression' && node.id) { // Named function expressions are not supported yet, since they would // introduce recursion possibilities that are not as simple to solve. // E.g. // // const foo = function bar() { bar() }; // const bar = 42; // A different `bar` // return (0, common_1.featureNotSupported)(cur, 'Named function expressions'); } if (body.type === 'BlockStatement') { const statements = body.body; findVarDeclarations(statements); // Note: we don't do `findBlockScopeDeclarations` because the traversal // will find these declarations (let and const) in the function body which // is a "block" } else { /* Note: Arrow functions with expression bodies do not have any hoisted variables */ } traverse(body); popScope(scope); } function traverseBlockScope(node, sameInstanceCountAsParent) { // Creates a lexical scope const scope = pushBlockScope(node, sameInstanceCountAsParent ?? true); // Here we don't need to populate the hoisted variables because they're // already populated by the containing function/program findBlockScopeDeclarations(node.body); for (const statement of node.body) { traverse(statement); } popScope(scope); return scope; } function traverseTryStatement(node) { if (node.finalizer) { (0, common_1.visitingNode)(cur, node.finalizer); return (0, common_1.compileError)(cur, 'Not supported: finally'); } if (!node.handler) { // If we supported `finally` then the catch is optional, but a try on its // own doesn't make sense. return (0, common_1.compileError)(cur, 'Missing catch clause in try..catch'); } const tryScope = traverseBlockScope(node.block); tryScope.isTryScope = true; traverseCatchBlock(node.handler); } function traverseCatchBlock(node) { const scope = pushBlockScope(node.body, true); scope.isCatchScope = true; if (node.param) { if (node.param.type !== 'Identifier') { (0, common_1.visitingNode)(cur, node.param); return (0, common_1.compileError)(cur, 'Only simple binding supported in catch statement'); } const paramName = node.param.name; const binding = createBindingAndSelfReference(paramName, 'catch-param', node.param, false); scope.catchExceptionBinding = binding; } // A catch clause seems to define its own scope for `var` declarations findVarDeclarations(node.body.body); findBlockScopeDeclarations(node.body.body); // Iterate through the body to find variable usage (0, traverse_ast_1.traverseChildren)(cur, node.body, traverse); popScope(scope); } function traverseForStatement(node) { // The outer block is for the loop variables (e.g. `i`). If these are part // of a closure scope, this scope is created during the loop // initialization and given the initial values of the loop variables, and // then cloned between each loop iteration so that each loop iteration // "sees" the value of the variables from its iteration. const sameInstanceCountAsParent = false; // Create a lexical scope for any variables introduced by the `for` const scope = pushBlockScope(node, sameInstanceCountAsParent); if (node.init && node.init.type === 'VariableDeclaration') { bindLexicalDeclaration(node.init); } // Note: this also needs to traverse the `node.init` and `node.update` (0, traverse_ast_1.traverseChildren)(cur, node, (node, context) => { if (node.type === 'BlockStatement') { // The loop body also exists once per loop iteration, so in some sense // it has the same lifetime as its parent (the loop outer block) but the // loop outer block is cloned on each iteration while the inner block is // not, which is why we mark it as different lifetimes. This means that // the variables declared in the loop body get a fresh TDZ value at the // beginning of each iteration rather than inheriting the cloned value // from the previous iteration. const bodyHasSameInstanceCountAsParent = false; traverseBlockScope(node, bodyHasSameInstanceCountAsParent); } else { traverse(node, context); } }); popScope(scope); } function handleAssignmentExpression(node) { (0, traverse_ast_1.traverseChildren)(cur, node, traverse); handleMutationToVariable(node.left); } function handleUpdateExpression(node) { (0, traverse_ast_1.traverseChildren)(cur, node, traverse); handleMutationToVariable(node.argument); } function handleAwaitExpression(node) { (0, traverse_ast_1.traverseChildren)(cur, node, traverse); const currentFunction = containingFunction(currentScope()) ?? (0, utils_1.unexpected)(); if (currentFunction.type === 'ModuleScope') { return (0, common_1.compileError)(cur, 'Await expressions are not supported at the top level'); } currentFunction.awaitExpressions.push(node); } function handleMutationToVariable(expr) { // This is basically to determine which slots need to be mutable. The main // reason for this is to decide which parameters need to be copied into // local slots. if (expr.type === 'Identifier') { const reference = references.get(expr) ?? (0, utils_1.unexpected)(); const resolvesTo = reference.resolvesTo; if (resolvesTo.type === 'Binding') { if (resolvesTo.binding.isDeclaredReadonly) { (0, common_1.compileError)(cur, `Cannot assign to variable "${reference.name}" because it is declared readonly`); } resolvesTo.binding.isWrittenTo = true; } } } function createVariableReference(node) { const name = node.type === 'Identifier' ? node.name : '#this'; const binding = node.type === 'Identifier' ? findBinding(name) : findThisBinding(); if (binding) { const currentFunction = containingFunction(currentScope()); const bindingFunction = containingFunction(binding.scope); const isInLocalFunction = bindingFunction === currentFunction; binding.isUsed = true; // Note that this includes block-scoped variables for blocks at the root level const mustBeClosureAllocated = !isInLocalFunction; if (mustBeClosureAllocated) { if (!currentFunction) (0, utils_1.unexpected)(); binding.isAccessedByNestedFunction = true; const isGlobal = binding.scope.type === 'ModuleScope'; // Note: Global variables can be accessed without a closure scope if (!isGlobal) { markClosureChain(currentScope(), binding.scope); } } const reference = { name: name, resolvesTo: { type: 'Binding', binding }, isInLocalFunction, nearestScope: currentScope(), access: undefined // Will be populated in a later phase }; references.set(node, reference); currentScope().references.push(reference); } else { // Binding not found if (node.type === 'ThisExpression') { // The `this` expression must evaluate to undefined const reference = { name: name, resolvesTo: { type: 'RootLevelThis' }, isInLocalFunction: false, nearestScope: currentScope(), access: undefined, // Populated in phase 2 }; references.set(node, reference); currentScope().references.push(reference); } else { // Free variable reference const reference = { name, isInLocalFunction: false, nearestScope: currentScope(), resolvesTo: { type: 'FreeVariable', name }, access: undefined, // Populated in phase 2 }; model.freeVariables.add(name); references.set(node, reference); currentScope().references.push(reference); } } } function findBinding(name) { // Loop through the scope stack starting from the inner-most and working // out until we find it for (let i = scopeStack.length - 1; i >= 0; i--) { const scope = scopeStack[i]; const binding = scope.bindings[name]; if (binding) { return binding; } } // If a binding is not found, it's a free variable (a reference to a global) return undefined; } function findThisBinding() { // Loop through the scope stack starting from the inner-most and working // out until we find it for (let i = scopeStack.length - 1; i >= 0; i--) { const scope = scopeStack[i]; if (scope.thisBinding) { return scope.thisBinding; } } // If a binding is not found, it's a free variable (a reference to a global) return undefined; } // Mark all the scopes from referencingScope (inclusive) to bindingScope // (exclusive) as needing to have a reference to their parent (because they // access their outer scope). Note that "undefined" here refers to the // module scope. For functions, it also marks them as closures because they // will need to capture their parent scope at runtime. function markClosureChain(referencingScope, bindingScope) { let cursor = referencingScope; // While we're not at the scope we want to be at while (cursor !== bindingScope) { if (!cursor) (0, utils_1.unexpected)(); cursor.accessesParentScope = true; if (cursor.type === 'FunctionScope') { cursor.functionIsClosure = true; } cursor = cursor.parent; } } // Returns the innermost function containing or equal to the given scope, // or undefined if the given scope is not within a function (e.g. it's at // the model level) function containingFunction(scope) { let current = scope; while (current !== undefined && current.type !== 'FunctionScope' && current.type !== 'ModuleScope') current = current.parent; return current; } } /** * This function looks for var declarations for a variable scope (program- or * function-level) and creates bindings for them in the current scope. * * Note: this function does NOT find exported var declarations (e.g. `export * var x;`). */ function findVarDeclarations(body) { for (const statement of body) { traverse(statement); } function traverse(node_) { const node = node_; switch (node.type) { case 'ExportNamedDeclaration': case 'ImportDeclaration': break; // Handled separately case 'VariableDeclaration': { // This function is only looking for hoisted variables if (node.kind === 'var') { bindVarDeclaration(node, false); } break; } case 'FunctionDeclaration': case 'FunctionExpression': case 'ClassMethod': case 'CatchClause': // `var` declarations in catch clauses seem not to be hoisted to the function level case 'ArrowFunctionExpression': case 'ClassDeclaration': case 'ClassExpression': break; default: // We don't want to recurse into nested functions accidentally if (B.isFunctionNode(node)) (0, utils_1.assertUnreachable)(node); (0, traverse_ast_1.traverseChildren)(cur, node, traverse); break; } } } // This function looks for block-scoped declarations (let, const, and function // declarations). It does not look recursively because these kinds of // declarations are not hoisted out of nested blocks. function findBlockScopeDeclarations(statements) { for (const statement of statements) { if (statement.type === 'ExportNamedDeclaration' || statement.type === 'ImportDeclaration') continue; // Handled separately (0, common_1.visitingNode)(cur, statement); if (statement.type === 'VariableDeclaration') { bindLexicalDeclaration(statement); } else if (statement.type === 'FunctionDeclaration') { // Function declarations are "hoisted" but not to the function scope but // rather to the top of the block if (statement.id) { bindFunctionDeclaration(statement, false); } } else if (statement.type === 'ClassDeclaration') { bindLexicalDeclaration(statement); } } } function bindLexicalDeclaration(statement) { if (statement.type === 'VariableDeclaration' && statement.kind !== 'var') { (0, utils_1.hardAssert)(statement.kind === 'const' || statement.kind === 'let'); for (const declaration of statement.declarations) { const id = declaration.id; if (id.type !== 'Identifier') return (0, common_1.compileError)(cur, 'Syntax not supported', id); const name = id.name; const binding = createBindingAndSelfReference(name, statement.kind, declaration, false); currentScope().lexicalDeclarations.push(binding); } } else if (statement.type === 'ClassDeclaration') { const id = statement.id; const name = id.name; const binding = createBindingAndSelfReference(name, 'class', statement, false); currentScope().lexicalDeclarations.push(binding); } } function bindFunctionDeclaration(node, isExported) { const id = node.id ?? (0, utils_1.unexpected)(); const name = id.name; const binding = createBindingAndSelfReference(name, 'function', node, isExported); currentScope().nestedFunctionDeclarations.push({ func: node, binding, }); return binding; } function bindClassDeclaration(node, isExported) { const id = node.id ?? (0, utils_1.unexpected)(); const name = id.name; const binding = createBindingAndSelfReference(name, 'class', node, isExported); return binding; } function findImportsAndExports(program) { for (const statement of program.body) { (0, common_1.visitingNode)(cur, statement); switch (statement.type) { case 'ExportNamedDeclaration': bindNamedExports(statement); break; case 'ImportDeclaration': createImportBindings(statement); break; } } } function createImportBindings(statement) { const source = statement.source.value; const isExported = false; for (const specifier of statement.specifiers) { (0, common_1.visitingNode)(cur, specifier); const localName = specifier.local.name; const binding = createBindingAndSelfReference(localName, 'import', specifier, isExported); importBindings.set(binding, { source, specifier }); } } function bindNamedExports(statement) { if (statement.source || statement.specifiers.length) { return (0, common_1.compileError)(cur, 'Only simple export syntax is supported'); } const declaration = statement.declaration; if (!declaration) { // Older versions of babel didn't seem to allow for a null declaration, // so I'm thinking maybe it's to support a new language feature. I // haven't looked into it. (Note: this might be to support `export { x // as y }` syntax) return (0, common_1.featureNotSupported)(cur, 'Expected a declaration'); } const isExported = true; if (declaration.type === 'VariableDeclaration') { bindVarDeclaration(declaration, isExported); } else if (declaration.type === 'FunctionDeclaration') { bindFunctionDeclaration(declaration, isExported); } else if (declaration.type === 'ClassDeclaration') { bindClassDeclaration(declaration, isExported); } else { return (0, common_1.compileError)(cur, `Not supported: export of ${declaration.type}`); } } function bindVarDeclaration(decl, isExported) { for (const node of decl.declarations) { if (node.id.type !== 'Identifier') { return (0, common_1.compileError)(cur, 'Only simple variable declarations are supported.'); } const name = node.id.name; if (!(0, utils_1.isNameString)(name)) { return (0, common_1.compileError)(cur, `Invalid variable identifier: "${name}"`); } const scope = currentScope(); const existingBinding = scope.varDeclarations.find(v => v.name === name); if (existingBinding) { // Duplicate var declarations of the same name are allowed but they // point to the same variable. const selfReferenceNode = getDeclarationSelfReference(node); if (selfReferenceNode) { const ref = { name: name, isInLocalFunction: true, nearestScope: currentScope(), resolvesTo: { type: 'Binding', binding: existingBinding }, access: undefined // Will be populated in a later phase }; references.set(selfReferenceNode, ref); } } else { const binding = createBindingAndSelfReference(name, 'var', node, isExported); scope.varDeclarations.push(binding); } } } function pushModuleScope(node) { // Top-level-await (not really supported yet) const isAsyncFunction = node.body.some(n => containsAwait(cur, n)); const scope = { type: 'ModuleScope', node, bindings: Object.create(null), children: [], references: [], parent: undefined, ilFunctionId: (0, utils_1.uniqueNameInSet)('moduleEntry', ilFunctionNames), prologue: [], epilogue: [], lexicalDeclarations: [], nestedFunctionDeclarations: [], varDeclarations: [], parameterBindings: [], embeddingCandidates: [], functionIsClosure: false, sameInstanceCountAsParent: false, isAsyncFunction, awaitExpressions: [], }; scopes.set(node, scope); pushScope(scope); return scope; } function pushFunctionScope(node, hasThisBinding, isAsync, className) { const scope = createFunctionScope(node, hasThisBinding, isAsync, className); model.functions.push(scope); scopes.set(node, scope); pushScope(scope); return scope; } function createFunctionScope(node, hasThisBinding, isAsyncFunction, className) { const name = node ? node.type === 'FunctionDeclaration' ? node.id?.name : node.type === 'ClassMethod' ? !node.computed && node.key.type === 'Identifier' ? `${className}_${node.key.name}` : `${className}_method` : undefined : className ? className : undefined; if (name && !(0, utils_1.isNameString)(name)) { return (0, common_1.compileError)(cur, `Invalid function identifier: "${name}`); } const ilFunctionId = (0, utils_1.uniqueNameInSet)(name ?? 'anonymous', ilFunctionNames); const scope = { type: 'FunctionScope', ...createBaseScope(node, false, isAsyncFunction), node, ilFunctionId, funcName: name, // Assume the function is not a closure until we find a free variable // that references the outer scope functionIsClosure: false }; if (hasThisBinding) { scope.thisBinding = createBinding('#this', 'this', undefined, false, scope); } return scope; } function pushClassScope(node) { const name = node.type === 'ClassDeclaration' ? node.id?.name : undefined; if (name && !(0, utils_1.isNameString)(name)) { return (0, common_1.compileError)(cur, `Invalid class identifier: "${name}`); } const scope = { type: 'ClassScope', ...createBaseScope(node, true, false), className: name, // These will be populated later physicalConstructorScope: undefined, staticConstructorScope: undefined, virtualConstructorScope: undefined, }; scopes.set(node, scope); pushScope(scope); return scope; } function pushBlockScope(node, sameInstanceCountAsParent) { const scope = createBlockScope(node, sameInstanceCountAsParent); scopes.set(node, scope); pushScope(scope); return scope; } function createBlockScope(node, sameInstanceCountAsParent) { return { type: 'BlockScope', ...createBaseScope(node, sameInstanceCountAsParent, false) }; } function createBaseScope(node, sameInstanceCountAsParent, isAsyncFunction) { return { node, bindings: Object.create(null), children: [], references: [], parent: currentScope(), prologue: [], epilogue: [], sameInstanceCountAsParent, lexicalDeclarations: [], varDeclarations: [], embeddingCandidates: [], // Note: parameter bindings at the block level are used by parameterBindings: [], nestedFunctionDeclarations: [], closureSlots: undefined, isAsyncFunction, awaitExpressions: [], }; } function pushScope(scope) { const parent = scopeStack[scopeStack.length - 1]; // Can be undefined parent && parent.children.push(scope); scopeStack.push(scope); } function popScope(scope) { (0, utils_1.hardAssert)(scopeStack[scopeStack.length - 1] === scope); scopeStack.pop(); } function createParameterBindings(scope, params) { for (const param of params) { if (param.type !== 'Identifier') { return (0, common_1.featureNotSupported)(cur, 'Only simple parameters supported'); } const binding = createBindingAndSelfReference(param.name, 'param', param, false); scope.parameterBindings.push(binding); } } function createBindingAndSelfReference(name, kind, node, isExported) { const binding = createBinding(name, kind, node, isExported, currentScope()); const selfReferenceNode = getDeclarationSelfReference(node); if (selfReferenceNode) { const ref = { name: name, isInLocalFunction: true, nearestScope: currentScope(), resolvesTo: { type: 'Binding', binding }, access: undefined // Will be populated in a later phase }; references.set(selfReferenceNode, ref); binding.selfReference = ref; } return binding; } function getDeclarationSelfReference(node) { switch (node.type) { case 'FunctionDeclaration': return node.id ?? undefined; case 'ClassDeclaration': return node.id ?? undefined; case 'Identifier': return node; case 'VariableDeclarator': return node.id.type === 'Identifier' ? node.id : undefined; case 'ImportDefaultSpecifier': return node.local; case 'ImportSpecifier': return node.local; case 'ImportNamespaceSpecifier': return node.local; default: return (0, utils_1.assertUnreachable)(node); } } function createBinding(name, kind, node, isExported, scope) { const readonly = kind === 'const'; const scopeBindings = scope.bindings; const isLexical = kind === 'let' || kind === 'const'; if (isLexical && name in scopeBindings) { return (0, common_1.compileError)(cur, `Variable "${name}" already declared in scope`); } const binding = { kind, name, // We do slot assignment in a separate pass slot: undefined, scope, node, isExported, selfReference: undefined, isDeclaredReadonly: readonly, // Assume by default that the variable is not written to, isWrittenTo: false, // Assuming not closure allocated until we detect otherwise isAccessedByNestedFunction: false, isUsed: false, }; scopeBindings[name] = binding; binding.node && bindings.set(binding.node, binding); isExported && model.exportedBindings.push(binding); return binding; } function registerWithEmbeddingLocation(cur, func) { // See [Closure Embedding](../../../doc/internals/closure-embedding.md) // Move to the declaring scope of the function, not the function scope itself let embeddingScope = func.parent; // Find the outer-most scope that is still the same "lifetime" as the // function declaration. while (embeddingScope && embeddingScope.sameInstanceCountAsParent) { embeddingScope = embeddingScope.parent; } // Note: `undefined` means we've reached the top-level module scope. if (embeddingScope) { // In this pass, we don't yet know if the func is a closure, so we don't // know if it should be embedded or not, but we add it to a list of // possible functions that could be embedded, and then later find the // first closure in this list to embed. embeddingScope.embeddingCandidates.push(func); } } } exports.pass1_findScopesAndBindings = pass1_findScopesAndBindings; function checkNoThis(cur, node, context) { inner(node); function inner(node) { if (node.type === 'ThisExpression') { (0, common_1.featureNotSupported)(cur, `Using \`this\` inside ${context}`); } (0, traverse_ast_1.traverseChildren)(cur, node, inner); } } // Checks for `await` expressions in a given statement or expression, ignoring // the body of nested functions. function containsAwait(cur, node) { let containsAwait = false; inner(node); return containsAwait; function inner(node) { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression' || node.type === 'ClassMethod') { /* Do not traverse into nested functions */ } else if (node.type === 'AwaitExpression') { containsAwait = true; } else { (0, traverse_ast_1.traverseChildren)(cur, node, inner); } } } //# sourceMappingURL=pass-1-find-scopes-and-bindings.js.map