ts-morph icon indicating copy to clipboard operation
ts-morph copied to clipboard

MappedTypeNode: Add method to generate a structure with enumerated keys?

Open ajvincent opened this issue 3 years ago • 2 comments

Is your feature request related to a problem? Please describe.

See issues #758, #1311. Figuring out the implied fields of a MappedTypeNode is hard. As I explain in #1311, I think I have found a way to make this work, and I want to implement it in the MappedTypeNode class directly.

Describe the solution you'd like

I want to provide an API to generate a TypeAliasDeclarationStructure. In my own project, I would add it to a temporary SourceFile in order to extract further type information.

  getEnumeratedStructure(name: string) : OptionalKind<TypeAliasDeclarationStructure> {
  }

This would do several steps:

  1. Get the type of a base structure from an ancestor of the node as a string, and extract the mapped type's definition, calling it templateTypeString.
  2. Get the variable Identifier's name, and find its reference nodes.
  3. Identify the set of property keys (strings, numbers, symbols) from the mapped type node's constraint type.
  4. Create a TypeAliasDeclaration structure with type "" and the user's passed-in name. Call it enumeratedDeclaration.
  5. let typeOutput: string[] = [];
  6. For each element of the set of property keys: a. Make a copy of templateTypeString, calling it fieldText. b. For each variable reference node (in reverse order), replace the text of the reference node in fieldText with the property key (escaped). c. typeOutput.push(" " + key + ": " +fieldText + ";")
  7. enumeratedDeclaration.type = "{\n" + typeOutput.join("\n") + "\n};\n";
  8. return enumeratedDeclaration;

Ideally, I would add several static and private utility methods to MappedTypeNode to achieve this.

Why a structure?

I don't necessarily want to modify the source files of a target project, except possibly temporary source files I would not save. As far as I know, neither TypeScript nor ts-morph supports detached nodes. A structure is just an ordinary JavaScript object, from what I see.

Describe alternatives you've considered

None so far. This is the first option which will (at least partially) resolve for me the mapped type's fields. I fully expect I will have to work further in my own projects to analyze and optimize the generated type.

Notes on testing

I can already imagine some interesting test cases:

Parameterized mapped types

export type NumberStringType = {
  repeatForward<S extends string>(s: S, n: number): string;
  repeatBack(n: number, s: string): string;
};

type NumberStringEnumerated<Q extends string> = {
  [P in keyof NumberStringType as `${P}${Q}`]: `${Q} = ${P}`;
}

ECMAScript symbols

const SymbolKey = Symbol("key");

type FooBarAndSymbol = {
  foo: unknown;
  bar: unknown;

  [SymbolKey]: boolean;
};

type MappedFooBarAndSymbol = {
  [key in keyof FooBarAndSymbol]: key;
};

Conditional types in the mapped type's type node

type FooBar = {
  foo: unknown;
  bar: unknown;
};

type MappedFooBar = {
  [key in keyof FooBar]: key extends "foo" ? true : false;
};

Non-aliased mapped types

type MappedFooBar = {
  version: "0.1";
} & {
  [key in ("foo" | "bar")]: key;
};

I want to leave it up to other API's to perform the type resolution for indexed access and conditional types, at least for now.

ajvincent avatar Dec 01 '22 04:12 ajvincent

Another test might indicate trouble:

type InfiniteStringKeys = {
  [key: string] : 1;
};

type MappedTypeInfiniteStringKeys = {
  [key in keyof InfiniteStringKeys]: 2;
};

Both of these have an infinite set of string keys, which would make enumerating them all impossible. We'd have to throw an exception in that case. 🤔

ajvincent avatar Dec 02 '22 03:12 ajvincent

So I am coming up on an issue with performance due to a deeply nested, computed type (lots of mapped types and such throughout) based on some generated code (type-graphql). I feel like having all of the types just dumped into a .d.ts file would be more performant (at least from the language server's perspective) which I think was kind of the original intent of this issue. I don't suppose that any meaningful work was ever done on this?

john-landgrave avatar Jan 15 '24 19:01 john-landgrave