From b58e9ea7751a7be9e66012e26236e566b2a8b89b Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Wed, 27 Jul 2016 19:26:59 -0700 Subject: [PATCH] feat(compiler): Added support for references to static fields. (#10334) Closes: #10332 --- .../compiler-cli/src/static_reflector.ts | 13 ++++- .../test/static_reflector_spec.ts | 24 +++++++++ tools/@angular/tsc-wrapped/src/collector.ts | 13 ++++- .../tsc-wrapped/test/collector.spec.ts | 51 ++++++++++++++++++- 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/modules/@angular/compiler-cli/src/static_reflector.ts b/modules/@angular/compiler-cli/src/static_reflector.ts index c338e6fe44953..771039347d27e 100644 --- a/modules/@angular/compiler-cli/src/static_reflector.ts +++ b/modules/@angular/compiler-cli/src/static_reflector.ts @@ -438,8 +438,17 @@ export class StaticReflector implements ReflectorReader { return null; case 'select': let selectTarget = simplify(expression['expression']); - let member = simplify(expression['member']); - if (selectTarget && isPrimitive(member)) return selectTarget[member]; + if (selectTarget instanceof StaticSymbol) { + // Access to a static instance variable + const declarationValue = resolveReferenceValue(selectTarget); + if (declarationValue && declarationValue.statics) { + selectTarget = declarationValue.statics; + } else { + return null; + } + } + const member = simplify(expression['member']); + if (selectTarget && isPrimitive(member)) return simplify(selectTarget[member]); return null; case 'reference': if (!expression.module) { diff --git a/modules/@angular/compiler-cli/test/static_reflector_spec.ts b/modules/@angular/compiler-cli/test/static_reflector_spec.ts index 3d1985c85da8e..cfa79c2196fb2 100644 --- a/modules/@angular/compiler-cli/test/static_reflector_spec.ts +++ b/modules/@angular/compiler-cli/test/static_reflector_spec.ts @@ -404,6 +404,13 @@ describe('StaticReflector', () => { expect(annotations.length).toBe(1); expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100}); }); + + it('should be able to get metadata for a class containing a static field reference', () => { + const annotations = + reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); + expect(annotations.length).toBe(1); + expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]); + }); }); class MockReflectorHost implements StaticReflectorHost { @@ -963,6 +970,23 @@ class MockReflectorHost implements StaticReflectorHost { providers: MyModule.with(100) }) export class MyComponent { } + `, + '/tmp/src/static-field.ts': ` + import {Injectable} from 'angular2/core'; + + @Injectable() + export class MyModule { + static VALUE = 'Some string'; + } + `, + '/tmp/src/static-field-reference.ts': ` + import {Component} from 'angular2/src/core/metadata'; + import {MyModule} from './static-field'; + + @Component({ + providers: [ { provider: 'a', useValue: MyModule.VALUE } ] + }) + export class Foo { } ` }; diff --git a/tools/@angular/tsc-wrapped/src/collector.ts b/tools/@angular/tsc-wrapped/src/collector.ts index 3433b0c352009..c138da4095e11 100644 --- a/tools/@angular/tsc-wrapped/src/collector.ts +++ b/tools/@angular/tsc-wrapped/src/collector.ts @@ -150,9 +150,20 @@ export class MetadataCollector { case ts.SyntaxKind.GetAccessor: case ts.SyntaxKind.SetAccessor: const property = member; + if (property.flags & ts.NodeFlags.Static) { + const name = evaluator.nameOf(property.name); + if (!isMetadataError(name)) { + if (property.initializer) { + const value = evaluator.evaluateNode(property.initializer); + recordStaticMember(name, value); + } else { + recordStaticMember(name, errorSym('Variable not initialized', property.name)); + } + } + } const propertyDecorators = getDecorators(property.decorators); if (propertyDecorators) { - let name = evaluator.nameOf(property.name); + const name = evaluator.nameOf(property.name); if (!isMetadataError(name)) { recordMember(name, {__symbolic: 'property', decorators: propertyDecorators}); } diff --git a/tools/@angular/tsc-wrapped/test/collector.spec.ts b/tools/@angular/tsc-wrapped/test/collector.spec.ts index c5d7df05882b7..15854b4eb3888 100644 --- a/tools/@angular/tsc-wrapped/test/collector.spec.ts +++ b/tools/@angular/tsc-wrapped/test/collector.spec.ts @@ -16,7 +16,8 @@ describe('Collector', () => { host = new Host(FILES, [ '/app/app.component.ts', '/app/cases-data.ts', '/app/error-cases.ts', '/promise.ts', '/unsupported-1.ts', '/unsupported-2.ts', 'import-star.ts', 'exported-functions.ts', - 'exported-enum.ts', 'exported-consts.ts', 'static-method.ts', 'static-method-call.ts' + 'exported-enum.ts', 'exported-consts.ts', 'static-method.ts', 'static-method-call.ts', + 'static-field-reference.ts' ]); service = ts.createLanguageService(host, documentRegistry); program = service.getProgram(); @@ -378,6 +379,37 @@ describe('Collector', () => { }] }]); }); + + it('should be able to collect a static field', () => { + let staticSource = program.getSourceFile('/static-field.ts'); + let metadata = collector.getMetadata(staticSource); + expect(metadata).toBeDefined(); + let classData = metadata.metadata['MyModule']; + expect(classData).toBeDefined(); + expect(classData.statics).toEqual({VALUE: 'Some string'}); + }); + + it('should be able to collect a reference to a static field', () => { + let staticSource = program.getSourceFile('/static-field-reference.ts'); + let metadata = collector.getMetadata(staticSource); + expect(metadata).toBeDefined(); + let classData = metadata.metadata['Foo']; + expect(classData).toBeDefined(); + expect(classData.decorators).toEqual([{ + __symbolic: 'call', + expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'}, + arguments: [{ + providers: [{ + provide: 'a', + useValue: { + __symbolic: 'select', + expression: {__symbolic: 'reference', module: './static-field.ts', name: 'MyModule'}, + member: 'VALUE' + } + }] + }] + }]); + }); }); // TODO: Do not use \` in a template literal as it confuses clang-format @@ -642,6 +674,23 @@ const FILES: Directory = { }) export class Foo { } `, + 'static-field.ts': ` + import {Injectable} from 'angular2/core'; + + @Injectable() + export class MyModule { + static VALUE = 'Some string'; + } + `, + 'static-field-reference.ts': ` + import {Component} from 'angular2/core'; + import {MyModule} from './static-field.ts'; + + @Component({ + providers: [ { provide: 'a', useValue: MyModule.VALUE } ] + }) + export class Foo { } + `, 'node_modules': { 'angular2': { 'core.d.ts': `