language-tools
language-tools copied to clipboard
Component Tooltips Should Provide Prop Info
The Problem: When adding a component to a page, it's cumbersome to need to open the component to view which props exist, and which have default values. It would be more ergonomic for the component's tooltip to show all of the props available (and any JSDoc markup for each prop).
(For the purposes of this explanation, here's an example component (click to reveal) with a few example props that I will refer to below):
Currently, when you hover a component in a page's markup without any ` @component ` tag present in the markup, you get something like this. It doesn't provide any information about the component's props:

If you want that information visible outside of the component, the closest thing we get to this is the error tooltip that shows when a component is missing some props that don't have default values and thus are undefined by the component. These are required and expected to be provided when the component is used:

This isn't terribly easy to read to begin with, especially with numerous/complex props, but it does convey that the language server is already aware of the expected props/types on the component, and which ones are optional.
We actually get ~~less~~ no information about the component's props when the error highlighting disappears. Now I need to open the component to go see what other props/methods are available on it... not a great experience:

Ironically, the props you actually do provide to the component show their JSDoc comments in their own tooltips:

This is weird, because if I'm already passing the prop name in and providing the values, I'm probably past the point of needing the JSDoc comment to guide me.
A solution:
I'd like to see the component tag's tooltip able to provide all of the prop information on hover, along with any JSDoc comments that may exist for each prop. Here's a crude example (forgive my copy/paste-into-Paint skills here):

Alternatives:
The @component comment tag is capable of providing this to some degree, but is considerably more verbose. Moreover, the language server is already aware of these props and types, so we shouldn't ask the user to type out all the JSDoc syntax within a @component tag, when a comment above each prop could get you 90% of the way there.
What would happen when there's a @component comment? Should it always be put below that comment, or should it not appear in this case because the user may describe the props there on their own?
Good question! I feel like there could be a few viable ways to do it, but the first one you mentioned seems to make the most sense to me. If @component comments appear below the list of props, it respects the user's intent to have more verbose documentation.
The downside is that the tooltip could get quite large. The upside is that it immediately would educate the user that maybe documenting props in @component isn't necessary, or is at least redundant with this more terse option.
I'd love to hear some alternative opinions though. This would essentially be an enhancement of what @component is doing to a large degree. To me, its use-case is better left for documenting something that doesn't fall under "how do I use this prop?".
It should be possible to do. But I think there should a config to toggle this behaviour. It could get out of hand pretty easily. Especially when dealing with a component library. And I would prefer to just ctrl+I/ctrl+space to trigger completion and go through the props to see the individual docs.
Combine with the mentioned above, we could have a multiple-choice config:
- fallback, only show it when there's no
@componentcomment - combine, show both
- disable
Agreed. Which one makes sense as the default in the config though? I tend to like 1. As I said above, in the presence of this kind of feature, I think using @component expresses developer intent for more verbose component documentation, and may obviate the need for this low-level "help text".
For comparison with JSX: In a React project, if you hover over a component, it shows the function definition or const definition which also contains a rough description of the props or, if the user added docs, a litte more. If it's a class component however, this only shows the the class name and possibly the doc that is added to the class, but not the docs of the constructor - which would be equivalent here. I'm not saying that this means "works as designed, we won't do this".
Regarding implementation: If we make this configurable, this needs to be something that is done at runtime and not at compile time, so it needs to be something inside svelte-language-server and can't be something inside svelte2tsx - which could make things more complicated.
I think this would be a great addition. My two cents is I like putting all of the prop documentation in the @component comment block. I recently added comments above each exported prop in a project of mine and I find it makes the JavaScript code harder to read. I understand certain maintainability advantages in putting comments right above the line they reference, but the readability bonus of keeping the code clean outweighs those benefits for me.
This is tricky. What is the most expected outcome? We have @component, we will at some point have a way to describe slots and events through JSDoc - where to best keep that code? Coming from JS it's expected that the comment above a variable appears when hovering (usage of) that variable. I don't know if I would expect a comment I have put somewhere else to appear there. It certainly seems confusing if this would go both ways, and it would also accelerate different styles of writing the comments ("are team I-put-everything-in-@component or team I-put-it-above-exported-props?").
If that's the standard then for sure take my comment with a grain of salt. I just found a component that looks like this as more pleasant to read:
<!--
@component
Generates an SVG x-axis. This component is also configured to detect if your x-scale is an ordinal scale. If so, it will place the markers in the middle of the bandwidth.
@type {Boolean} [gridlines=true] – Extend lines from the ticks into the chart space
@type {Boolean} [tickMarks=false] – Show a vertical mark for each tick.
@type {Boolean} [baseline=false] – Show a solid line at the bottom.
@type {Boolean} [snapTicks=false] – Instead of centering the text on the first and the last items, align them to the edges of the chart.
@type {Function} [formatTick=d => d] – A function that passes the current tick value and expects a nicely formatted value in return.
@type {Number|Array|Function} [ticks] – If this is a number, it passes that along to the [d3Scale.ticks](https://github.com/d3/d3-scale) function. If this is an array, hardcodes the ticks to those values. If it's a function, passes along the default tick values and expects an array of tick values in return. If nothing, it uses the default ticks supplied by the D3 function.
@type {Number} [xTick=0] – TK
@type {Number} [yTick=16] – The distance from the baseline to place each tick value.
-->
<script>
import { getContext } from 'svelte';
const { width, height, xScale, yRange } = getContext('LayerCake');
export let gridlines = true;
export let tickMarks = false;
export let baseline = false;
export let snapTicks = false;
export let formatTick = d => d;
export let ticks = undefined;
export let xTick = 0;
export let yTick = 16;
</script>
Than one like this:
<!--
@component
Generates an SVG x-axis. This component is also configured to detect if your x-scale is an ordinal scale. If so, it will place the markers in the middle of the bandwidth.
-->
<script>
import { getContext } from 'svelte';
const { width, height, xScale, yRange } = getContext('LayerCake');
/** @type {Boolean} [gridlines=true] – Extend lines from the ticks into the chart space */
export let gridlines = true;
/** @type {Boolean} [tickMarks=false] – Show a vertical mark for each tick. */
export let tickMarks = false;
/** @type {Boolean} [baseline=false] – Show a solid line at the bottom. */
export let baseline = false;
/** @type {Boolean} [snapTicks=false] – Instead of centering the text on the first and the last items, align them to the edges of the chart. */
export let snapTicks = false;
/** @type {Function} [formatTick=d => d] – A function that passes the current tick value and expects a nicely formatted value in return. */
export let formatTick = d => d;
/** @type {Number|Array|Function} [ticks] – If this is a number, it passes that along to the [d3Scale.ticks](https://github.com/d3/d3-scale) function. If this is an array, hardcodes the ticks to those values. If it's a function, passes along the default tick values and expects an array of tick values in return. If nothing, it uses the default ticks supplied by the D3 function. */
export let ticks = undefined;
/** @type {Number} [xTick=0] – TK */
export let xTick = 0;
/** @type {Number} [yTick=16] – The distance from the baseline to place each tick value. */
export let yTick = 16;
</script>
Although I of course see the advantages with keeping a comment right next to the element.
Writing in TS, I care about prop autocomplete much more than about component tooltip, and second version from comment above is more desirable because it's DRY and with TS you can skip most of the @type {Boolean} [gridlines=true] – noise because it's inferred.
#1717 also asks about adding slots to hover info.
For comparison with JSX: In a React project, if you hover over a component, it shows the function definition or const definition which also contains a rough description of the props or, if the user added docs, a litte more. If it's a class component however, this only shows the the class name and possibly the doc that is added to the class, but not the docs of the constructor - which would be equivalent here. I'm not saying that this means "works as designed, we won't do this".
@dummdidumm Has anything has changed on this front now that Svelte 5 has changed the component model significantly? Are snippets something that should be documented? With components as functions, does this change the expected documentation outcome?
Good question. One could argue that the JSDoc you put above $props() should become the documentation, and/or that we move the type definition into the hover info somehow.
The tricky part is that for from a types-perspective, nothing changes. Component will still be typed as classes because it's more ergonomic for the cases we care about, and you shouldn't get the impression that you can invoke the component yourself like a regular function.