Skip to content

Typedoc seems not to read through keyof another type #1894

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
steveluscher opened this issue Mar 24, 2022 · 8 comments
Closed

Typedoc seems not to read through keyof another type #1894

steveluscher opened this issue Mar 24, 2022 · 8 comments
Labels
no bug This is expected behavior

Comments

@steveluscher
Copy link

Search terms

keyof, references

Expected Behavior

Typedoc compiles this code just fine:

/**
 * The complete list of possible things.
 */
export type Things = 'this' | 'that';

I would expect it to be able to also, at least, compile this – reading through the reference to ThingConfig as it does.

/**
 * The complete list of possible things.
 */
export type Things = keyof ThingConfig;

/**
 * Configuration relating to all possible things.
 */
type ThingConfig = {
  this: {location: 'here'};
  that: {location: 'there'};
};

Actual Behavior

Instead, I get this warning.

Warning: ThingConfig, defined at src/example.ts:13, is referenced by Things but not included in the documentation.

We have --treatWarningsAsErrors turned on, so this has become a problem :)

Steps to reproduce the bug

mkdir src/
echo '
    /**
     * The complete list of possible things.
     */
    export type Things = keyof ThingConfig;

    /**
     * Configuration relating to all possible things.
     */
    type ThingConfig = {
      this: {location: 'here'};
      that: {location: 'there'};
    };
' > src/example.ts
typedoc src/example.ts

Environment

  • Typedoc version: 0.22.13
  • TypeScript version: 4.4.3
  • Node.js version: 16.14.2
  • OS: MacOS 12.2.1
@steveluscher
Copy link
Author

It's also quite possible that I just have no idea how this thing is supposed to work, rather that that this is a bug.

@steveluscher
Copy link
Author

Possibly related to #1519, in that I'd love for Typedoc to actually resolve keyof ThingConfig to the literal union 'this' | 'that' here.

@Gerrit0 Gerrit0 added the no bug This is expected behavior label Mar 26, 2022
@Gerrit0
Copy link
Collaborator

Gerrit0 commented Mar 26, 2022

This is working as intended - it's a different feature request than 1519, closer to #1823.

I don't think this is something that it makes sense for TypeDoc to do by default. Expanding keyof Foo could mean expanding a 20 character piece of documentation into something that has 100x strings, particularly since people's use of massive string unions have increased a ton since template literal types were introduced. This specific case is pretty easy to do with a plugin:

// typedoc --plugin path/to/this/file.js
const td = require("typedoc");

/** @param {td.Application} app */
exports.load = function (app) {
    app.converter.on(td.Converter.EVENT_CREATE_DECLARATION, resolveKeyof);
};

/**
 * @param {td.Context} context
 * @param {td.DeclarationReflection} reflection
 * @param {td.TypeScript.Node | undefined} node
 */
function resolveKeyof(context, reflection, node) {
    if (!node) return;

    if (
        reflection.kindOf(td.ReflectionKind.TypeAlias) &&
        reflection.type?.type === "typeOperator" &&
        reflection.type.operator === "keyof"
    ) {
        const type = context.checker.getTypeAtLocation(node);
        reflection.type = new td.UnionType(type.types.map((type) => new td.LiteralType(type.value)));
    }
}

@steveluscher
Copy link
Author

Love it!

@tmadhavan
Copy link

Could this be made to work for a generic parameter also? I've attempted adding the plugin, but I have a method like

type EventName = keyof Events
myMethod <T extends EventName> () { //blah }

And my documentation is

Type parameters
T: keyof Events

Thanks!

@Gerrit0
Copy link
Collaborator

Gerrit0 commented Apr 13, 2022

Unfortunately, doing this everywhere is much more difficult. Types can be present in quite a few places, and a plugin would have to manually check each location to replace the type. For a type parameter's constraint, I believe this would do the trick (untested)

// typedoc --plugin path/to/this/file.js
const td = require("typedoc");

/** @param {td.Application} app */
exports.load = function (app) {
    app.converter.on(td.Converter.CREATE_TYPE_PARAMETER, resolveKeyof);
};

/**
 * @param {td.Context} context
 * @param {td.TypeParameterReflection} typeParam
 */
function resolveKeyof(context, typeParam) {
    // Note: getSymbolFromReflection is internal, and will likely break in a future update
    const symbol = context.project.getSymbolFromReflection(typeParam);
    const declaration = symbol?.declarations?.[0]
    if (!declaration) return;

    if (typeParam.type.type === "typeOperator" && typeParam.type.operator === "keyof") {
        const type = context.checker.getTypeAtLocation(declaration);
        reflection.type = new td.UnionType(type.types.map((type) => new td.LiteralType(type.value)));
    }
}

@tmadhavan
Copy link

Thanks for the response, really appreciate it 👍 I suspected it might be a bit tricky!

@Gerrit0 Gerrit0 removed the bug label Feb 2, 2025
@develohpanda
Copy link

Follow-up, I was able to achieve this without a plugin, and using the @useDeclaredType tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no bug This is expected behavior
Projects
None yet
Development

No branches or pull requests

4 participants