     1→import { BindingKind, Context, type Location, Origin } from '../common';
     2→import { Errors, ParseError } from '../errors';
     3→import { type Parser } from './parser';
     4→
     5→/**
     6→ * Scope kinds
     7→ */
     8→export const enum ScopeKind {
     9→  ForStatement = 1 << 0,
    10→  Block = 1 << 1,
    11→  CatchStatement = 1 << 2,
    12→  SwitchStatement = 1 << 3,
    13→  TryStatement = 1 << 4,
    14→  CatchBlock = 1 << 5,
    15→  FunctionBody = 1 << 6,
    16→  FunctionRoot = 1 << 7,
    17→  FunctionParams = 1 << 8,
    18→  ArrowParams = 1 << 9,
    19→}
    20→
    21→/** Scope error interface */
    22→interface ScopeError {
    23→  type: Errors;
    24→  params: string[];
    25→  start: Location;
    26→  end: Location;
    27→}
    28→
    29→export class Scope {
    30→  // Some scopeError doesn't necessarily fail parsing.
    31→  // For example function a(dup, dup) {} is fine,
    32→  // But duplicated params is not allowed in strict mode,
    33→  // So function a(dup, dup) { "use strict" } would fail.
    34→  // Retain the scopeError on scope for later decision.
    35→  scopeError?: ScopeError;
    36→
    37→  variableBindings = new Map<string, BindingKind>();
    38→
    39→  constructor(
    40→    public readonly parser: Parser,
    41→    public readonly type: ScopeKind = ScopeKind.Block,
    42→    public readonly parent?: Scope,
    43→  ) {}
    44→
    45→  createChildScope(type?: ScopeKind) {
    46→    return new Scope(this.parser, type, this);
    47→  }
    48→
    49→  /**
    50→   * Adds either a var binding or a block scoped binding.
    51→   *
    52→   * @param context Context masks
    53→   * @param name Binding name
    54→   * @param type Binding kind
    55→   * @param origin Binding Origin
    56→   */
    57→  addVarOrBlock(context: Context, name: string, kind: BindingKind, origin: Origin) {
    58→    if (kind & BindingKind.Variable) {
    59→      this.addVarName(context, name, kind);
    60→    } else {
    61→      this.addBlockName(context, name, kind, origin);
    62→    }
    63→    if (origin & Origin.Export) {
    64→      this.parser.declareUnboundVariable(name);
    65→    }
    66→  }
    67→
    68→  /**
    69→   * Adds a variable binding
    70→   *
    71→   * @param context Context masks
    72→   * @param name Binding name
    73→   * @param type Binding kind
    74→   */
    75→  addVarName(context: Context, name: string, kind: BindingKind): void {
    76→    const { parser } = this;
    77→    // eslint-disable-next-line @typescript-eslint/no-this-alias
    78→    let currentScope: Scope | undefined = this;
    79→
    80→    while (currentScope && (currentScope.type & ScopeKind.FunctionRoot) === 0) {
    81→      const { variableBindings } = currentScope;
    82→      const value = variableBindings.get(name);
    83→
    84→      if (value && value & BindingKind.LexicalBinding) {
    85→        if (
    86→          parser.options.webcompat &&
    87→          (context & Context.Strict) === 0 &&
    88→          ((kind & BindingKind.FunctionStatement && value & BindingKind.LexicalOrFunction) ||
    89→            (value & BindingKind.FunctionStatement && kind & BindingKind.LexicalOrFunction))
    90→        ) {
    91→          // No op
    92→        } else {
    93→          parser.report(Errors.DuplicateBinding, name);
    94→        }
    95→      }
    96→      if (currentScope === this) {
    97→        if (value && value & BindingKind.ArgumentList && kind & BindingKind.ArgumentList) {
    98→          currentScope.recordScopeError(Errors.DuplicateBinding, name);
    99→        }
   100→      }
   101→      if (
   102→        value &&
   103→        (value & BindingKind.CatchPattern || (value & BindingKind.CatchIdentifier && !parser.options.webcompat))
   104→      ) {
   105→        parser.report(Errors.DuplicateBinding, name);
   106→      }
   107→
   108→      currentScope.variableBindings.set(name, kind);
   109→
   110→      currentScope = currentScope.parent;
   111→    }
   112→  }
   113→
   114→  hasVariable(name: string) {
   115→    return this.variableBindings.has(name);
   116→  }
   117→
   118→  /**
   119→   * Adds block scoped binding
   120→   *
   121→   * @param context Context masks
   122→   * @param name Binding name
   123→   * @param type Binding kind
   124→   * @param origin Binding Origin
   125→   */
   126→  addBlockName(context: Context, name: string, kind: BindingKind, origin: Origin) {
   127→    const { parser } = this;
   128→    const value = this.variableBindings.get(name);
   129→
   130→    if (value && (value & BindingKind.Empty) === 0) {
   131→      if (kind & BindingKind.ArgumentList) {
   132→        this.recordScopeError(Errors.DuplicateBinding, name);
   133→      } else if (
   134→        parser.options.webcompat &&
   135→        (context & Context.Strict) === 0 &&
   136→        origin & Origin.BlockStatement &&
   137→        value === BindingKind.FunctionLexical &&
   138→        kind === BindingKind.FunctionLexical
   139→      ) {
   140→        // No op
   141→      } else {
   142→        parser.report(Errors.DuplicateBinding, name);
   143→      }
   144→    }
   145→
   146→    if (
   147→      this.type & ScopeKind.FunctionBody &&
   148→      this.parent?.hasVariable(name) &&
   149→      (this.parent.variableBindings.get(name)! & BindingKind.Empty) === 0
   150→    ) {
   151→      parser.report(Errors.DuplicateBinding, name);
   152→    }
   153→
   154→    if (this.type & ScopeKind.ArrowParams && value && (value & BindingKind.Empty) === 0) {
   155→      if (kind & BindingKind.ArgumentList) {
   156→        this.recordScopeError(Errors.DuplicateBinding, name);
   157→      }
   158→    }
   159→
   160→    if (this.type & ScopeKind.CatchBlock) {
   161→      if (this.parent!.variableBindings.get(name)! & BindingKind.CatchIdentifierOrPattern)
   162→        parser.report(Errors.ShadowedCatchClause, name);
   163→    }
   164→
   165→    this.variableBindings.set(name, kind);
   166→  }
   167→
   168→  /**
   169→   * Record duplicate binding errors that may occur in a arrow head or function parameters
   170→   *
   171→   * @param parser Parser state
   172→   * @param type Errors type
   173→   */
   174→  recordScopeError(type: Errors, ...params: string[]) {
   175→    this.scopeError = {
   176→      type,
   177→      params,
   178→      start: this.parser.tokenStart,
   179→      end: this.parser.currentLocation,
   180→    };
   181→  }
   182→
   183→  reportScopeError() {
   184→    const { scopeError } = this;
   185→    if (!scopeError) {
   186→      return;
   187→    }
   188→
   189→    throw new ParseError(scopeError.start, scopeError.end, scopeError.type, ...scopeError.params);
   190→  }
   191→}
   192→
   193→/**
   194→ * Create a parsing scope for arrow head, and add lexical binding
   195→ *
   196→ * @param parser Parser state
   197→ * @param context Context masks
   198→ * @param value Binding name to be declared
   199→ */
   200→export function createArrowHeadParsingScope(parser: Parser, context: Context, value: string): Scope {
   201→  const scope = parser.createScope().createChildScope(ScopeKind.ArrowParams);
   202→  scope.addBlockName(context, value, BindingKind.ArgumentList, Origin.None);
   203→  return scope;
   204→}
