Reputation: 679
I'm wondering if there is a way to resolve the final value for an expression (using the TypeScript compiler API) that would work in a way similar to getTypeAtLocation.
Take the following example:
const g = {
a: 'hello'
}
export enum wonkyEnum {
A, // = 0
B = 1,
C = g['a'] as any,
D = "str" as any
E = [ ...[1,2,3] ][2]
}
My hope is that there is even a partial solution which will not require writing an involved recursive parser which covers all possible contingencies for an initializer expression. If nothing is exposed in TS, 3rd party open-source code could also be useful.
Otherwise, any detail which would help me to know all possible node variations in this place would help!
I already have a robust recursive parser system in place, which I am using primarily for walking Type structure, but it can be implemented for Node / Kind, also. I mostly would like to be sure that I'm not cluttering up the code unnecessarily or reinventing the wheel before going that route.
Thanks!
David's answer below is correct - there isn't a means to do it with TS.
In exploring it further, to cover all cases, actual evaluation would be required. This could get messy and be impossible in many cases (see comments below).
Fortunately, in the case of enums, TypeScript appears to generally be able to get the answer with the getConstantValue() method.
As a fallback, we'll recursively parse initializer node covering a few expression cases (as follows):
// key is tested for node.kind equality in parseInitializer()
const valueParsers = Map<number | number[], (node: any) => string | number | boolean | null>([
[SyntaxKind.TrueKeyword, () => true],
[SyntaxKind.FalseKeyword, () => false],
[SyntaxKind.NullKeyword, () => null],
[SyntaxKind.StringLiteral, (node: LiteralLikeNode) => node.text],
[
[SyntaxKind.AsExpression, SyntaxKind.ParenthesizedExpression, SyntaxKind.TypeAssertionExpression],
(node: any) => parseInitializer(node.expression) // recursive call
],
]);
If anyone has any ideas at broadening it a bit without going too crazy, feel free to comment below!
Upvotes: 4
Views: 1614
Reputation: 106640
This is useful to do, but the compiler doesn't have anything built in for resolving expression values. It's not concerned with that... the getConstantValue
on the type checker is specifically for enum members.
I also don't believe there is any library to help do this at the moment... at least I haven't seen one. Perhaps you or someone else would like to create one? That would be a fun project!
Anyway, these are just my high level thoughts that would work, but could be improved on:
Finding Property Assignment Value
It's kind of hard to do this because you would have to check if something modified g.a
before it is assigned to the enum member:
const g = { a: 'hello' };
g.a = 'other string';
If you really want to implement something like that, then I believe one way to do that is to get the symbol of g.a
(getSymbolAtLocation
), then from there look at the property assignment in the #valueDeclaration
property. From that property assignment, you'll have to walk the nodes in the tree down in order to get the last assignment to that property before the enum member declaration (some person looking for trouble might reassign the property in a previous enum member initializer). If the assignment expression is a constant or some expression you can resolve then you know the value. If it's not, then you'll have to go to that declaration and walk the tree from there. Basically, it starts getting really complicated and you won't always be able to determine the value statically.
Perhaps a limitation of your analyzer might be that string types aren't allowed in which case a const assertion should be used?
const g = { a: 'hello' } as const;
In that case, getTypeAtLocation
for a
in g.a
is the "hello"
string literal type.
Nested Arrays
For these nested arrays [ ...[1,2,3] ][2]
you could walk the tree in post order, resolve the array items, flatten the array due to the spread syntax, then get the third item in the array. Perhaps knowing it is only looking for the third item you could optimize that and only resolve enough to get the second item (could be a big time saver if the array is something like [...[g.a, 2, 3]]
).
Upvotes: 3