Skip to content
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

Can I hide a specific function's "type parameters"? #2273

Closed
JL102 opened this issue May 6, 2023 · 6 comments
Closed

Can I hide a specific function's "type parameters"? #2273

JL102 opened this issue May 6, 2023 · 6 comments
Labels
question Question about functionality
Milestone

Comments

@JL102
Copy link

JL102 commented May 6, 2023

Search terms

generic, hide, type param, generics

Question

Hi,

I did a bunch of TS black magic on one of my app's functions to provide useful IntelliSense hints, but as a result, the "type parameters" / generics look like absolute gibberish in my generated docs pages. Is there a way to manually hide the type parameters section for that function?

image

@JL102 JL102 added the question Question about functionality label May 6, 2023
@Gerrit0
Copy link
Collaborator

Gerrit0 commented May 6, 2023

There's not a builtin way to do this, however, a plugin to do so / other type rewriting isn't too difficult:

The space after @removeType is unfortunately necessary, which I consider a bug and am going to go fix now...

$ npx typedoc --plugin ./plugins/gh2273.js ./src/gh2273.ts
// CC0
// @ts-check
const td = require("typedoc");

// Plugin which, given a file like this:
//
// ```ts
// /**
//  * {@displayType ReturnType}
//  * @param x {@displayType 123}
//  * @param y {@displayType `{ copied directly }`}
//  * @typeParam T {@removeType }
//  */
// export function foo<T extends { a: 1; b: { c: 2 } }>(x: T, y: T): string { return "" }
// ```
//
// Will display documentation as if it was:
// ```ts
// export function foo<T>(x: 123, y: { copied directly }): ReturnType;
// ```

/** @param {td.Application} app */
exports.load = function (app) {
    // Automatically add `@displayType` and `@removeType` to the list of allowed inline tags
    // if not present. We use an inline tag so that it can be included multiple times in a comment
    // and included inside blocks that will be attached to different reflections.
    app.on(td.Application.EVENT_BOOTSTRAP_END, () => {
        const tags = [...app.options.getValue("inlineTags")];
        if (!tags.includes("@displayType")) {
            tags.push("@displayType");
        }
        if (!tags.includes("@removeType")) {
            tags.push("@removeType");
        }
        app.options.setValue("inlineTags", tags);
    });

    app.converter.on(
        td.Converter.EVENT_RESOLVE,
        /**
         * @param {td.Context} context
         * @param {td.DeclarationReflection | td.TypeParameterReflection | td.ParameterReflection | td.SignatureReflection} refl
         */
        (context, refl) => {
            if (!refl.comment) return;

            const index = refl.comment.summary.findIndex(
                (part) =>
                    part.kind === "inline-tag" &&
                    ["@displayType", "@removeType"].includes(part.tag)
            );

            if (index === -1) return;

            const removed = refl.comment.summary.splice(index, 1);
            const part = /** @type {td.InlineTagDisplayPart} */ (removed[0]);

            // Clean up the existing type so that the project can be serialized/deserialized without warnings
            refl.type?.visit(
                td.makeRecursiveVisitor({
                    reflection(r) {
                        context.project.removeReflection(r.declaration);
                    },
                })
            );

            if (part.tag === "@removeType") {
                delete refl.type;
            } else {
                // @displayType
                refl.type = new td.UnknownType(
                    part.text.replace(/^`*|`*$/g, "")
                );
            }
        }
    );
};

Gerrit0 added a commit that referenced this issue May 6, 2023
@Gerrit0
Copy link
Collaborator

Gerrit0 commented May 6, 2023

I should mention that removeReflection should generally not be used after EVENT_RESOLVE_BEGIN has fired, I think it's okay here since the only reflection types that could be removed are references to types...

@JL102
Copy link
Author

JL102 commented May 9, 2023

