sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
77.64k stars 4.04k forks source link

JSDoc style prop comments lost in build output when using $$Props and ComponentProps for inheritance #9331

Closed kitschpatrol closed 9 months ago

kitschpatrol commented 9 months ago

Describe the bug

Maybe this is more of language tools question, or possibly just an immutable TypeScript behavior... but I'm working on a component library where a number of components inherit props from other components to avoid redeclaration.

The library project was created with the library option in npm create svelte@latest.

I'm defining $$Props explicitly in many of the components, and using ComponentProps to bring forward the types I want the component to inherit. The components are written with TS, but I'm also using JSDoc style comments to give nice documentation on hover in your IDE.

This all works great in development where the uncompiled TS .svelte files are used, but when I build the library out to .js and .d.ts files, type utilities like ComponentProps are expanded and the JSDoc comments are lost.

See the reproduction section, but basically if you do something like this in your component:

interface $$Props extends ComponentProps<SomeComponent> {
    /** Prop description */
    additionalProp: T;
}

All JSDoc prop annotations from SomeComponent are lost in the built type definition output, even if they're declared correctly in SomeComponent. The comment on additionalProp makes it through.

I've tried several permutations of tsconfig settings, including removeComments etc. with no joy.

My current work-around plan is to distribute the library exporting the src files instead of the built dist files. I suppose the other alternative would be to duplicate and redeclare all props and their JSDoc annotations manually instead of using ComponentProps and $$Props, or some post-build processing to try to stitch everything back together... but I really don't want to go there.

This might be tangentially related to documentation loss reported in language-tools issues #1377 and #1698.

Any help or ideas are appreciated.

Reproduction

I'll publish the full lib to GitHub in a few days even if I can't figure this out, but minimal snippets to understand the problem are below:

Excerpt of a prop declaration chain, note the JSDoc style comments.

Source files:

Binding.svelte (excerpt, this is effectively a "base" component class):

    /** Object with values to manipulate. */
    export let params: T;

    /** The key for the value in the params object the control should manipulate. */
    export let key: string;

    /** Prevent interactivity. Defaults to `false`. */
    export let disabled: boolean = false;

    /** Text displayed next to control. */
    export let label: string | undefined = undefined;

    /** Control configuration exposing TweakPane's internal [BindingParams](https://tweakpane.github.io/docs/api/types/BindingParams.html), contingent on type of bound param. TODO: Templatized types. */
    export let bindingParams: object | undefined = undefined;

    /** Custom color scheme. Only applies if the control component is created outside a `<Pane>` component. */
    export let theme: Theme | undefined = undefined;

    /** Bindable reference to internal TweakPane [BindingApi](https://tweakpane.github.io/docs/api/classes/_internal_.BindingApi.html) for this control, not generally intended for direct use. Treat as read only. */
    export let bindingRef: BindingApi | undefined = undefined;

    /** Imported Tweakpane `TpPluginBundle` module to register before creating the binding. Primarily for internal use. */
    export let plugin: TpPluginBundle | undefined = undefined;

ValueBinding.svelte (excerpt, "inherits" props from Binding.svelte):

    import Binding from '../core/Binding.svelte';
    import type { ComponentProps } from 'svelte';

    interface BindableValue extends Bindable {
        [x: string]: T;
    }

    interface $$Props extends Omit<ComponentProps<Binding<BindableValue>>, 'key' | 'params'> {
        /** Value to manipulate. */
        value: T;
    }

Build Output:

Binding.svelte.d.ts (excerpt, comments survive, great!):

declare class __sveltets_Render<T extends Bindable> {
    props(): {
        /** Object with values to manipulate. */ params: T;
        /** The key for the value in the params object the control should manipulate. */ key: string;
        /** Prevent interactivity. Defaults to `false`. */ disabled?: boolean | undefined;
        /** Text displayed next to control. */ label?: string | undefined;
        /** Control configuration exposing TweakPane's internal [BindingParams](https://tweakpane.github.io/docs/api/types/BindingParams.html), contingent on type of bound param. TODO: Templatized types. */ bindingParams?: object | undefined;
        /** Custom color scheme. Only applies if the control component is created outside a `<Pane>` component. */ theme?: Theme | undefined;
        /** Bindable reference to internal TweakPane [BindingApi](https://tweakpane.github.io/docs/api/classes/_internal_.BindingApi.html) for this control, not generally intended for direct use. Treat as read only. */ bindingRef?: BindingApi<unknown, unknown, import("@tweakpane/core/dist/blade/binding/controller/binding.js").BindingController<unknown, import("@tweakpane/core").ValueController<unknown, import("@tweakpane/core").View, import("@tweakpane/core").Value<unknown>>, import("@tweakpane/core").BindingValue<unknown>>> | undefined;
        /** Imported Tweakpane `TpPluginBundle` module to register before creating the binding. Primarily for internal use. */ plugin?: TpPluginBundle | undefined;
    };
    events(): {} & {
        [evt: string]: CustomEvent<any>;
    };
    slots(): {};
}

ValueBinding.svelte.d.ts (note expansion of prop definitions, and loss of JSDoc comments on inherited props):

declare class __sveltets_Render<T extends any> {
    props(): Omit<{
        params: Bindable & {
            [x: string]: T;
        };
        key: string;
        disabled?: boolean | undefined;
        label?: string | undefined;
        bindingParams?: object | undefined;
        theme?: import("..").Theme | undefined;
        bindingRef?: import("@tweakpane/core").BindingApi<unknown, unknown, import("@tweakpane/core/dist/blade/binding/controller/binding").BindingController<unknown, import("@tweakpane/core").ValueController<unknown, import("@tweakpane/core").View, import("@tweakpane/core").Value<unknown>>, import("@tweakpane/core").BindingValue<unknown>>> | undefined;
        plugin?: import("@tweakpane/core").TpPluginBundle | undefined;
    }, "key" | "params"> & {
        /** Value to manipulate. */
        value: T;
    };
    events(): {} & {
        [evt: string]: CustomEvent<any>;
    };
    slots(): {};
}

System Info

System:
    OS: macOS 14.0
    CPU: (8) arm64 Apple M1
    Memory: 1.85 GB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.17.1 - ~/.nvm/versions/node/v18.17.1/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v18.17.1/bin/yarn
    npm: 10.1.0 - ~/.nvm/versions/node/v18.17.1/bin/npm
    pnpm: 8.6.1 - ~/.nvm/versions/node/v18.17.1/bin/pnpm
  Browsers:
    Chrome: 118.0.5993.70
    Edge: 118.0.2088.46
    Safari: 17.0
  npmPackages:
    svelte: ^4.2.1 => 4.2.1

Severity

annoyance

kitschpatrol commented 9 months ago

Closing in favor of 2186 on the language-tools repo.