Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type has no index signature #14951

Closed
testerez opened this issue Mar 31, 2017 · 34 comments · Fixed by #37903
Closed

type has no index signature #14951

testerez opened this issue Mar 31, 2017 · 34 comments · Fixed by #37903
Labels
Committed The team has roadmapped this issue Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@testerez
Copy link

testerez commented Mar 31, 2017

TypeScript Version: 2.2.2

Code

function getName(id: number) {
  return {
    1: 'one',
    2: 'two',
  }[id] || 'something else';
}

Expected behavior:
{1: 'one'}[id] type should be string | undefined
So I would expect the return type to be inferred as string.

Actual behavior:

Element implicitly has an 'any' type because type '{ 1: string; 2: string; }' has no index signature.

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Mar 31, 2017
@RyanCavanaugh
Copy link
Member

Interesting point -- a fresh object literal type certainly could have an index signature derived from its properties. Not sure how common this pattern is in practice, though (do you really want to allocate a new object every time this function gets called?).

@testerez
Copy link
Author

testerez commented Mar 31, 2017

The function is to illustrate my point. I found this pattern more elegant than a switch/case for example. Is it as performant? I don't know but I don't think I care most of the time.
Are you suggesting that it could work only if [id] is applied directly to the object declaration? Ideally, I'd like to also be able to do:

const nameMap = {
  1: 'one',
  2: 'two',
};
function getName(id: number) { // inferred return type should be string
  return nameMap[id] || 'something else';
}

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Mar 31, 2017

The implicit any there is by design -- we don't allow arbitrary indexing into objects that don't include an index signature in their type. Once an object has been "indirected" we no longer trust that it isn't an alias to some other object that might have non-string properties (in this case).

@testerez
Copy link
Author

Ok, makes sense. Supporting my first example would already be nice for me.

@PyroVortex
Copy link

I'd go so far as to expect the return type from the first example to be 'one' | 'two' | 'something else'. At least, the only reason I would use that particular pattern would be if that were my desired return type. Otherwise the easy solution is to do:

const nameMap: { [key: number]: string } = {
    1: 'one',
    2: 'two'
};
function getName(id: number) { // inferred return type is string
    return nameMap[id] || 'something else';
}

@RyanCavanaugh
Copy link
Member

@PyroVortex we have no way to know that the line nameMap[3] = "bob"; isn't somewhere in your program

@PyroVortex
Copy link

@RyanCavanaugh
I was specifically concerned with the pattern

return {
    1: 'one',
    2: 'two'
}[id];

for which the object is for all intents and purposes completely immutable by virtue of scoping. With this particular construction I would want the compiler to be as specific as possible in the typing. Otherwise I would fall back to the pattern of specifying a type for the constant.

@vladimir-tikhonov
Copy link

👍 for this feature. Lack of the index signature often makes auto-inherited types useless, so I need to copy-paste type inherited by typescript compiler and add index signature to it.

The fix is really simple and clear in my opinion - for objects like this:

const randomMapping = {
    stringProp: "1",
    numberProp: 2
};

Instead of the following type:

interface RandomMappingWithoutIndexSignature {
    stringProp: string;
    numberProp: number;
}

Generate type with the index signature:

interface RandomMappingWithIndexSignature {
    stringProp: string;
    numberProp: number;

    [propName: string]: string | number | undefined;
}

Am I missing something?

@lukewlms
Copy link

We have a use case where this would be nice as well. The simple Knex.js setup code below won't work without declaring the type of configs; I just want to index in and get whatever config obj matches the env.

const env = process.env.NODE_ENV || 'development';
const configs = { development, test, production };
const knex = knexConstructor(configs[env]);

However in the case @vladimir-tikhonov shows, I think that index signature can't be reasonably inferred. Even if it's const, an object can still have properties added later returning any arbitrary type. Indexing into the object would then have to return any - and that's an implicit any.

So it seems to me the design @RyanCavanaugh is referring to is correct and inevitable - there's no way to guarantee you're going to get specific types (i.e. not any) from the index signature.

@agalazis
Copy link

Also allow union types for keys please but I do believe that some sort of inference could be done here

@JakobJingleheimer
Copy link

