feat(ivy): support enum values in static resolution (#25619)
This commit adds support for enumeration values. An enumeration value is now a first-class return value of the resolver, which provides both a Reference to the enum type itself and the name of the value from that enum. Resolving an enum itself returns a Map<string, EnumValue>. PR Close #25619
This commit is contained in:
parent
61218f5f0b
commit
5c95b4b3a3
|
@ -9,4 +9,4 @@
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
|
|
||||||
export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector';
|
export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector';
|
||||||
export {AbsoluteReference, ImportMode, Reference, ResolvedReference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
|
export {AbsoluteReference, EnumValue, ImportMode, Reference, ResolvedReference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function isDynamicValue(value: any): value is DynamicValue {
|
||||||
* non-primitive value, or a special `DynamicValue` type which indicates the value was not
|
* non-primitive value, or a special `DynamicValue` type which indicates the value was not
|
||||||
* available statically.
|
* available statically.
|
||||||
*/
|
*/
|
||||||
export type ResolvedValue = number | boolean | string | null | undefined | Reference |
|
export type ResolvedValue = number | boolean | string | null | undefined | Reference | EnumValue |
|
||||||
ResolvedValueArray | ResolvedValueMap | DynamicValue;
|
ResolvedValueArray | ResolvedValueMap | DynamicValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +72,15 @@ export interface ResolvedValueArray extends Array<ResolvedValue> {}
|
||||||
* `ResolvedValue`.
|
* `ResolvedValue`.
|
||||||
*/ export interface ResolvedValueMap extends Map<string, ResolvedValue> {}
|
*/ export interface ResolvedValueMap extends Map<string, ResolvedValue> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A value member of an enumeration.
|
||||||
|
*
|
||||||
|
* Contains a `Reference` to the enumeration itself, and the name of the referenced member.
|
||||||
|
*/
|
||||||
|
export class EnumValue {
|
||||||
|
constructor(readonly enumRef: Reference<ts.EnumDeclaration>, readonly name: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the scope of a function body, which includes `ResolvedValue`s for the parameters of that
|
* Tracks the scope of a function body, which includes `ResolvedValue`s for the parameters of that
|
||||||
* body.
|
* body.
|
||||||
|
@ -438,6 +447,8 @@ class StaticInterpreter {
|
||||||
return context.scope.get(node) !;
|
return context.scope.get(node) !;
|
||||||
} else if (ts.isExportAssignment(node)) {
|
} else if (ts.isExportAssignment(node)) {
|
||||||
return this.visitExpression(node.expression, context);
|
return this.visitExpression(node.expression, context);
|
||||||
|
} else if (ts.isEnumDeclaration(node)) {
|
||||||
|
return this.visitEnumDeclaration(node, context);
|
||||||
} else if (ts.isSourceFile(node)) {
|
} else if (ts.isSourceFile(node)) {
|
||||||
return this.visitSourceFile(node, context);
|
return this.visitSourceFile(node, context);
|
||||||
} else {
|
} else {
|
||||||
|
@ -445,6 +456,18 @@ class StaticInterpreter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private visitEnumDeclaration(node: ts.EnumDeclaration, context: Context): ResolvedValue {
|
||||||
|
const enumRef = this.getReference(node, context) as Reference<ts.EnumDeclaration>;
|
||||||
|
const map = new Map<string, EnumValue>();
|
||||||
|
node.members.forEach(member => {
|
||||||
|
const name = this.stringNameFromPropertyName(member.name, context);
|
||||||
|
if (name !== undefined) {
|
||||||
|
map.set(name, new EnumValue(enumRef, name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
private visitElementAccessExpression(node: ts.ElementAccessExpression, context: Context):
|
private visitElementAccessExpression(node: ts.ElementAccessExpression, context: Context):
|
||||||
ResolvedValue {
|
ResolvedValue {
|
||||||
const lhs = this.visitExpression(node.expression, context);
|
const lhs = this.visitExpression(node.expression, context);
|
||||||
|
@ -689,6 +712,8 @@ function literal(value: ResolvedValue): any {
|
||||||
function identifierOfDeclaration(decl: ts.Declaration): ts.Identifier|undefined {
|
function identifierOfDeclaration(decl: ts.Declaration): ts.Identifier|undefined {
|
||||||
if (ts.isClassDeclaration(decl)) {
|
if (ts.isClassDeclaration(decl)) {
|
||||||
return decl.name;
|
return decl.name;
|
||||||
|
} else if (ts.isEnumDeclaration(decl)) {
|
||||||
|
return decl.name;
|
||||||
} else if (ts.isFunctionDeclaration(decl)) {
|
} else if (ts.isFunctionDeclaration(decl)) {
|
||||||
return decl.name;
|
return decl.name;
|
||||||
} else if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) {
|
} else if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {TypeScriptReflectionHost} from '..';
|
import {TypeScriptReflectionHost} from '..';
|
||||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||||
import {AbsoluteReference, Reference, ResolvedValue, staticallyResolve} from '../src/resolver';
|
import {AbsoluteReference, EnumValue, Reference, ResolvedValue, staticallyResolve} from '../src/resolver';
|
||||||
|
|
||||||
function makeSimpleProgram(contents: string): ts.Program {
|
function makeSimpleProgram(contents: string): ts.Program {
|
||||||
return makeProgram([{name: 'entry.ts', contents}]).program;
|
return makeProgram([{name: 'entry.ts', contents}]).program;
|
||||||
|
@ -265,4 +265,23 @@ describe('ngtsc metadata', () => {
|
||||||
|
|
||||||
it('template expressions work',
|
it('template expressions work',
|
||||||
() => { expect(evaluate('const a = 2, b = 4;', '`1${a}3${b}5`')).toEqual('12345'); });
|
() => { expect(evaluate('const a = 2, b = 4;', '`1${a}3${b}5`')).toEqual('12345'); });
|
||||||
|
|
||||||
|
it('enum resolution works', () => {
|
||||||
|
const result = evaluate(
|
||||||
|
`
|
||||||
|
enum Foo {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = Foo.B;
|
||||||
|
`,
|
||||||
|
'r');
|
||||||
|
if (!(result instanceof EnumValue)) {
|
||||||
|
return fail(`result is not an EnumValue`);
|
||||||
|
}
|
||||||
|
expect(result.enumRef.node.name.text).toBe('Foo');
|
||||||
|
expect(result.name).toBe('B');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue