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:
Alex Rickabaugh 2018-08-22 11:07:47 -07:00 committed by Matias Niemelä
parent 61218f5f0b
commit 5c95b4b3a3
3 changed files with 47 additions and 3 deletions

View File

@ -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';

View File

@ -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)) {

View File

@ -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');
});
}); });