JakobJingleheimer commented Apr 22, 2018

I'm not sure if this is the same issue, but I'm getting a similar Error:

TS7017: Element implicitly has an 'any' type because type 'iAbilityModifiers' has no index signature.

Here's a simplified example:

enum eFoobar {
    foo = 'foo',
    bar = 'bar',
}

type tFooBar = { // this is `iAbilityModifiers` from the error
    [fb in keyof eFoobar]: number;
};
const character = {
    // …
    foobars: _.reduce(eFoobar,
        (acc, key) => _.merge(acc, {[key]: 10}),
        {} as tFooBar
    ),
    // …
},
{_.map(someOptions, ({ text: foobar, value: key }) => (
    <input value={character.foobars[key]} /> {/* error references this line */}
)}

According to the Mapped Types documentation, adding keyof in [fb in keyof eFoobar] is supposed to take care of the typing:

In these examples, the properties list is keyof T and the resulting type is some variant of T[P]. This is a good template for any general use of mapped types. That’s because this kind of transformation is homomorphic, which means that the mapping applies only to properties of T and no others.

@ericdrobinson
Copy link

Not sure if this is helpful in terms of adding a use-case, but I encountered what appears to be the same (or related?) issue by using a type annotation that uses an enum to restrict the names of properties. This is accomplished by defining the index signature (I think? I'm honestly not sure about the terminology here - "index signature" seems to be something very specific to TypeScript...?):

type EnumIndexedType =
{
    // Note: entries are optional.
    [TKey in SomeList]?: string;    // <-- is this not an index signature?
}

See the full example at the TypeScript playground here (issue on line 27).

If someone can point out a better or otherwise "more correct" way of doing what I'm attempting here that works as TypeScript expects, I'd very much appreciate it!

Regardless, the error as it stands is very confusing: "No Index Signature?! Isn't that, like, the only type annotation I wrote?"

@JakobJingleheimer
Copy link

JakobJingleheimer commented May 11, 2018

@ericdrobinson, yes, the error message is extremely confusing. However, what it is actually complaining about is not the string.

What you wrote is called a Mapped Type (horrible name for trying to google it).

You've got 2 choices:

type Hashmap<K, V> = {
    [k in K]: V;
}

I usually use this with K being an enum and V being some kind of class/interface:

enum eCharacterClass {
    mage = 'mage',
    rogue = 'rogue',
    warrior = 'warrior',
}

class CharacterClass {
    health: number = 1;
    // …
}

type tCharacterClassSet = HashMap<eCharacterClass, CharacterClass>;
/*
{
    [eCharacterClass.mage]: CharacterClass{},
    [eCharacterClass.rogue]: CharacterClass{},
    [eCharacterClass.warrior]: CharacterClass{},
}
*/

So you could do:

type EnumIndexedType = Hashmap<SomeList, string>;

Alternatively, you could create a weak relationship:

interface WeakHashmap<V> {
    [key: string]: V[keyof V];
}
type tCharacterClassWeakSet = WeakHashmap<CharacterClass>;
/*
{
    mage: CharacterClass{},
    rogue: CharacterClass{},
    warrior: CharacterClass{},
}
*/

In this second one, the "index signature" will be string, which means there is a weak link between your Mapped Type and your enum: You can bypass the enum by supplying any value that happens to be in it ('foo' instead of SomeList.foo).

@ericdrobinson
Copy link

ericdrobinson commented May 14, 2018

@jshado1 Your Option 2 makes total sense to me but doesn't provide the same restrictions I'm going for, as you point out. With Option 1, I'm not able to get things to work in my actual environment.

First, in order to remove errors, I had to modify the HashMap definition to this:

type HashMap<K extends string, V> = {
    [k in K]: V;
}

Notice the extends string. That removed the error stating that

Type 'K' is not assignable to type 'string'.

At the end of the day, the HashMap version also resulted in the same type of error in VSCode:

[ts] Element implicitly has an 'any' type because type 'HashMap<SomeEnum, SomeClass>' has no index signature.

To be clear, viewing the issue in my previous comment requires enabling the noImplicitAny option in the Options dropdown. Indeed, the code suggested in Option 2 appears to result in the same error when the noImplicitAny option is set. Please see this merged example and enable the noImplicitAny option to see the issue.

@JakobJingleheimer
Copy link

@ericdrobinson are you using an enum with string values? I just tried it without, and I get that error, but when I switch it to the string enum, it works (Playground). You cannot cast an enum (a collection of options) to a string, but you can cast its options to strings if need-be. I don't know the TS syntax for doing that in a Mapped Type (I'd guess either as string or : string somewhere).

@ericdrobinson
Copy link

are you using an enum with string values?

@jshado1 I take it you didn't actually look at the Playground example I provided and follow the directions about setting the noImplicitAny option, yes? The enum I used does indeed have string values.

I have adjusted the Playground example that you provided to include an actual triggering use of the issue. Instructions:

  1. Please click this link.
  2. Press the Options button above the TypeScript input field.
  3. In the dropdown that appears, enable the noImplicitAny option.
  4. Dismiss the Options dropdown.

You should see an error appear no line 27, one built from your code.

Line 34, on the other hand, has a workaround. As the comment above the for-in loop suggests, the error is confusing and does not suggest that the workaround is to explicitly cast the type of entry to the enum type, something that TypeScript should theoretically be able to determine. At the very least, TypeScript should identify index signatures built with enums as valid (at least when string values are supplied as in the provided examples).

@vadistic
Copy link

The error message is truly confusing - I just spend good few hours before looking elsewhere before realizing this...

(TS 2.8 / 2.9@rc)

const palette = {
    primary: 'hotpink',
    secondary: 'green',
    danger: 'red'
}

type PaletteColors = keyof typeof palette
type Palette = { [Color in PaletteColors]: string }

// Not really necessary but let's assert index signature second time
const myPalette = palette as Palette

const key = 'primarrrrry'

// ERR: Element implicitly has an 'any' type because type Palette
// has no index signature. 
const dynamic = myPalette[key]

Playground

The Error here is, that var key is not assignable to index type/ index signature of Palette. In my example Palette has index signature (from object literal expression and another one from type assertion)

Did I get it right? - If yes, then this error message should definitively be tweaked, because it's plain wrong, not just confusing...

@ericdrobinson
Copy link

The Error here is, that var key is not assignable to index type/ index signature of Palette. In my example Palette has index signature (from object literal expression and another one from type assertion)

If you look at the type of myPalette in the Playground, you'll see that it's type is the literal object rather than something that resolved to Palette. If you annotate myPalette to be of type Palette then you still get the "no index signature" error. Which... is confusing because Palette technically does have an index signature (unless I'm missing something - apparently TypeScript index signatures require that the index key be of type string, number, or Symbol so perhaps something there).

To me, this part feels like a bug.

That said, the error you encounter drops away entirely if you add an explicit type declaration to your definition of key:

const key: PaletteColors = 'primarry'

Rather, what you get now is a far more helpful error that says:

Type '"primarry"' is not assignable to type '"primary" | "secondary" | "danger"'.

At the end of the day, more explicit declaration of expected types appears to appease the compiler. However, the compiler's current error is extremely confusing as both you (@vadistic) and myself have encountered.

@JakobJingleheimer
Copy link

Yes, TypeScript is very prone to throwing "has no index signature" whenever the wind blows (it seems to be the TS version of "something went wrong").

@ericdrobinson sorry before, I missed the enable noImplicitAny step (I thought it was already enabled). I use those two Mapped Types in my own project, which has noImplicitAny enabled, and they don't cause that error.

@RyanCavanaugh
Copy link
Member

Which... is confusing because Palette technically does have an index signature

Palette is a mapped type, and mapped types don't have index signatures. The fact that both use [ ] is a syntactic coincidence.

In my example Palette has index signature (from object literal expression and another one from type assertion)

Object literals don't have index signatures. They are assignable to types with index signatures if they have compatible properties and are fresh (i.e. provably do not have properties we don't know about) but never have index signatures implicitly or explicitly.

Nothing in this example has an index signature.

@vadistic
Copy link

@RyanCavanaugh @ericdrobinson Thanks for answering.

I thought I knew how they worked, but it looks I was wrong and made two false assumptions:

  1. Thought that typeof of object literals are inferred to have (string | number) index signature
  2. Thought that mapped types over union of string literals is a way to declare a limited set of string index signatures That's how it looked to me from the example in Typescript Deep Dive

I'll think over it, thanks!

@ericdrobinson
Copy link

Interesting point -- a fresh object literal type certainly could have an index signature derived from its properties. Not sure how common this pattern is in practice, though (do you really want to allocate a new object every time this function gets called?).

@RyanCavanaugh I ran across this when using the JavaScript for-in pattern with a [mapped] type that used enum values as indices. However, it also affects a far simpler (and likely more common) case:

const palette = {
    primary: 'hotpink',
    secondary: 'green',
    danger: 'red'
}

for (let elt in palette)
{
    console.log(palette[elt]);  // ... type {...} has no index signature.
}

Here is a playground showcasing the issue. Be sure to turn on the noImplicitAny option.

This is the standard affair use-case for the for-in pattern. Check out the example used on MDN for the for...in statement:

var string1 = "";
var object1 = {a: 1, b: 2, c: 3};

for (var property1 in object1) {
  string1 = string1 + object1[property1];
}

console.log(string1);

Copy the MDN example over into the TypeScript playground and you get the same error (with the noImplicitAny option enabled, of course).

The enum case that lead me here is simply a variation on this theme.

Given that this is a standard method for iterating over properties of an object in JavaScript (and theoretically TypeScript), what is the TypeScript Way™ of handling the implicit any that appears due to object literals not having a standard index signature?


Palette is a mapped type, and mapped types don't have index signatures. The fact that both use [ ] is a syntactic coincidence.

@RyanCavanaugh If [] refers to index signatures with concrete types, what do you call [] when used in a mapped types definition? Is there a term?

That aside, I see now that what the [] is doing in the mapped type is syntactic sugar to encapsulate a set of "types" (not sure of the correct terminology here). When you expand what is inside the [], you end up with a list of concrete properties (I assume the compiler/language service does this in the background). That syntax effectively encapsulates the mapping rules.

@RyanCavanaugh
Copy link
Member

@ericdrobinson Regarding the first example, we have to put aside the fact that we (from inspection) can see the runtime behavior of this code by executing it in our heads. If palette, which has the type {primary: string, secondary: string, danger: string} came from somewhere else, it might be an alias to an object that actually had the runtime shape {primary: string, secondary: string, danger: string, OTHER: number}. So we can't guarantee that palette[elt] always produces a string.

Of course, we can always add more special cases to the type system to detect the specific case of iterating over a known-bounded object literal, but this just leads to the "Why does this code not behave like this other nearly-identical code?" problem when we fail to properly exhaust all the special cases.

The "standard" way would be to write const palette: { [key: string]: string } = {...`

Regarding mapped types, remember that { [K in T]: U } is a special form - it can't be combined with other properties within the { }. So there's not really a name for the [K in T: U] part because it's just part of the overall "mapped type" syntax { [K in T]: U }

@ericdrobinson
Copy link

Regarding the first example, we have to put aside the fact that we (from inspection) can see the runtime behavior of this code by executing it in our heads. If palette, which has the type {primary: string, secondary: string, danger: string} came from somewhere else, it might be an alias to an object that actually had the runtime shape {primary: string, secondary: string, danger: string, OTHER: number}. So we can't guarantee that palette[elt] always produces a string.

@RyanCavanaugh Fair enough! That certainly is a fair point. I considered adding a default [key: string]: any (and key: number equivalent) to object literals but that would reduce the ability of a user to create something more specific without jumping through hoops. Hmm...

Perhaps consider adjusting the error to be more informative? Looking for info on this (while admittedly being under the false assumption that the Mapped Type was a restrictive index signature of sorts) didn't turn up much helpful information.

Alternatively (or in addition), could you expand upon index signatures in the documentation to provide an explanation as to why they're not provided by default (especially with object literals)? I'd guess I'm not the only one to be tripped up by the [] syntax of mapped types.


The "standard" way would be to write const palette: { [key: string]: string } = {...

Mind blown.


Regarding mapped types, remember that { [K in T]: U } is a special form - it can't be combined with other properties within the { }. So there's not really a name for the [K in T: U] part because it's just part of the overall "mapped type" syntax { [K in T]: U }.

Got it. I will say that when I tested out the "standard" way you suggested above, I immediately found myself trying to adjust it to const palette: { [key in SomeEnum]: string } = {.... It only occurred to me after seeing the implicit any error return that I had merely created an inline mapped type... that syntactic coincidence strikes again... ;p

@digable1
Copy link

Here's my little example that caused me to run into this issue (mostly typed in IDE to reduce syntax errors):

type UnionParameter = object | string | boolean;

export class MyLittleExample {
    public getBooleanAttributeKeysIfApplicable(unionParameter: UnionParameter): Array<string> {
        const justTheBooleanKeys = [];
        if (typeof unionParameter === 'object') {
            const unionParameterKeys = Object.keys(unionParameter);

            for (const key of unionParameterKeys) {
                if (typeof unionParameter[key] === 'boolean') { // ran into 'has no index signature here
                    justTheBooleanKeys.push(key);
                }
            }
        }

        return justTheBooleanKeys;
    }
}

Based on other comments, I sort of see why this is happening. However, unless my feeble brain is feeble, this looks like a valid use case and I might not be alone.

@bobvanderlinden
Copy link

bobvanderlinden commented Aug 16, 2018

I think the problem is two-fold.
Consider the following example:

const obj = {
  a: 1,
  b: 2
}
for (val key in obj) {
  console.log(obj[key])
}

This results in the following error:

Element implicitly has an 'any' type because type '{ a: number; b: number; }' has no index signature.

Note that key is considered to be string. Where-as we can see that key could be "a" | "b" in this example. The error makes sense when key is string, as the set of keys is higher than the keys that are in the object, thus indexing is dangerous. However, when key would have been considered to be "a" | "b" then the error shouldn't show up.

I also think that when the indexer type is greater than the key type (string vs "a" | "b"), then Typescript might want to infer | undefined from the result of the indexer.

So, Typescript is too quick to derive string from an object key. In addition, Typescript shouldn't error when the key of the indexer exactly matches the key type of the object.

@RyanCavanaugh RyanCavanaugh added Help Wanted You can do this Committed The team has roadmapped this issue Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". and removed In Discussion Not yet reached consensus labels Aug 23, 2018
@RyanCavanaugh RyanCavanaugh added this to the Community milestone Aug 23, 2018
@RyanCavanaugh
Copy link
Member

Accepting PRs for a solution that special-cases property access expressions where the thing being accessed is an object literal. Use the type of the index to filter to matching properties (the relevant indexing type here could be a string, number, or union of literals thereof) and return a union of the matching property types along with undefined if the indexing type is not bounded to only matching literals.

@MilesHeise
Copy link

It looks like there may be a fix in for this? But in the mean time I'm going to post a suggestion for others who came here like me. My use case was I had a mapped type that I could use when I had a bunch of identical reducers for different namespaces/domains (just a combo of Record and Readonly) and it worked in most cases but I ran into an issue where it gave me an implicit any error when I tried something like somethingReducer[domain]. I found that I could make a second type that just defined an index signature, and then use that in connection with my mapped type to fix the error. essentially you just have to go from this:

declare type DomainMap<DomainEnum extends string, Type> =  {
    readonly [value in DomainEnum]: Type;
}

to this:

declare type DefineIndexSignature<Type> = {
    [key: string]: Type
}

declare type DomainMap<DomainEnum extends string, Type> = DefineIndexSignature<Type> & {
    readonly [value in DomainEnum]: Type;
}

hope that might help others who have my same issue . . .

@simeyla
Copy link

simeyla commented Jan 23, 2019

I entended the color pallete exercise. May be of interest to some.

Playground - TURN ON STRICT MODE

const palette = {
    primary: { color: 'hotpink' },
    secondary: { color: 'green' },
    danger: { color: 'red', sound: 'alert' }
}

// the named entries: 'primary' | 'secondary' | 'danger'
type PaletteColor = keyof typeof palette

// infer the whole type of the pallete
// (not a string based index)
type Palette = typeof palette;

// just to prove we really have the 'Palette' type here I create a forced reference 
const fakePalette: Palette = <any> undefined;

// via 'Palette' we now have the FULL tree of our palette object available
// THIS FAILS AT RUNTIME OBVIOUSLY BUT THIS SHOWS COMPILER UNDERSTANDS IT
fakePalette.danger.color;
fakePalette.danger.sound;

// but not this one 
fakePalette.primary.sound;  // doesn't exist

// now back to dynamic strings
// make a reference to the palette (no particular reason!)
const myPalette = palette;  // don't need to assign a type here

// create a key somehow. compile definitely doesn't think this is a color
const key = 'prim' + 'ary';

// cast it to 'PaletteColor'
// caution: this could obviously now give runtime errors
const dynamic = myPalette[key as PaletteColor]

// try to get some properties from our 'dynamic' color pallette
const dynamicColor = dynamic.color;  // runtime gives 'hotpink' - this works!
console.log('dynamic.color: ' + dynamicColor);

// only common properties to all palette entries are available now
const dynamicCikir = dynamic.color;  // common to all
const dynamicSound = dynamic.sound;  // only in 'danger'

// hover over this 
type typeOfDynamic = typeof dynamic;

// you can't call 'sound' or even check for its existance (without type guard)
if (dynamic.sound)
{
    // isn't on all entries
}

// the only way I can see to fix this is to define an interface for the entries
interface PalletteEntry { color: string, sound?: 'alert' | 'barking' };

// and force it on each one
const palette2 = {
    primary: <PalletteEntry> { color: 'hotpink' },
    secondary: <PalletteEntry> { color: 'green' },
    danger: <PalletteEntry> { color: 'red', sound: 'alert' }
}


// now every palette entry has 'color' and 'sound' available
// even if they don't actually exist 
palette2.danger.color;
palette2.danger.sound;
palette2.secondary.sound;  // 'alert' | 'barking' | undefined


// the alternative is to make this a string indexed type
// but then you lose the ability to just type this anywhere
palette.primary

Addendum: You can make an 'inline' typeguard like this (in the situation that not all properties on your 'type' are of the same exact type)

if ('sound' in dynamic)
{
    dynamic.sound;
}

@glenjamin
Copy link

Palette is a mapped type, and mapped types don't have index signatures. The fact that both use [ ] is a syntactic coincidence.

This was the "aha" moment in this thread which made the error message make sense. It would be really helpful if the error message when doing index access on a mapped type explained this, or if the mapped type documentation mentioned this.

It would be even better if indexing over mapped types would produce the value, which i think is what #29528 is asking for.

@fregante
Copy link

fregante commented Jan 9, 2020

@RyanCavanaugh I'm a bit confused here. This particular error is frequent and annoying for me, but this example shouldn't be an exception.

a fresh object literal type certainly could have an index signature derived from its properties.

The fact that the object is static is irrelevant: the problem here is the key is a plain number and not keyof staticObject

The correct type should be:

function getName(id: 1 | 2) {
  return {
    1: 'one',
    2: 'two',
  }[id] || 'something else';
}

Valid: https://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABAcwKZQHIEMC2qAUMAJgFyICMiAPogEwCUZAzlAE4xjKIDeAUIolboQrJHwEDyZAOQJU0gDT8JtGVADucRcoC+AbWIBdajWlM4eKAAsOXVABsm8gNy8dQA

Fix/improve the Type has no index signature error in general, don't make this an exception.

@RyanCavanaugh
Copy link
Member

@fregante I'm not really sure what you're getting at

@fregante
Copy link

fregante commented Jan 10, 2020

Accepting PRs for a solution that special-cases property access expressions where the thing being accessed is an object literal.

Maybe I misread your solution proposal. Did you say that in this case only the error “Type has no index signature” will be dropped? Because I don’t think this is a special case.

However if my example (which doesn’t cause the error) returns 'one' | 'two' | 'something else' instead of string then that’s good.

@okmttdhr
Copy link
Contributor

@RyanCavanaugh
Hi, I submitted the PR #37903, could you take a look at it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committed The team has roadmapped this issue Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet