Repository: puppeteer/puppeteer
File: packages/puppeteer-core/src/util/disposable.ts (TypeScript)

Code before:
  #disposed = false;
  #stack: AsyncDisposable[] = [];

  /**
   * Returns a value indicating whether the stack has been disposed.
   */
  get disposed(): boolean {
    return this.#disposed;
  }

  /**
   * Alias for `[Symbol.asyncDispose]()`.
   */
  async dispose(): Promise<void> {
    await this[asyncDisposeSymbol]();
  }

  /**
   * Adds a AsyncDisposable resource to the top of stack, returning the resource.
   * Has no effect if provided `null` or `undefined`.
   *
   * @param value - A `AsyncDisposable` object, `null`, or `undefined`.
   * `null` and `undefined` will not be added, but will be returned.
   * @returns The provided `value`.
   */
  use<T extends AsyncDisposable | null | undefined>(value: T): T {
    if (value && typeof value[asyncDisposeSymbol] === 'function') {
      this.#stack.push(value);
    }
    return value;
  }

  /**
   * Adds a non-disposable resource and a disposal callback to the top of the stack.
   *
   * @param value - A resource to be disposed.
   * @param onDispose - A callback invoked to dispose the provided value.
   * Will be invoked with `value` as the first parameter.
   * @returns The provided `value`.
   */
  adopt<T>(value: T, onDispose: (value: T) => Promise<void>): T {
    this.#stack.push({
      [asyncDisposeSymbol]() {
        return onDispose(value);
      },
    });
    return value;
  }

  /**
   * Add a disposal callback to the top of the stack to be invoked when stack is disposed.

Review comment (bug):
I think the type here is wrong, T can extend `Disposable` as well as `AsyncDisposable`. 
And we need to check that case also. It's on the GetDisposeMethod in the spec update https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-getdisposemethod

Code after:
  #disposed = false;
  #stack: AsyncDisposable[] = [];

  /**
   * Returns a value indicating whether the stack has been disposed.
   */
  get disposed(): boolean {
    return this.#disposed;
  }

  /**
   * Alias for `[Symbol.asyncDispose]()`.
   */
  async dispose(): Promise<void> {
    await this[asyncDisposeSymbol]();
  }

  /**
   * Adds a AsyncDisposable resource to the top of stack, returning the resource.
   * Has no effect if provided `null` or `undefined`.
   *
   * @param value - A `AsyncDisposable` object, `null`, or `undefined`.
   * `null` and `undefined` will not be added, but will be returned.
   * @returns The provided `value`.
   */
  use<T extends AsyncDisposable | Disposable | null | undefined>(value: T): T {
    if (value) {
      const asyncDispose = (value as AsyncDisposable)[asyncDisposeSymbol];
      const dispose = (value as Disposable)[disposeSymbol];

      if (typeof asyncDispose === 'function') {
        this.#stack.push(value as AsyncDisposable);
      } else if (typeof dispose === 'function') {
        this.#stack.push({
          [asyncDisposeSymbol]: async () => {
            (value as Disposable)[disposeSymbol]();
          },
        });
      }
    }

    return value;
  }

  /**
   * Adds a non-disposable resource and a disposal callback to the top of the stack.
   *
   * @param value - A resource to be disposed.
   * @param onDispose - A callback invoked to dispose the provided value.
   * Will be invoked with `value` as the first parameter.
   * @returns The provided `value`.