Repository: vuejs/vue-next
File: packages/compiler-vapor/src/generate.ts (TypeScript)

Code before:
      identifiers[id].unshift(map[id] || id)
    }

    const ret = fn()
    ids.forEach(id => remove(identifiers[id], map[id] || id))

    return ret
  }

  enterBlock(block: BlockIRNode) {
    const parent = this.block
    this.block = block
    return (): BlockIRNode => (this.block = parent)
  }

  scopeLevel: number = 0
  enterScope(): [level: number, exit: () => number] {
    return [this.scopeLevel++, () => this.scopeLevel--] as const
  }

  seenVarNames: Set<string> = new Set()
  templateVars: Map<number, string> = new Map()
  genVarName(prefix: string, baseNum: number): string {
    let num = baseNum
    let name = `${prefix}${num}`
    while (this.bindingNames.has(name) || this.seenVarNames.has(name)) {
      name = `${prefix}${++num}`
    }
    this.seenVarNames.add(name)
    return name
  }

  tName(i: number): string {
    let name = this.templateVars.get(i)
    if (!name) {
      this.templateVars.set(i, (name = this.genVarName('t', i)))
    }
    return name
  }

  pName(i: number): string {
    return this.genVarName('p', i)
  }

  constructor(
    public ir: RootIRNode,
    options: CodegenOptions,
  ) {
    const defaultOptions: Required<CodegenOptions> = {
      mode: 'module',
      prefixIdentifiers: true,

Review comment (performance):
A small optimization could be to analyze the `bindingMetadata` in the constructor ahead of time and create a mapping for what the next free name/number is; i.e. if `bindingMetadata` has `t0`, `t1`, `t2`, and `t4` then this mapping could be built like `{ 0 => 3, 1 => 3, 2 => 3, 4 => 5 }` and it can be looked up for constant-time instead of potentially linear time complexity; likewise for the `increaseId` function

Code after:

  identifiers: Record<string, (string | SimpleExpressionNode)[]> =
    Object.create(null)

  seenInlineHandlerNames: Record<string, number> = Object.create(null)

  block: BlockIRNode
  withId<T>(
    fn: () => T,
    map: Record<string, string | SimpleExpressionNode | null>,
  ): T {
    const { identifiers } = this
    const ids = Object.keys(map)

    for (const id of ids) {
      identifiers[id] ||= []
      identifiers[id].unshift(map[id] || id)
    }

    const ret = fn()
    ids.forEach(id => remove(identifiers[id], map[id] || id))

    return ret
  }

  enterBlock(block: BlockIRNode) {
    const parent = this.block
    this.block = block
    return (): BlockIRNode => (this.block = parent)
  }

  scopeLevel: number = 0
  enterScope(): [level: number, exit: () => number] {
    return [this.scopeLevel++, () => this.scopeLevel--] as const
  }

  private templateVars: Map<number, string> = new Map()
  private nextIdMap: Map<string, Map<number, number>> = new Map()
  private lastIdMap: Map<string, number> = new Map()
  private lastTIndex: number = -1
  private initNextIdMap(): void {
    if (this.bindingNames.size === 0) return

    // build a map of binding names to their occupied ids
    const map = new Map<string, Set<number>>()
    for (const name of this.bindingNames) {
      const m = idWithTrailingDigitsRE.exec(name)
      if (!m) continue

      const prefix = m[1]
      const num = Number(m[2])