@Gerrit0 Thanks! I had to make a small tweak because delete reflection.type does not include "default" types, e.g. if <T = SomeSpecifiedType>. for anyone interested, here's the code I modified (I translated it to typescript/ESM since my existing plugin was already using TypeScript)

    app.on(Application.EVENT_BOOTSTRAP_END, () => {
        const tags = [...app.options.getValue('inlineTags')];
        if (!tags.includes('@displayType')) {
            tags.push('@displayType');
        }
        if (!tags.includes('@removeType')) {
            tags.push('@removeType');
        }
        app.options.setValue('inlineTags', tags);
    });
    
    app.converter.on(
        Converter.EVENT_RESOLVE,
        (context: Context, reflection: DeclarationReflection|TypeParameterReflection|ParameterReflection|SignatureReflection) => {
            if (!reflection.comment) return;

            const index = reflection.comment.summary.findIndex(
                (part) =>
                    part.kind === 'inline-tag' &&
                    ['@displayType', '@removeType'].includes(part.tag)
            );

            if (index === -1) return;

            const removed = reflection.comment.summary.splice(index, 1);
            const part = (removed[0]) as InlineTagDisplayPart;

            // Clean up the existing type so that the project can be serialized/deserialized without warnings
            reflection.type?.visit(
                makeRecursiveVisitor({
                    reflection(r) {
                        context.project.removeReflection(r.declaration);
                    },
                })
            );

            if (part.tag === '@removeType') {
                // reflection.type is given by "extends", reflection.default is given by "="
                delete reflection.type;
                if ('default' in reflection)
                    delete reflection.default;
            } else {
                // console.log('unknowntype', refl.name, part.tag);
                // @displayType
                reflection.type = new UnknownType(
                    part.text.replace(/^`*|`*$/g, '')
                );
            }
        }
    );

The function I mentioned actually doesn't require the programmer to specify the type for the generic. Like, it is declared with three generic types <Obj, EventKey extends keyof ThisCBMap, ThisCBMap = EventCallbackMap<Obj>>, but the type of Obj is determined by the contents of the first parameter and EventKey is determined by the contents of the second parameter. So when someone calls listen(), they don't need to specify the generics.

Would it be possible for teh plugin to delete/disable the TypeParameterReflection entirely, instead of just deleting reflection.type and reflection.default, so that they don't show up in the documentation pages?

@JL102
Copy link
Author

JL102 commented May 9, 2023

Nvm, got it!

app.on(Application.EVENT_BOOTSTRAP_END, () => {
    const tags = [...app.options.getValue('inlineTags')];
    if (!tags.includes('@displayType')) {
        tags.push('@displayType');
    }
    if (!tags.includes('@removeType')) {
        tags.push('@removeType');
    }
    if (!tags.includes('@removeTypeParameterCompletely')) {
        tags.push('@removeTypeParameterCompletely');
    }
    app.options.setValue('inlineTags', tags);
});

app.converter.on(
    Converter.EVENT_RESOLVE,
    (context: Context, reflection: DeclarationReflection|TypeParameterReflection|ParameterReflection|SignatureReflection) => {
        if (!reflection.comment) return;

        const index = reflection.comment.summary.findIndex(
            (part) =>
                part.kind === 'inline-tag' &&
                ['@displayType', '@removeType', '@removeTypeParameterCompletely'].includes(part.tag)
        );

        if (index === -1) return;

        const removed = reflection.comment.summary.splice(index, 1);
        const part = (removed[0]) as InlineTagDisplayPart;

        // Clean up the existing type so that the project can be serialized/deserialized without warnings
        reflection.type?.visit(
            makeRecursiveVisitor({
                reflection(r) {
                    context.project.removeReflection(r.declaration);
                },
            })
        );

        // @removeType removes the type/default of the type parameter/generic 
        if (part.tag === '@removeType') {
            // reflection.type is given by "extends", reflection.default is given by "="
            delete reflection.type;
            if ('default' in reflection)
                delete reflection.default;
        }
        // @removeTypeParameterCompletely removes the type parameter completely
        else if (part.tag === '@removeTypeParameterCompletely') {
            console.log('HELLO WORLD', reflection.name);
            context.project.removeReflection(reflection);
        }
        else {
            // console.log('unknowntype', refl.name, part.tag);
            // @displayType
            reflection.type = new UnknownType(
                part.text.replace(/^`*|`*$/g, '')
            );
        }
    }
);

In case I wanna keep the previous @removeType behavior later, I added a new one, @removeTypeParameterCompletely, which does what I want. Bit of an obtuse name, but hey, it does what I'm looking for.

Thanks!

@JL102 JL102 closed this as completed May 9, 2023
@Gerrit0
Copy link
Collaborator

Gerrit0 commented May 9, 2023

Since you're now removing reflections which are more real, the plugin should probably be changed to do the removal at RESOLVE_BEGIN, since there are pieces of typedoc that assume nothing is removed after that...

@JL102
Copy link
Author

JL102 commented May 11, 2023

Since you're now removing reflections which are more real, the plugin should probably be changed to do the removal at RESOLVE_BEGIN, since there are pieces of typedoc that assume nothing is removed after that...

Ok, will do that, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Question about functionality
Projects
None yet
Development

No branches or pull requests

2 participants