MappedTypeNode: Add method to generate a structure with enumerated keys?
- Dominant language
- TypeScript
- Stars
- 6.2k
- Forks
- 238
- Avg merge
- 2m
- Merged PRs (30d)
- 1
Description
**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.
```typescript
getEnumeratedStructure(name: string) : OptionalKind {
}
```
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`.
1. Get the variable `Identifier`'s name, and find its reference nodes.
1. Identify the set of property keys (strings, numbers, symbols) from the mapped type node's constraint type.
1. Create a `TypeAliasDeclaration` structure with type "" and the user's passed-in name. Call it `enumeratedDeclaration`.
1. `let typeOutput: string[] = [];`
1. 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 + ";")`
1. `enumeratedDeclaration.type = "{\n" + typeOutput.join("\n") + "\n};\n";`
1. `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***
```typescript
export type NumberStringType = {
repeatForward(s: S, n: number): string;
repeatBack(n: number, s: string): string;
};
type NumberStringEnumerated = {
[P in keyof NumberStringType as `${P}${Q}`]: `${Q} = ${P}`;
}
```
***ECMAScript symbols***
```typescript
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***
```typescript
type FooBar = {
foo: unknown;
bar: unknown;
};
type MappedFooBar = {
[key in keyof FooBar]: key extends "foo" ? true : false;
};
```
***Non-aliased mapped types***
```typescript
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.
Contributor guide
Research direction
Start with the MappedTypeNode class and review issues #758 and #1311 for the mapped-type resolution context. Define the proposed TypeAliasDeclarationStructure API around the listed steps, then consider the parameterized, symbol, conditional, and non-aliased examples as acceptance cases; done means the structure can be generated without modifying the target source file.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100