Skip to content

Use overloads to preserve legacy parameter names when combined with modern Options objects #2470

Open

Description

Context

I'm a member of the TypeScript/JavaScript team at Google. We are currently working on a lint rule that enforces that call-site parameter comments /* likeThis= */ match their declarations.

The problem we are seeing is that several DOM methods have transitioned to using an options bag for certain arguments. In the generated dom.generated.d.ts, these options are collapsed into a single signature using a union type. When this happens, the specific semantic name of the boolean (e.g., useCapture or selfOnly) is replaced by a generic name.

This breaks developer intent for:

IDE Hover Tips: Users see options: boolean | ImportNodeOptions instead of the descriptive boolean name.

Call-site Documentation: Lint rules and internal style guides that enforce parameter comments (e.g., /* selfOnly= */ true) fail because the AST reflects the name options, not selfOnly.


Examples

document.importNode

The spec moved from deep: boolean to an options object where the boolean equivalent (inverted) is now selfOnly.

Current generated type: importNode<T extends Node>(importedNode: T, options?: boolean | ImportNodeOptions): T;

Proposed Overloads:
importNode<T extends Node>(importedNode: T, deep?: boolean): T;
importNode<T extends Node>(importedNode: T, options?: ImportNodeOptions): T;


element.addEventListener

The spec moved from useCapture: boolean to an options object where the boolean equivalent is now capture.

Current generated type: addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;

Proposed Overloads:
addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: AddEventListenerOptions): void;


Some other effected methods include removeEventListener and scrollIntoView


Proposed Solution

I propose that the generator (or manual overrides) split these into explicit overloads to preserve the legacy parameter names alongside the modern object-based signature. I understand these definitions are derived from Web IDL. However, since the TypeScript lib aims to provide the best possible developer experience, adding these overloads to overridingTypes.json (or the equivalent generation logic) would significantly improve the clarity of codebases that still rely on the boolean flags for performance or brevity.

Activity

  1. jakebailey commented on Apr 20, 2026

    @jakebailey
    Member

    I propose that the generator (or manual overrides) split these into explicit overloads to preserve the legacy parameter names alongside the modern object-based signature.

    Note that this can end up being a breaking change. For example, in both cases you've given, it's impossible to call the overloaded version with a union of bool | Options as neither accept both.

  2. spkapust commented on Apr 20, 2026

    @spkapust
    Author

    Hi Jake! What I failed to mention was that we would still keep the union-based signature. Could we avoid breakage by keeping the union-based signature as the final declaration while providing a specific overload for the boolean case above it? We really only need the overload with the old api declaration anyway.

  3. jakebailey commented on Apr 20, 2026

    @jakebailey
    Member

    How many of these cases are there?

  4. spkapust commented on Apr 20, 2026

    @spkapust
    Author

    By cases do you mean how many methods? Or how many times are we seeing this issue come up when we try to apply our lint?

    For the former I mainly see two, importNode and addEventListener. I can give an exact number if you need it.

    For the latter it's on the order of hundreds.

  5. jakebailey commented on Apr 20, 2026

    @jakebailey
    Member

    Mainly, I'm trying to understand how big of a change this would be, since the fewer special cases we have the better, or if there is a clear pattern in the upstream spec for this. (Maybe these are already handwritten only.)

  6. spkapust commented on Apr 20, 2026

    @spkapust
    Author

    As far as I can tell, there are not many cases. I've seen it 4 times. Though I imagine that over time there will be other apis that follow this pattern.

    addEventListenerspec

    removeEventListenerspec

    importNode spec

    scrollIntoViewspec

  7. spkapust commented on May 4, 2026

    @spkapust
    Author

    After the May 4th 2026 TypeScript Friends meeting, the consensus was that instead of doing overloads, we would change the parameter names to aOrB for cases where this happens.

    e.g. importNode<T extends Node>(importedNode: T, options?: ImportNodeOptions | boolean): T;
    becomes
    importNode<T extends Node>(importedNode: T, optionsOrDeep?: ImportNodeOptions | boolean): T;

  8. spkapust commented on May 4, 2026

    @spkapust
    Author

    Let me know if you want me to put in a PR or if this something easier done on your end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions