/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/babel-parser/src/util/scope.ts
235 строк
7 KB
Huáng Jùnliàng
refactor(parser): use AST definitions from babel/types (#17853)
12 мар 2026, 12:43
Не верифицирован
12 мар 2026, 12:43
46204ac
Код
Авторство
О чём код?
import { ScopeFlag, BindingFlag } from "./scopeflags.ts"; import type * as N from "../types.ts"; import { Errors } from "../parse-error.ts"; import type Tokenizer from "../tokenizer/index.ts"; export const enum NameType { // var-declared names in the current lexical scope Var = 1 << 0, // lexically-declared names in the current lexical scope Lexical = 1 << 1, // lexically-declared FunctionDeclaration names in the current lexical scope Function = 1 << 2, } // Start an AST node, attaching a start offset. export class Scope { flags: ScopeFlag = 0; names = new Map<string, NameType>(); firstLexicalName = ""; constructor(flags: ScopeFlag) { this.flags = flags; } } // The functions in this module keep track of declared variables in the // current scope in order to detect duplicate variable names. export default class ScopeHandler<IScope extends Scope = Scope> { parser: Tokenizer; scopeStack: IScope[] = []; inModule: boolean; undefinedExports = new Map<string, number>(); constructor(parser: Tokenizer, inModule: boolean) { this.parser = parser; this.inModule = inModule; } get inTopLevel() { return (this.currentScope().flags & ScopeFlag.PROGRAM) > 0; } get inFunction() { return (this.currentVarScopeFlags() & ScopeFlag.FUNCTION_BASE) > 0; } get allowSuper() { return (this.currentThisScopeFlags() & ScopeFlag.SUPER) > 0; } get allowDirectSuper() { return (this.currentThisScopeFlags() & ScopeFlag.DIRECT_SUPER) > 0; } get allowNewTarget() { return (this.currentThisScopeFlags() & ScopeFlag.NEW_TARGET) > 0; } get inClass() { return (this.currentThisScopeFlags() & ScopeFlag.CLASS_BASE) > 0; } get inClassAndNotInNonArrowFunction() { const flags = this.currentThisScopeFlags(); return ( (flags & ScopeFlag.CLASS_BASE) > 0 && (flags & ScopeFlag.FUNCTION_BASE) === 0 ); } get inStaticBlock() { for (let i = this.scopeStack.length - 1; ; i--) { const { flags } = this.scopeStack[i]; if (flags & ScopeFlag.STATIC_BLOCK) { return true; } if (flags & (ScopeFlag.VAR | ScopeFlag.CLASS_BASE)) { // function body, module body, class property initializers return false; } } } get inNonArrowFunction() { return (this.currentThisScopeFlags() & ScopeFlag.FUNCTION_BASE) > 0; } get inBareCaseStatement() { return (this.currentScope().flags & ScopeFlag.SWITCH) > 0; } get treatFunctionsAsVar() { return this.treatFunctionsAsVarInScope(this.currentScope()); } createScope(flags: ScopeFlag): Scope { return new Scope(flags); } enter(flags: ScopeFlag) { /*:: +createScope: (flags:ScopeFlag) => IScope; */ // @ts-expect-error This method will be overwritten by subclasses this.scopeStack.push(this.createScope(flags)); } exit(): ScopeFlag { const scope = this.scopeStack.pop()!; return scope.flags; } // The spec says: // > At the top level of a function, or script, function declarations are // > treated like var declarations rather than like lexical declarations. treatFunctionsAsVarInScope(scope: IScope): boolean { return !!( scope.flags & (ScopeFlag.FUNCTION_BASE | ScopeFlag.STATIC_BLOCK) || (!this.parser.inModule && scope.flags & ScopeFlag.PROGRAM) ); } declareName(name: string, bindingType: BindingFlag, loc: number) { let scope = this.currentScope(); if ( bindingType & BindingFlag.SCOPE_LEXICAL || bindingType & BindingFlag.SCOPE_FUNCTION ) { this.checkRedeclarationInScope(scope, name, bindingType, loc); let type = scope.names.get(name) || 0; if (bindingType & BindingFlag.SCOPE_FUNCTION) { type = type | NameType.Function; } else { if (!scope.firstLexicalName) { scope.firstLexicalName = name; } type = type | NameType.Lexical; } scope.names.set(name, type); if (bindingType & BindingFlag.SCOPE_LEXICAL) { this.maybeExportDefined(scope, name); } } else if (bindingType & BindingFlag.SCOPE_VAR) { for (let i = this.scopeStack.length - 1; i >= 0; --i) { scope = this.scopeStack[i]; this.checkRedeclarationInScope(scope, name, bindingType, loc); scope.names.set(name, (scope.names.get(name) || 0) | NameType.Var); this.maybeExportDefined(scope, name); if (scope.flags & ScopeFlag.VAR) break; } } if (this.parser.inModule && scope.flags & ScopeFlag.PROGRAM) { this.undefinedExports.delete(name); } } maybeExportDefined(scope: IScope, name: string) { if (this.parser.inModule && scope.flags & ScopeFlag.PROGRAM) { this.undefinedExports.delete(name); } } checkRedeclarationInScope( scope: IScope, name: string, bindingType: BindingFlag, loc: number, ) { if (this.isRedeclaredInScope(scope, name, bindingType)) { this.parser.raise(Errors.VarRedeclaration, loc, { identifierName: name, }); } } isRedeclaredInScope( scope: IScope, name: string, bindingType: BindingFlag, ): boolean { if (!(bindingType & BindingFlag.KIND_VALUE)) return false; if (bindingType & BindingFlag.SCOPE_LEXICAL) { return scope.names.has(name); } const type = scope.names.get(name) || 0; if (bindingType & BindingFlag.SCOPE_FUNCTION) { return ( (type & NameType.Lexical) > 0 || (!this.treatFunctionsAsVarInScope(scope) && (type & NameType.Var) > 0) ); } return ( ((type & NameType.Lexical) > 0 && // Annex B.3.4 // https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks !( scope.flags & ScopeFlag.SIMPLE_CATCH && scope.firstLexicalName === name )) || (!this.treatFunctionsAsVarInScope(scope) && (type & NameType.Function) > 0) ); } checkLocalExport(id: N.Identifier) { const { name } = id; const topLevelScope = this.scopeStack[0]; if (!topLevelScope.names.has(name)) { this.undefinedExports.set(name, id.start!); } } currentScope(): IScope { return this.scopeStack[this.scopeStack.length - 1]; } currentVarScopeFlags(): ScopeFlag { for (let i = this.scopeStack.length - 1; ; i--) { const { flags } = this.scopeStack[i]; if (flags & ScopeFlag.VAR) { return flags; } } } // Could be useful for `arguments`, `this`, `new.target`, `super()`, `super.property`, and `super[property]`. currentThisScopeFlags(): ScopeFlag { for (let i = this.scopeStack.length - 1; ; i--) { const { flags } = this.scopeStack[i]; if ( flags & (ScopeFlag.VAR | ScopeFlag.CLASS_BASE) && !(flags & ScopeFlag.ARROW) ) { return flags; } } } }