Skip to content

Parameter types for const generic constructor-as-class get categorized constructor w/wrong return type (and maybe a bogus call signature) #2914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pjeby opened this issue Mar 21, 2025 · 1 comment
Milestone

Comments

@pjeby
Copy link
Contributor

pjeby commented Mar 21, 2025

Search terms

@class tag, const generic constructor declared as class

Expected Behavior

Argument types of a const of type new(...) =>... declared as @class should be rendered in the docs with the declared constructor type, including any declared generic argument or result types. The constructor, if listed as a member, should not be categorized, since any category applied to the const should be at the top level.

Actual Behavior

Documentation is rendered with a categorized constructor returning the declared class, ignoring any generic result type, and omitting its arguments. In addition, if the constructor result type is callable, a bogus call signature based on that type is added to the documentation page as if the class were callable, rather than instances of it being so.

Steps to reproduce the bug

This will omit the arguments and show a constructor return type of ArgsWillBeOmitted instead of {x: string}:

/**
 * This will render as `new ArgsWillBeOmitted(): ArgsWillBeOmitted`, improperly
 * categorize the constructor, and add an `x` property to `ArgsWillBeOmitted`.
 *
 * @class
 * @category Constructor should not be categorized
 */
export declare const ArgsWillBeOmitted: new(x: string) => {x: string}

And this shows the addition of the signature to the type:

/**
 * This will also get a bogus call signature of (x: T): Object
 * @class
 * @category Constructor should not be categorized
 */
export declare const BogusSignature: new <T extends (x: T) => Object>(x: T) => T

The above is a simplified form of where I'm actually getting the issue, which is CallableObject (source).

In short, it appears as though constructors-as-classes get an improperly categorized constructor without arguments that returns the "class" instead of the declared return type of the constructor. The return type is instead "adopted" into the type for the class (instead of simply documenting the constructor as-is), and if the return type is callable, its most generic form is added as a call signature placed at the top of the page.

Environment

  • Typedoc version: 0.28.1
  • TypeScript version: 5.4.5
  • Node.js version: 18.20.2
  • OS: Windows
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Apr 2, 2025

There's a few things here:

  1. The comment is included on both the class and constructor.

    This is a bug, the comment should only be added to the class.

  2. Parameters on the constructor function are dropped.

    This is a bug, they should be converted and included.

  3. Type parameters on the constructor function are not converted as type parameters on the class.

    This is a bug, kind of... @class makes assumptions about the shape of the variable it is documenting.
    Treating the first constructor's type parameters is consistent with how @class uses the return type
    of that constructor to define the class shape, so it makes sense to me to make it behave like that rather
    than including the type parameters on each constructor signature.

  4. Call signatures on the return type for constructors are included on the converted class as call signatures.

    This is working as intended. TypeDoc doesn't have a space in the model to separate out static/instance
    call signatures, so they are all placed on the class. This is the same behavior you'll get if documenting a
    declaration-merged class like:

    class Foo {}
    interface Foo { (): string }

    Could this be better? Probably... But honestly, such a type really isn't a "normal" class which @class is
    meant to be used to document. I'd personally prefer seeing your CallableObject documented as a variable
    so that it's immediately obvious to me when browsing the docs that something unusual is going on when
    a class inherits from it. I'm not sure it's worth the time...

  5. Generic return types of constructor signatures documented with @class are described using their constraint.

    This is a design limitation. @class works by asking TypeScript what properties/methods/signatures are available
    on the return type of the constructor function, and documenting them as members of the type. This mostly handles
    generic typed properties/methods but for signatures the signature that TypeScript provides is the constraint.
    (This makes sense to me, TypeDoc has asked for what the signatures are, which would require either knowing
    what the type parameter is for a given instance, or using the constraint, which TS picks because we obviously
    don't have the actual instantiated type here)

    This is another reason I'd document CallableObject as a variable, it really isn't a class, but a magical
    constructor for a class.

Gerrit0 added a commit that referenced this issue Apr 2, 2025
Resolves multiple bugs reported in #2914
@Gerrit0 Gerrit0 added this to the v0.28.2 milestone Apr 7, 2025
@Gerrit0 Gerrit0 closed this as completed Apr 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants