TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Mapped keys do not preserve JSDoc documentation

Open devmanbr opened this issue 2 years ago • 5 comments

Bug Report

🔎 Search Terms

mapping types with docs; preserve docs on mapped types

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about this

⏯ Playground Link

Playground link with relevant code

💻 Code

All relevant code can be found in the example links.

Why doesn't TypeScript/Vscode preserve documentation / JSDoc for mapped types?

Using this example that appears in the TypeScript documentation itself, which you can quickly reproduce in Vscode locally, you can see the problem.

It is expected that when remapping the keys of a type object, the documentation would remain what was defined in the original/previous keys, but this does not happen. Nothing is preserved.

This sucks in the development context, because it requires us to have to repeat the documentation of a property or method, for example, in multiple places, thousands of times. This can lead to consistency issues as the code grows and loses the point of automating things.

Another directly related problem is in Index Signatures. Even if documentation is added, nothing is preserved. This also includes Union Types. An example can be seen here.

🙁 Actual behavior

JSDoc documentation is not preserved in mapped keys.

🙂 Expected behavior

Mapped keys must have JSDoc documentation of their original/previous names.

devmanbr avatar Sep 11 '22 12:09 devmanbr

This seems like an extension of #47933; if you write code like (Playground Link):

type someType = {
  /**
   * some text to be displayed
   */
  [key:string]: string;
};

declare const test: someType;

someType.anything;

Hovering over anything gives you back the docstring as expected, but it doesn't look like the documentation follows through to use of that declaration in a contextual context where you're assigning to someType.

jakebailey avatar Sep 12 '22 22:09 jakebailey

FYI @danay1999

jakebailey avatar Sep 12 '22 22:09 jakebailey

@jakebailey Thank you for the heads up. It seems the main issue is that it doesn't conserve the JSDoc from the original keys, and therefore it has nothing to show. I am not sure if it would count as an extension of https://github.com/microsoft/TypeScript/issues/47933

danay1999 avatar Sep 15 '22 16:09 danay1999

What I cited was just a brief example. But I haven't come across types working as they should (as I reported) in real use cases.

devmanbr avatar Sep 22 '22 21:09 devmanbr

I know it's hard to project this, but as far as the primary problem at least is there any light on when it might be resolved?

devmanbr avatar Sep 22 '22 21:09 devmanbr

I'd like to have this, too. Having dug into the code, it looks like this is mostly an intentional design decision. I'm interested in revisiting that, if it is. That said, I think there are a few potential issues with just blithely passing along the documentation.

The most significant one is simply that the documentation may well not be correct for the mapped properties. Consider a version of your example above using setters:

type Setters<Type> = {
  [Property in keyof Type as `set${Capitalize<string & Property>}`]: (value: Type[Property]) => void
};

The documentation "Person's name." doesn't really fit the method setName(). I mean, you can probably follow along in this case, but you can see how slightly more exotic cases would end up with some really wacky and confusing documentation strings.

But since there are some solid use cases for being able to bring the documentation along, I wonder if it might be worthwhile to add some kind of template-literal-like syntax to the JSDoc to enable it explicitly. Consider something like

type Setters<Type> = {
  /** Set ${Uncapitalize<docof Property>} */
  [Property in keyof Type as `set${Capitalize<string & Property>}`]: (
    /** New value for ${Uncapitalize<docof Property>} */
    value: Type[Property]
  ) => void
};

interface Person {
  /** Person's name */
  name: string;
  /** Person's age */
  age: number;
}

const me: Setters<Person> = {
  // 💬 Documentation: "Set person's name"
  setName: (value) => {},
  // 💬 Documentation: "Set person's age"
  setAge: (value) => {}
};

// 💬 Documentation on parameter: "New value for person's name"
me.setName("Jake Carter")
// 💬 Documentation on parameter: "New value for person's age"
me.setAge(35)

The other way to go would be the proposal for full on imperative types. I'm not sure if this idea is stronger or weaker for being more constrained than that.

Peeja avatar Mar 31 '23 01:03 Peeja

your observations are very valid, @Peeja. I honestly don't know what the best way is, but I really hope that something can be done to allow for this more "dynamic" control at the documentation level on mapped types.

devmanbr avatar Apr 12 '23 12:04 devmanbr

I noticed that Pick preserves JSDoc but some simple variations do not:

interface Person {
  /** Person's name */
  name: string;
  /** Person's age */
  age: number;
}

type PickName = Pick<Person, 'name'>;
const pickName: PickName = {
  name: 'alice',  // <-- has JSDoc
};

interface ExplicitName {
  name: Person['name'];
}
const explicitName: ExplicitName = {
  name: 'bob',  // <-- no JSDoc
};

JSDoc seems to get preserved by a "homomorphic mapped type" but not by anything else.

danvk avatar Aug 29 '23 18:08 danvk

Adding to this,

type Text= {
  otherProperty: string,
  /**
    * @deprecated <- This is also not working
  */
  [key: string]: unknown;
}

SushantChandla avatar Apr 09 '24 09:04 SushantChandla

I use renaming of mapped properties quite often to prefix properties for React components to be passed down to sub components, it really sucks having the documentation for the original source properties essentially disappearing into the ether due to the rewriting. It'd be great if we could get some kind of hinting for documentation on generated properties to pull in the source documentation.

Tharaxis avatar May 28 '24 19:05 Tharaxis