Also adds auto upgrade from lower version based on the .d.ts file (e.g. from version 3 to 4). This is needed as we are now also capturing type aliases in metadata files (and we rely on this), see 6e3498ca8e6752c0d59c19c89e3211744e80f6d8.
1509 lines
49 KiB
TypeScript
1509 lines
49 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
import {MetadataCollector} from '../../src/metadata/collector';
|
|
import {ClassMetadata, ConstructorMetadata, METADATA_VERSION, MetadataEntry, ModuleMetadata, isClassMetadata, isMetadataGlobalReferenceExpression} from '../../src/metadata/schema';
|
|
|
|
import {Directory, Host, expectValidSources} from './typescript.mocks';
|
|
|
|
describe('Collector', () => {
|
|
const documentRegistry = ts.createDocumentRegistry();
|
|
let host: Host;
|
|
let service: ts.LanguageService;
|
|
let program: ts.Program;
|
|
let collector: MetadataCollector;
|
|
|
|
beforeEach(() => {
|
|
host = new Host(FILES, [
|
|
'/app/app.component.ts',
|
|
'/app/cases-data.ts',
|
|
'/app/error-cases.ts',
|
|
'/promise.ts',
|
|
'/unsupported-1.ts',
|
|
'/unsupported-2.ts',
|
|
'/unsupported-3.ts',
|
|
'class-arity.ts',
|
|
'declarations.d.ts',
|
|
'import-star.ts',
|
|
'exported-classes.ts',
|
|
'exported-functions.ts',
|
|
'exported-enum.ts',
|
|
'exported-type.ts',
|
|
'exported-consts.ts',
|
|
'local-symbol-ref.ts',
|
|
'local-function-ref.ts',
|
|
'local-symbol-ref-func.ts',
|
|
'local-symbol-ref-func-dynamic.ts',
|
|
'private-enum.ts',
|
|
're-exports.ts',
|
|
're-exports-2.ts',
|
|
'export-as.d.ts',
|
|
'static-field-reference.ts',
|
|
'static-method.ts',
|
|
'static-method-call.ts',
|
|
'static-method-with-if.ts',
|
|
'static-method-with-default.ts',
|
|
'class-inheritance.ts',
|
|
'class-inheritance-parent.ts',
|
|
'class-inheritance-declarations.d.ts',
|
|
'interface-reference.ts'
|
|
]);
|
|
service = ts.createLanguageService(host, documentRegistry);
|
|
program = service.getProgram();
|
|
collector = new MetadataCollector({quotedNames: true});
|
|
});
|
|
|
|
it('should not have errors in test data', () => { expectValidSources(service, program); });
|
|
|
|
it('should return undefined for modules that have no metadata', () => {
|
|
const sourceFile = program.getSourceFile('app/empty.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toBeUndefined();
|
|
});
|
|
|
|
it('should treat all symbols of .d.ts files as exported', () => {
|
|
const sourceFile = program.getSourceFile('declarations.d.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
DeclaredClass: {__symbolic: 'class'},
|
|
declaredFn: {__symbolic: 'function'},
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should return an interface reference for types', () => {
|
|
const sourceFile = program.getSourceFile('/exported-type.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {SomeType: {__symbolic: 'interface'}}
|
|
});
|
|
});
|
|
|
|
it('should return an interface reference for interfaces', () => {
|
|
const sourceFile = program.getSourceFile('app/hero.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {Hero: {__symbolic: 'interface'}}
|
|
});
|
|
});
|
|
|
|
it('should be able to collect a simple component\'s metadata', () => {
|
|
const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
HeroDetailComponent: {
|
|
__symbolic: 'class',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
|
arguments: [{
|
|
selector: 'my-hero-detail',
|
|
template: `
|
|
<div *ngIf="hero">
|
|
<h2>{{hero.name}} details!</h2>
|
|
<div><label>id: </label>{{hero.id}}</div>
|
|
<div>
|
|
<label>name: </label>
|
|
<input [(ngModel)]="hero.name" placeholder="name"/>
|
|
</div>
|
|
</div>
|
|
`
|
|
}]
|
|
}],
|
|
members: {
|
|
hero: [{
|
|
__symbolic: 'property',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression:
|
|
{__symbolic: 'reference', module: 'angular2/core', name: 'Input'}
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should be able to get a more complicated component\'s metadata', () => {
|
|
const sourceFile = program.getSourceFile('/app/app.component.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
AppComponent: {
|
|
__symbolic: 'class',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
|
arguments: [{
|
|
selector: 'my-app',
|
|
template: `
|
|
<h2>My Heroes</h2>
|
|
<ul class="heroes">
|
|
<li *ngFor="#hero of heroes"
|
|
(click)="onSelect(hero)"
|
|
[class.selected]="hero === selectedHero">
|
|
<span class="badge">{{hero.id | lowercase}}</span> {{hero.name | uppercase}}
|
|
</li>
|
|
</ul>
|
|
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
|
`,
|
|
directives: [
|
|
{
|
|
__symbolic: 'reference',
|
|
module: './hero-detail.component',
|
|
name: 'HeroDetailComponent',
|
|
},
|
|
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
|
|
],
|
|
providers: [{__symbolic: 'reference', module: './hero.service', default: true}],
|
|
pipes: [
|
|
{__symbolic: 'reference', module: 'angular2/common', name: 'LowerCasePipe'},
|
|
{__symbolic: 'reference', module: 'angular2/common', name: 'UpperCasePipe'}
|
|
]
|
|
}]
|
|
}],
|
|
members: {
|
|
__ctor__: [{
|
|
__symbolic: 'constructor',
|
|
parameters: [{__symbolic: 'reference', module: './hero.service', default: true}]
|
|
}],
|
|
onSelect: [{__symbolic: 'method'}],
|
|
ngOnInit: [{__symbolic: 'method'}],
|
|
getHeroes: [{__symbolic: 'method'}]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should return the values of exported variables', () => {
|
|
const sourceFile = program.getSourceFile('/app/mock-heroes.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
HEROES: [
|
|
{'id': 11, 'name': 'Mr. Nice', '$quoted$': ['id', 'name']},
|
|
{'id': 12, 'name': 'Narco', '$quoted$': ['id', 'name']},
|
|
{'id': 13, 'name': 'Bombasto', '$quoted$': ['id', 'name']},
|
|
{'id': 14, 'name': 'Celeritas', '$quoted$': ['id', 'name']},
|
|
{'id': 15, 'name': 'Magneta', '$quoted$': ['id', 'name']},
|
|
{'id': 16, 'name': 'RubberMan', '$quoted$': ['id', 'name']},
|
|
{'id': 17, 'name': 'Dynama', '$quoted$': ['id', 'name']},
|
|
{'id': 18, 'name': 'Dr IQ', '$quoted$': ['id', 'name']},
|
|
{'id': 19, 'name': 'Magma', '$quoted$': ['id', 'name']},
|
|
{'id': 20, 'name': 'Tornado', '$quoted$': ['id', 'name']}
|
|
]
|
|
}
|
|
});
|
|
});
|
|
|
|
let casesFile: ts.SourceFile;
|
|
let casesMetadata: ModuleMetadata;
|
|
|
|
beforeEach(() => {
|
|
casesFile = program.getSourceFile('/app/cases-data.ts');
|
|
casesMetadata = collector.getMetadata(casesFile) !;
|
|
});
|
|
|
|
it('should provide any reference for an any ctor parameter type', () => {
|
|
const casesAny = <ClassMetadata>casesMetadata.metadata['CaseAny'];
|
|
expect(casesAny).toBeTruthy();
|
|
const ctorData = casesAny.members !['__ctor__'];
|
|
expect(ctorData).toEqual(
|
|
[{__symbolic: 'constructor', parameters: [{__symbolic: 'reference', name: 'any'}]}]);
|
|
});
|
|
|
|
it('should record annotations on set and get declarations', () => {
|
|
const propertyData = {
|
|
name: [{
|
|
__symbolic: 'property',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Input'},
|
|
arguments: ['firstName']
|
|
}]
|
|
}]
|
|
};
|
|
const caseGetProp = <ClassMetadata>casesMetadata.metadata['GetProp'];
|
|
expect(caseGetProp.members).toEqual(propertyData);
|
|
const caseSetProp = <ClassMetadata>casesMetadata.metadata['SetProp'];
|
|
expect(caseSetProp.members).toEqual(propertyData);
|
|
const caseFullProp = <ClassMetadata>casesMetadata.metadata['FullProp'];
|
|
expect(caseFullProp.members).toEqual(propertyData);
|
|
});
|
|
|
|
it('should record references to parameterized types', () => {
|
|
const casesForIn = <ClassMetadata>casesMetadata.metadata['NgFor'];
|
|
expect(casesForIn).toEqual({
|
|
__symbolic: 'class',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Injectable'}
|
|
}],
|
|
members: {
|
|
__ctor__: [{
|
|
__symbolic: 'constructor',
|
|
parameters: [{
|
|
__symbolic: 'reference',
|
|
name: 'ClassReference',
|
|
arguments: [{__symbolic: 'reference', name: 'NgForRow'}]
|
|
}]
|
|
}]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should report errors for destructured imports', () => {
|
|
const unsupported1 = program.getSourceFile('/unsupported-1.ts');
|
|
const metadata = collector.getMetadata(unsupported1);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
a: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 16},
|
|
b: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 19},
|
|
c: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 16},
|
|
d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 19},
|
|
e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 15}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should report an error for references to unexpected types', () => {
|
|
const unsupported1 = program.getSourceFile('/unsupported-2.ts');
|
|
const metadata = collector.getMetadata(unsupported1) !;
|
|
const barClass = <ClassMetadata>metadata.metadata['Bar'];
|
|
const ctor = <ConstructorMetadata>barClass.members !['__ctor__'][0];
|
|
const parameter = ctor.parameters ![0];
|
|
expect(parameter).toEqual({
|
|
__symbolic: 'error',
|
|
message: 'Reference to non-exported class',
|
|
line: 3,
|
|
character: 4,
|
|
context: {className: 'Foo'}
|
|
});
|
|
});
|
|
|
|
it('should be able to handle import star type references', () => {
|
|
const importStar = program.getSourceFile('/import-star.ts');
|
|
const metadata = collector.getMetadata(importStar) !;
|
|
const someClass = <ClassMetadata>metadata.metadata['SomeClass'];
|
|
const ctor = <ConstructorMetadata>someClass.members !['__ctor__'][0];
|
|
const parameters = ctor.parameters;
|
|
expect(parameters).toEqual([
|
|
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
|
|
]);
|
|
});
|
|
|
|
it('should record all exported classes', () => {
|
|
const sourceFile = program.getSourceFile('/exported-classes.ts');
|
|
const metadata = collector.getMetadata(sourceFile);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
SimpleClass: {__symbolic: 'class'},
|
|
AbstractClass: {__symbolic: 'class'},
|
|
DeclaredClass: {__symbolic: 'class'}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should be able to record functions', () => {
|
|
const exportedFunctions = program.getSourceFile('/exported-functions.ts');
|
|
const metadata = collector.getMetadata(exportedFunctions);
|
|
expect(metadata).toEqual({
|
|
__symbolic: 'module',
|
|
version: METADATA_VERSION,
|
|
metadata: {
|
|
one: {
|
|
__symbolic: 'function',
|
|
parameters: ['a', 'b', 'c'],
|
|
value: {
|
|
a: {__symbolic: 'reference', name: 'a'},
|
|
b: {__symbolic: 'reference', name: 'b'},
|
|
c: {__symbolic: 'reference', name: 'c'}
|
|
}
|
|
},
|
|
two: {
|
|
__symbolic: 'function',
|
|
parameters: ['a', 'b', 'c'],
|
|
value: {
|
|
a: {__symbolic: 'reference', name: 'a'},
|
|
b: {__symbolic: 'reference', name: 'b'},
|
|
c: {__symbolic: 'reference', name: 'c'}
|
|
}
|
|
},
|
|
three: {
|
|
__symbolic: 'function',
|
|
parameters: ['a', 'b', 'c'],
|
|
value: [
|
|
{__symbolic: 'reference', name: 'a'}, {__symbolic: 'reference', name: 'b'},
|
|
{__symbolic: 'reference', name: 'c'}
|
|
]
|
|
},
|
|
supportsState: {
|
|
__symbolic: 'function',
|
|
parameters: [],
|
|
value: {
|
|
__symbolic: 'pre',
|
|
operator: '!',
|
|
operand: {
|
|
__symbolic: 'pre',
|
|
operator: '!',
|
|
operand: {
|
|
__symbolic: 'select',
|
|
expression: {
|
|
__symbolic: 'select',
|
|
expression: {__symbolic: 'reference', name: 'window'},
|
|
member: 'history'
|
|
},
|
|
member: 'pushState'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
complexFn: {__symbolic: 'function'},
|
|
declaredFn: {__symbolic: 'function'}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should be able to handle import star type references', () => {
|
|
const importStar = program.getSourceFile('/import-star.ts');
|
|
const metadata = collector.getMetadata(importStar) !;
|
|
const someClass = <ClassMetadata>metadata.metadata['SomeClass'];
|
|
const ctor = <ConstructorMetadata>someClass.members !['__ctor__'][0];
|
|
const parameters = ctor.parameters;
|
|
expect(parameters).toEqual([
|
|
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
|
|
]);
|
|
});
|
|
|
|
it('should be able to collect the value of an enum', () => {
|
|
const enumSource = program.getSourceFile('/exported-enum.ts');
|
|
const metadata = collector.getMetadata(enumSource) !;
|
|
const someEnum: any = metadata.metadata['SomeEnum'];
|
|
expect(someEnum).toEqual({A: 0, B: 1, C: 100, D: 101});
|
|
});
|
|
|
|
it('should ignore a non-export enum', () => {
|
|
const enumSource = program.getSourceFile('/private-enum.ts');
|
|
const metadata = collector.getMetadata(enumSource) !;
|
|
const publicEnum: any = metadata.metadata['PublicEnum'];
|
|
const privateEnum: any = metadata.metadata['PrivateEnum'];
|
|
expect(publicEnum).toEqual({a: 0, b: 1, c: 2});
|
|
expect(privateEnum).toBeUndefined();
|
|
});
|
|
|
|
it('should be able to collect enums initialized from consts', () => {
|
|
const enumSource = program.getSourceFile('/exported-enum.ts');
|
|
const metadata = collector.getMetadata(enumSource) !;
|
|
const complexEnum: any = metadata.metadata['ComplexEnum'];
|
|
expect(complexEnum).toEqual({
|
|
A: 0,
|
|
B: 1,
|
|
C: 30,
|
|
D: 40,
|
|
E: {__symbolic: 'reference', module: './exported-consts', name: 'constValue'}
|
|
});
|
|
});
|
|
|
|
it('should be able to collect a simple static method', () => {
|
|
const staticSource = program.getSourceFile('/static-method.ts');
|
|
const metadata = collector.getMetadata(staticSource) !;
|
|
expect(metadata).toBeDefined();
|
|
const classData = <ClassMetadata>metadata.metadata['MyModule'];
|
|
expect(classData).toBeDefined();
|
|
expect(classData.statics).toEqual({
|
|
with: {
|
|
__symbolic: 'function',
|
|
parameters: ['comp'],
|
|
value: [
|
|
{__symbolic: 'reference', name: 'MyModule'},
|
|
{provider: 'a', useValue: {__symbolic: 'reference', name: 'comp'}}
|
|
]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should be able to collect a call to a static method', () => {
|
|
const staticSource = program.getSourceFile('/static-method-call.ts');
|
|
const metadata = collector.getMetadata(staticSource) !;
|
|
expect(metadata).toBeDefined();
|
|
const classData = <ClassMetadata>metadata.metadata['Foo'];
|
|
expect(classData).toBeDefined();
|
|
expect(classData.decorators).toEqual([{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
|
arguments: [{
|
|
providers: {
|
|
__symbolic: 'call',
|
|
expression: {
|
|
__symbolic: 'select',
|
|
expression: {__symbolic: 'reference', module: './static-method', name: 'MyModule'},
|
|
member: 'with'
|
|
},
|
|
arguments: ['a']
|
|
}
|
|
}]
|
|
}]);
|
|
});
|
|
|
|
it('should be able to collect a static field', () => {
|
|
const staticSource = program.getSourceFile('/static-field.ts');
|
|
const metadata = collector.getMetadata(staticSource) !;
|
|
expect(metadata).toBeDefined();
|
|
const classData = <ClassMetadata>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', () => {
|
|
const staticSource = program.getSourceFile('/static-field-reference.ts');
|
|
const metadata = collector.getMetadata(staticSource) !;
|
|
expect(metadata).toBeDefined();
|
|
const classData = <ClassMetadata>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', name: 'MyModule'},
|
|
member: 'VALUE'
|
|
}
|
|
}]
|
|
}]
|
|
}]);
|
|
});
|
|
|
|
it('should be able to collect a method with a conditional expression', () => {
|
|
const source = program.getSourceFile('/static-method-with-if.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata).toBeDefined();
|
|
const classData = <ClassMetadata>metadata.metadata['MyModule'];
|
|
expect(classData).toBeDefined();
|
|
expect(classData.statics).toEqual({
|
|
with: {
|
|
__symbolic: 'function',
|
|
parameters: ['cond'],
|
|
value: [
|
|
{__symbolic: 'reference', name: 'MyModule'}, {
|
|
provider: 'a',
|
|
useValue: {
|
|
__symbolic: 'if',
|
|
condition: {__symbolic: 'reference', name: 'cond'},
|
|
thenExpression: '1',
|
|
elseExpression: '2'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should be able to collect a method with a default parameter', () => {
|
|
const source = program.getSourceFile('/static-method-with-default.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata).toBeDefined();
|
|
const classData = <ClassMetadata>metadata.metadata['MyModule'];
|
|
expect(classData).toBeDefined();
|
|
expect(classData.statics).toEqual({
|
|
with: {
|
|
__symbolic: 'function',
|
|
parameters: ['comp', 'foo', 'bar'],
|
|
defaults: [undefined, true, false],
|
|
value: [
|
|
{__symbolic: 'reference', name: 'MyModule'}, {
|
|
__symbolic: 'if',
|
|
condition: {__symbolic: 'reference', name: 'foo'},
|
|
thenExpression: {provider: 'a', useValue: {__symbolic: 'reference', name: 'comp'}},
|
|
elseExpression: {provider: 'b', useValue: {__symbolic: 'reference', name: 'comp'}}
|
|
},
|
|
{
|
|
__symbolic: 'if',
|
|
condition: {__symbolic: 'reference', name: 'bar'},
|
|
thenExpression: {provider: 'c', useValue: {__symbolic: 'reference', name: 'comp'}},
|
|
elseExpression: {provider: 'd', useValue: {__symbolic: 'reference', name: 'comp'}}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should be able to collect re-exported symbols', () => {
|
|
const source = program.getSourceFile('/re-exports.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.exports).toEqual([
|
|
{from: './static-field', export: ['MyModule']},
|
|
{from: './static-field-reference', export: [{name: 'Foo', as: 'OtherModule'}]},
|
|
{from: 'angular2/core'}
|
|
]);
|
|
});
|
|
|
|
it('should be able to collect a export as symbol', () => {
|
|
const source = program.getSourceFile('export-as.d.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata).toEqual({SomeFunction: {__symbolic: 'function'}});
|
|
});
|
|
|
|
it('should be able to collect exports with no module specifier', () => {
|
|
const source = program.getSourceFile('/re-exports-2.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata).toEqual({
|
|
MyClass: Object({__symbolic: 'class'}),
|
|
OtherModule: {__symbolic: 'reference', module: './static-field-reference', name: 'Foo'},
|
|
MyOtherModule: {__symbolic: 'reference', module: './static-field', name: 'MyModule'}
|
|
});
|
|
});
|
|
|
|
it('should collect an error symbol if collecting a reference to a non-exported symbol', () => {
|
|
const source = program.getSourceFile('/local-symbol-ref.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata).toEqual({
|
|
REQUIRED_VALIDATOR: {
|
|
__symbolic: 'error',
|
|
message: 'Reference to a local symbol',
|
|
line: 3,
|
|
character: 8,
|
|
context: {name: 'REQUIRED'}
|
|
},
|
|
SomeComponent: {
|
|
__symbolic: 'class',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
|
arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
|
|
}]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should collect an error symbol if collecting a reference to a non-exported function', () => {
|
|
const source = program.getSourceFile('/local-function-ref.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata).toEqual({
|
|
REQUIRED_VALIDATOR: {
|
|
__symbolic: 'error',
|
|
message: 'Reference to a non-exported function',
|
|
line: 3,
|
|
character: 13,
|
|
context: {name: 'required'}
|
|
},
|
|
SomeComponent: {
|
|
__symbolic: 'class',
|
|
decorators: [{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
|
arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
|
|
}]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should collect an error for a simple function that references a local variable', () => {
|
|
const source = program.getSourceFile('/local-symbol-ref-func.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata).toEqual({
|
|
foo: {
|
|
__symbolic: 'function',
|
|
parameters: ['index'],
|
|
value: {
|
|
__symbolic: 'error',
|
|
message: 'Reference to a local symbol',
|
|
line: 1,
|
|
character: 8,
|
|
context: {name: 'localSymbol'}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should collect any for interface parameter reference', () => {
|
|
const source = program.getSourceFile('/interface-reference.ts');
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect((metadata.metadata['SomeClass'] as ClassMetadata).members).toEqual({
|
|
__ctor__: [{
|
|
__symbolic: 'constructor',
|
|
parameterDecorators: [[{
|
|
__symbolic: 'call',
|
|
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Inject'},
|
|
arguments: ['a']
|
|
}]],
|
|
parameters: [{__symbolic: 'reference', name: 'any'}]
|
|
}]
|
|
});
|
|
});
|
|
|
|
describe('with interpolations', () => {
|
|
function e(expr: string, prefix?: string) {
|
|
const metadata = collectSource(`${prefix || ''} export let value = ${expr};`);
|
|
return expect(metadata.metadata['value']);
|
|
}
|
|
|
|
it('should be able to collect a raw interpolated string',
|
|
() => { e('`simple value`').toBe('simple value'); });
|
|
|
|
it('should be able to interpolate a single value',
|
|
() => { e('`${foo}`', 'const foo = "foo value"').toBe('foo value'); });
|
|
|
|
it('should be able to interpolate multiple values', () => {
|
|
e('`foo:${foo}, bar:${bar}, end`', 'const foo = "foo"; const bar = "bar";')
|
|
.toBe('foo:foo, bar:bar, end');
|
|
});
|
|
|
|
it('should be able to interpolate with an imported reference', () => {
|
|
e('`external:${external}`', 'import {external} from "./external";').toEqual({
|
|
__symbolic: 'binop',
|
|
operator: '+',
|
|
left: 'external:',
|
|
right: {
|
|
__symbolic: 'reference',
|
|
module: './external',
|
|
name: 'external',
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should simplify a redundant template', () => {
|
|
e('`${external}`', 'import {external} from "./external";')
|
|
.toEqual({__symbolic: 'reference', module: './external', name: 'external'});
|
|
});
|
|
|
|
it('should be able to collect complex template with imported references', () => {
|
|
e('`foo:${foo}, bar:${bar}, end`', 'import {foo, bar} from "./external";').toEqual({
|
|
__symbolic: 'binop',
|
|
operator: '+',
|
|
left: {
|
|
__symbolic: 'binop',
|
|
operator: '+',
|
|
left: {
|
|
__symbolic: 'binop',
|
|
operator: '+',
|
|
left: {
|
|
__symbolic: 'binop',
|
|
operator: '+',
|
|
left: 'foo:',
|
|
right: {__symbolic: 'reference', module: './external', name: 'foo'}
|
|
},
|
|
right: ', bar:'
|
|
},
|
|
right: {__symbolic: 'reference', module: './external', name: 'bar'}
|
|
},
|
|
right: ', end'
|
|
});
|
|
});
|
|
|
|
it('should reject a tagged literal', () => {
|
|
e('tag`some value`').toEqual({
|
|
__symbolic: 'error',
|
|
message: 'Tagged template expressions are not supported in metadata',
|
|
line: 0,
|
|
character: 20
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should ignore |null or |undefined in type expressions', () => {
|
|
const metadata = collectSource(`
|
|
import {Foo} from './foo';
|
|
export class SomeClass {
|
|
constructor (a: Foo, b: Foo | null, c: Foo | undefined, d: Foo | undefined | null, e: Foo | undefined | null | Foo) {}
|
|
}
|
|
`);
|
|
expect((metadata.metadata['SomeClass'] as ClassMetadata).members).toEqual({
|
|
__ctor__: [{
|
|
__symbolic: 'constructor',
|
|
parameters: [
|
|
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
|
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
|
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
|
{__symbolic: 'reference', module: './foo', name: 'Foo'},
|
|
{__symbolic: 'reference', module: './foo', name: 'Foo'}
|
|
]
|
|
}]
|
|
});
|
|
});
|
|
|
|
it('should treat exported class expressions as a class', () => {
|
|
const source = ts.createSourceFile(
|
|
'', `
|
|
export const InjectionToken: {new<T>(desc: string): InjectionToken<T>;} = class {
|
|
constructor(protected _desc: string) {}
|
|
|
|
toString(): string { return \`InjectionToken ${this._desc}\`; }
|
|
} as any;`,
|
|
ts.ScriptTarget.Latest, true);
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata).toEqual({InjectionToken: {__symbolic: 'class'}});
|
|
});
|
|
|
|
describe('in strict mode', () => {
|
|
it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => {
|
|
const source = program.getSourceFile('/local-symbol-ref.ts');
|
|
expect(() => collector.getMetadata(source, true)).toThrowError(/Reference to a local symbol/);
|
|
});
|
|
|
|
it('should throw if an error if collecting a reference to a non-exported function', () => {
|
|
const source = program.getSourceFile('/local-function-ref.ts');
|
|
expect(() => collector.getMetadata(source, true))
|
|
.toThrowError(/Reference to a non-exported function/);
|
|
});
|
|
|
|
it('should throw for references to unexpected types', () => {
|
|
const unsupported2 = program.getSourceFile('/unsupported-2.ts');
|
|
expect(() => collector.getMetadata(unsupported2, true))
|
|
.toThrowError(/Reference to non-exported class/);
|
|
});
|
|
|
|
it('should throw for errors in a static method', () => {
|
|
const unsupported3 = program.getSourceFile('/unsupported-3.ts');
|
|
expect(() => collector.getMetadata(unsupported3, true))
|
|
.toThrowError(/Reference to a non-exported class/);
|
|
});
|
|
});
|
|
|
|
describe('with invalid input', () => {
|
|
it('should not throw with a class with no name', () => {
|
|
const fileName = '/invalid-class.ts';
|
|
override(fileName, 'export class');
|
|
const invalidClass = program.getSourceFile(fileName);
|
|
expect(() => collector.getMetadata(invalidClass)).not.toThrow();
|
|
});
|
|
|
|
it('should not throw with a function with no name', () => {
|
|
const fileName = '/invalid-function.ts';
|
|
override(fileName, 'export function');
|
|
const invalidFunction = program.getSourceFile(fileName);
|
|
expect(() => collector.getMetadata(invalidFunction)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('inheritance', () => {
|
|
it('should record `extends` clauses for declared classes', () => {
|
|
const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts')) !;
|
|
expect(metadata.metadata['DeclaredChildClass'])
|
|
.toEqual({__symbolic: 'class', extends: {__symbolic: 'reference', name: 'ParentClass'}});
|
|
});
|
|
|
|
it('should record `extends` clauses for classes in the same file', () => {
|
|
const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts')) !;
|
|
expect(metadata.metadata['ChildClassSameFile'])
|
|
.toEqual({__symbolic: 'class', extends: {__symbolic: 'reference', name: 'ParentClass'}});
|
|
});
|
|
|
|
it('should record `extends` clauses for classes in a different file', () => {
|
|
const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts')) !;
|
|
expect(metadata.metadata['ChildClassOtherFile']).toEqual({
|
|
__symbolic: 'class',
|
|
extends: {
|
|
__symbolic: 'reference',
|
|
module: './class-inheritance-parent',
|
|
name: 'ParentClassFromOtherFile'
|
|
}
|
|
});
|
|
});
|
|
|
|
function expectClass(entry: MetadataEntry): entry is ClassMetadata {
|
|
const result = isClassMetadata(entry);
|
|
expect(result).toBeTruthy();
|
|
return result;
|
|
}
|
|
|
|
it('should collect the correct arity for a class', () => {
|
|
const metadata = collector.getMetadata(program.getSourceFile('/class-arity.ts')) !;
|
|
|
|
const zero = metadata.metadata['Zero'];
|
|
if (expectClass(zero)) expect(zero.arity).toBeUndefined();
|
|
const one = metadata.metadata['One'];
|
|
if (expectClass(one)) expect(one.arity).toBe(1);
|
|
const two = metadata.metadata['Two'];
|
|
if (expectClass(two)) expect(two.arity).toBe(2);
|
|
const three = metadata.metadata['Three'];
|
|
if (expectClass(three)) expect(three.arity).toBe(3);
|
|
const nine = metadata.metadata['Nine'];
|
|
if (expectClass(nine)) expect(nine.arity).toBe(9);
|
|
});
|
|
});
|
|
|
|
describe('regerssion', () => {
|
|
it('should be able to collect a short-hand property value', () => {
|
|
const metadata = collectSource(`
|
|
const children = { f1: 1 };
|
|
export const r = [
|
|
{path: ':locale', children}
|
|
];
|
|
`);
|
|
expect(metadata.metadata).toEqual({r: [{path: ':locale', children: {f1: 1}}]});
|
|
});
|
|
|
|
// #17518
|
|
it('should skip a default function', () => {
|
|
const metadata = collectSource(`
|
|
export default function () {
|
|
|
|
const mainRoutes = [
|
|
{name: 'a', abstract: true, component: 'main'},
|
|
|
|
{name: 'a.welcome', url: '/welcome', component: 'welcome'}
|
|
];
|
|
|
|
return mainRoutes;
|
|
|
|
}`);
|
|
expect(metadata).toBeUndefined();
|
|
});
|
|
|
|
it('should skip a named default export', () => {
|
|
const metadata = collectSource(`
|
|
function mainRoutes() {
|
|
|
|
const mainRoutes = [
|
|
{name: 'a', abstract: true, component: 'main'},
|
|
|
|
{name: 'a.welcome', url: '/welcome', component: 'welcome'}
|
|
];
|
|
|
|
return mainRoutes;
|
|
|
|
}
|
|
|
|
exports = foo;
|
|
`);
|
|
expect(metadata).toBeUndefined();
|
|
});
|
|
|
|
it('should be able to collect an invalid access expression', () => {
|
|
const source = createSource(`
|
|
import {Component} from '@angular/core';
|
|
|
|
const value = [];
|
|
@Component({
|
|
provider: [{provide: 'some token', useValue: value[]}]
|
|
})
|
|
export class MyComponent {}
|
|
`);
|
|
const metadata = collector.getMetadata(source) !;
|
|
expect(metadata.metadata.MyComponent).toEqual({
|
|
__symbolic: 'class',
|
|
decorators: [{
|
|
__symbolic: 'error',
|
|
message: 'Expression form not supported',
|
|
line: 5,
|
|
character: 55
|
|
}]
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('references', () => {
|
|
beforeEach(() => { collector = new MetadataCollector({quotedNames: true}); });
|
|
|
|
it('should record a reference to an exported field of a useValue', () => {
|
|
const metadata = collectSource(`
|
|
export var someValue = 1;
|
|
export const v = {
|
|
useValue: someValue
|
|
};
|
|
`);
|
|
expect(metadata.metadata['someValue']).toEqual(1);
|
|
expect(metadata.metadata['v']).toEqual({
|
|
useValue: {__symbolic: 'reference', name: 'someValue'}
|
|
});
|
|
});
|
|
|
|
it('should leave external references in place in an object literal', () => {
|
|
const metadata = collectSource(`
|
|
export const myLambda = () => [1, 2, 3];
|
|
const indirect = [{a: 1, b: 3: c: myLambda}];
|
|
export const v = {
|
|
v: {i: indirect}
|
|
}
|
|
`);
|
|
expect(metadata.metadata['v']).toEqual({
|
|
v: {i: [{a: 1, b: 3, c: {__symbolic: 'reference', name: 'myLambda'}}]}
|
|
});
|
|
});
|
|
|
|
it('should leave an external reference in place in an array literal', () => {
|
|
const metadata = collectSource(`
|
|
export const myLambda = () => [1, 2, 3];
|
|
const indirect = [1, 3, myLambda}];
|
|
export const v = {
|
|
v: {i: indirect}
|
|
}
|
|
`);
|
|
expect(metadata.metadata['v']).toEqual({
|
|
v: {i: [1, 3, {__symbolic: 'reference', name: 'myLambda'}]}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('substitutions', () => {
|
|
const lambdaTemp = 'lambdaTemp';
|
|
|
|
it('should be able to substitute a lambda', () => {
|
|
const source = createSource(`
|
|
const b = 1;
|
|
export const a = () => b;
|
|
`);
|
|
const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => {
|
|
if (node.kind === ts.SyntaxKind.ArrowFunction) {
|
|
return {__symbolic: 'reference', name: lambdaTemp};
|
|
}
|
|
return value;
|
|
});
|
|
expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp});
|
|
});
|
|
|
|
it('should compose substitution functions', () => {
|
|
const collector = new MetadataCollector({
|
|
substituteExpression: (value, node) => isMetadataGlobalReferenceExpression(value) &&
|
|
value.name == lambdaTemp ?
|
|
{__symbolic: 'reference', name: value.name + '2'} :
|
|
value
|
|
});
|
|
const source = createSource(`
|
|
const b = 1;
|
|
export const a = () => b;
|
|
`);
|
|
const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => {
|
|
if (node.kind === ts.SyntaxKind.ArrowFunction) {
|
|
return {__symbolic: 'reference', name: lambdaTemp};
|
|
}
|
|
return value;
|
|
});
|
|
expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp + '2'});
|
|
});
|
|
});
|
|
|
|
function override(fileName: string, content: string) {
|
|
host.overrideFile(fileName, content);
|
|
host.addFile(fileName);
|
|
program = service.getProgram();
|
|
}
|
|
|
|
function collectSource(content: string): ModuleMetadata {
|
|
const sourceFile = createSource(content);
|
|
return collector.getMetadata(sourceFile) !;
|
|
}
|
|
});
|
|
|
|
// TODO: Do not use \` in a template literal as it confuses clang-format
|
|
const FILES: Directory = {
|
|
'app': {
|
|
'app.component.ts': `
|
|
import {Component as MyComponent, OnInit} from 'angular2/core';
|
|
import * as common from 'angular2/common';
|
|
import {Hero} from './hero';
|
|
import {HeroDetailComponent} from './hero-detail.component';
|
|
import HeroService from './hero.service';
|
|
// thrown away
|
|
import 'angular2/core';
|
|
|
|
@MyComponent({
|
|
selector: 'my-app',
|
|
template:` +
|
|
'`' +
|
|
`
|
|
<h2>My Heroes</h2>
|
|
<ul class="heroes">
|
|
<li *ngFor="#hero of heroes"
|
|
(click)="onSelect(hero)"
|
|
[class.selected]="hero === selectedHero">
|
|
<span class="badge">{{hero.id | lowercase}}</span> {{hero.name | uppercase}}
|
|
</li>
|
|
</ul>
|
|
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
|
` +
|
|
'`' +
|
|
`,
|
|
directives: [HeroDetailComponent, common.NgFor],
|
|
providers: [HeroService],
|
|
pipes: [common.LowerCasePipe, common.UpperCasePipe]
|
|
})
|
|
export class AppComponent implements OnInit {
|
|
public title = 'Tour of Heroes';
|
|
public heroes: Hero[];
|
|
public selectedHero: Hero;
|
|
|
|
constructor(private _heroService: HeroService) { }
|
|
|
|
onSelect(hero: Hero) { this.selectedHero = hero; }
|
|
|
|
ngOnInit() {
|
|
this.getHeroes()
|
|
}
|
|
|
|
getHeroes() {
|
|
this._heroService.getHeroesSlowly().then(heroes => this.heroes = heroes);
|
|
}
|
|
}`,
|
|
'hero.ts': `
|
|
export interface Hero {
|
|
id: number;
|
|
name: string;
|
|
}`,
|
|
'empty.ts': ``,
|
|
'hero-detail.component.ts': `
|
|
import {Component, Input} from 'angular2/core';
|
|
import {Hero} from './hero';
|
|
|
|
@Component({
|
|
selector: 'my-hero-detail',
|
|
template: ` +
|
|
'`' +
|
|
`
|
|
<div *ngIf="hero">
|
|
<h2>{{hero.name}} details!</h2>
|
|
<div><label>id: </label>{{hero.id}}</div>
|
|
<div>
|
|
<label>name: </label>
|
|
<input [(ngModel)]="hero.name" placeholder="name"/>
|
|
</div>
|
|
</div>
|
|
` +
|
|
'`' +
|
|
`,
|
|
})
|
|
export class HeroDetailComponent {
|
|
@Input() public hero: Hero;
|
|
}`,
|
|
'mock-heroes.ts': `
|
|
import {Hero as Hero} from './hero';
|
|
|
|
export const HEROES: Hero[] = [
|
|
{"id": 11, "name": "Mr. Nice"},
|
|
{"id": 12, "name": "Narco"},
|
|
{"id": 13, "name": "Bombasto"},
|
|
{"id": 14, "name": "Celeritas"},
|
|
{"id": 15, "name": "Magneta"},
|
|
{"id": 16, "name": "RubberMan"},
|
|
{"id": 17, "name": "Dynama"},
|
|
{"id": 18, "name": "Dr IQ"},
|
|
{"id": 19, "name": "Magma"},
|
|
{"id": 20, "name": "Tornado"}
|
|
];`,
|
|
'default-exporter.ts': `
|
|
let a: string;
|
|
export default a;
|
|
`,
|
|
'hero.service.ts': `
|
|
import {Injectable} from 'angular2/core';
|
|
import {HEROES} from './mock-heroes';
|
|
import {Hero} from './hero';
|
|
|
|
@Injectable()
|
|
class HeroService {
|
|
getHeros() {
|
|
return Promise.resolve(HEROES);
|
|
}
|
|
|
|
getHeroesSlowly() {
|
|
return new Promise<Hero[]>(resolve =>
|
|
setTimeout(()=>resolve(HEROES), 2000)); // 2 seconds
|
|
}
|
|
}
|
|
export default HeroService;`,
|
|
'cases-data.ts': `
|
|
import {Injectable, Input} from 'angular2/core';
|
|
|
|
@Injectable()
|
|
export class CaseAny {
|
|
constructor(param: any) {}
|
|
}
|
|
|
|
@Injectable()
|
|
export class GetProp {
|
|
private _name: string;
|
|
@Input('firstName') get name(): string {
|
|
return this._name;
|
|
}
|
|
}
|
|
|
|
@Injectable()
|
|
export class SetProp {
|
|
private _name: string;
|
|
@Input('firstName') set name(value: string) {
|
|
this._name = value;
|
|
}
|
|
}
|
|
|
|
@Injectable()
|
|
export class FullProp {
|
|
private _name: string;
|
|
@Input('firstName') get name(): string {
|
|
return this._name;
|
|
}
|
|
set name(value: string) {
|
|
this._name = value;
|
|
}
|
|
}
|
|
|
|
export class ClassReference<T> { }
|
|
export class NgForRow {
|
|
|
|
}
|
|
|
|
@Injectable()
|
|
export class NgFor {
|
|
constructor (public ref: ClassReference<NgForRow>) {}
|
|
}
|
|
`,
|
|
'error-cases.ts': `
|
|
import HeroService from './hero.service';
|
|
|
|
export class CaseCtor {
|
|
constructor(private _heroService: HeroService) { }
|
|
}
|
|
`
|
|
},
|
|
'promise.ts': `
|
|
interface PromiseLike<T> {
|
|
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): PromiseLike<TResult>;
|
|
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): PromiseLike<TResult>;
|
|
}
|
|
|
|
interface Promise<T> {
|
|
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): Promise<TResult>;
|
|
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
|
|
catch(onrejected?: (reason: any) => T | PromiseLike<T>): Promise<T>;
|
|
catch(onrejected?: (reason: any) => void): Promise<T>;
|
|
}
|
|
|
|
interface PromiseConstructor {
|
|
prototype: Promise<any>;
|
|
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
|
|
reject(reason: any): Promise<void>;
|
|
reject<T>(reason: any): Promise<T>;
|
|
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
|
|
resolve(): Promise<void>;
|
|
}
|
|
|
|
declare var Promise: PromiseConstructor;
|
|
`,
|
|
'class-arity.ts': `
|
|
export class Zero {}
|
|
export class One<T> {}
|
|
export class Two<T, V> {}
|
|
export class Three<T1, T2, T3> {}
|
|
export class Nine<T1, T2, T3, T4, T5, T6, T7, T8, T9> {}
|
|
`,
|
|
'unsupported-1.ts': `
|
|
export let {a, b} = {a: 1, b: 2};
|
|
export let [c, d] = [1, 2];
|
|
export let e;
|
|
`,
|
|
'unsupported-2.ts': `
|
|
import {Injectable} from 'angular2/core';
|
|
|
|
class Foo {}
|
|
|
|
@Injectable()
|
|
export class Bar {
|
|
constructor(private f: Foo) {}
|
|
}
|
|
`,
|
|
'unsupported-3.ts': `
|
|
class Foo {}
|
|
|
|
export class SomeClass {
|
|
static someStatic() {
|
|
return Foo;
|
|
}
|
|
}
|
|
`,
|
|
'interface-reference.ts': `
|
|
import {Injectable, Inject} from 'angular2/core';
|
|
export interface Test {}
|
|
|
|
@Injectable()
|
|
export class SomeClass {
|
|
constructor(@Inject("a") test: Test) {}
|
|
}
|
|
`,
|
|
'import-star.ts': `
|
|
import {Injectable} from 'angular2/core';
|
|
import * as common from 'angular2/common';
|
|
|
|
@Injectable()
|
|
export class SomeClass {
|
|
constructor(private f: common.NgFor) {}
|
|
}
|
|
`,
|
|
'declarations.d.ts': `
|
|
declare class DeclaredClass {}
|
|
declare function declaredFn();
|
|
`,
|
|
'exported-classes.ts': `
|
|
export class SimpleClass {}
|
|
export abstract class AbstractClass {}
|
|
export declare class DeclaredClass {}
|
|
`,
|
|
'class-inheritance-parent.ts': `
|
|
export class ParentClassFromOtherFile {}
|
|
`,
|
|
'class-inheritance.ts': `
|
|
import {ParentClassFromOtherFile} from './class-inheritance-parent';
|
|
|
|
export class ParentClass {}
|
|
|
|
export declare class DeclaredChildClass extends ParentClass {}
|
|
|
|
export class ChildClassSameFile extends ParentClass {}
|
|
|
|
export class ChildClassOtherFile extends ParentClassFromOtherFile {}
|
|
`,
|
|
'exported-functions.ts': `
|
|
export function one(a: string, b: string, c: string) {
|
|
return {a: a, b: b, c: c};
|
|
}
|
|
export function two(a: string, b: string, c: string) {
|
|
return {a, b, c};
|
|
}
|
|
export function three({a, b, c}: {a: string, b: string, c: string}) {
|
|
return [a, b, c];
|
|
}
|
|
export function supportsState(): boolean {
|
|
return !!window.history.pushState;
|
|
}
|
|
export function complexFn(x: any): boolean {
|
|
if (x) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
export declare function declaredFn();
|
|
`,
|
|
'exported-type.ts': `
|
|
export type SomeType = 'a' | 'b';
|
|
`,
|
|
'exported-enum.ts': `
|
|
import {constValue} from './exported-consts';
|
|
|
|
export const someValue = 30;
|
|
export enum SomeEnum { A, B, C = 100, D };
|
|
export enum ComplexEnum { A, B, C = someValue, D = someValue + 10, E = constValue };
|
|
`,
|
|
'exported-consts.ts': `
|
|
export const constValue = 100;
|
|
`,
|
|
'static-method.ts': `
|
|
export class MyModule {
|
|
static with(comp: any): any[] {
|
|
return [
|
|
MyModule,
|
|
{ provider: 'a', useValue: comp }
|
|
];
|
|
}
|
|
}
|
|
`,
|
|
'static-method-with-default.ts': `
|
|
export class MyModule {
|
|
static with(comp: any, foo: boolean = true, bar: boolean = false): any[] {
|
|
return [
|
|
MyModule,
|
|
foo ? { provider: 'a', useValue: comp } : {provider: 'b', useValue: comp},
|
|
bar ? { provider: 'c', useValue: comp } : {provider: 'd', useValue: comp}
|
|
];
|
|
}
|
|
}
|
|
`,
|
|
'static-method-call.ts': `
|
|
import {Component} from 'angular2/core';
|
|
import {MyModule} from './static-method';
|
|
|
|
@Component({
|
|
providers: MyModule.with('a')
|
|
})
|
|
export class Foo { }
|
|
`,
|
|
'static-field.ts': `
|
|
export class MyModule {
|
|
static VALUE = 'Some string';
|
|
}
|
|
`,
|
|
'static-field-reference.ts': `
|
|
import {Component} from 'angular2/core';
|
|
import {MyModule} from './static-field';
|
|
|
|
@Component({
|
|
providers: [ { provide: 'a', useValue: MyModule.VALUE } ]
|
|
})
|
|
export class Foo { }
|
|
`,
|
|
'static-method-with-if.ts': `
|
|
export class MyModule {
|
|
static with(cond: boolean): any[] {
|
|
return [
|
|
MyModule,
|
|
{ provider: 'a', useValue: cond ? '1' : '2' }
|
|
];
|
|
}
|
|
}
|
|
`,
|
|
're-exports.ts': `
|
|
export {MyModule} from './static-field';
|
|
export {Foo as OtherModule} from './static-field-reference';
|
|
export * from 'angular2/core';
|
|
`,
|
|
're-exports-2.ts': `
|
|
import {MyModule} from './static-field';
|
|
import {Foo as OtherModule} from './static-field-reference';
|
|
class MyClass {}
|
|
export {OtherModule, MyModule as MyOtherModule, MyClass};
|
|
`,
|
|
'export-as.d.ts': `
|
|
declare function someFunction(): void;
|
|
export { someFunction as SomeFunction };
|
|
`,
|
|
'local-symbol-ref.ts': `
|
|
import {Component, Validators} from 'angular2/core';
|
|
|
|
var REQUIRED;
|
|
|
|
export const REQUIRED_VALIDATOR: any = {
|
|
provide: 'SomeToken',
|
|
useValue: REQUIRED,
|
|
multi: true
|
|
};
|
|
|
|
@Component({
|
|
providers: [REQUIRED_VALIDATOR]
|
|
})
|
|
export class SomeComponent {}
|
|
`,
|
|
'private-enum.ts': `
|
|
export enum PublicEnum { a, b, c }
|
|
enum PrivateEnum { e, f, g }
|
|
`,
|
|
'local-function-ref.ts': `
|
|
import {Component, Validators} from 'angular2/core';
|
|
|
|
function required() {}
|
|
|
|
export const REQUIRED_VALIDATOR: any = {
|
|
provide: 'SomeToken',
|
|
useValue: required,
|
|
multi: true
|
|
};
|
|
|
|
@Component({
|
|
providers: [REQUIRED_VALIDATOR]
|
|
})
|
|
export class SomeComponent {}
|
|
`,
|
|
'local-symbol-ref-func.ts': `
|
|
var localSymbol: any[];
|
|
|
|
export function foo(index: number): string {
|
|
return localSymbol[index];
|
|
}
|
|
`,
|
|
'node_modules': {
|
|
'angular2': {
|
|
'core.d.ts': `
|
|
export interface Type extends Function { }
|
|
export interface TypeDecorator {
|
|
<T extends Type>(type: T): T;
|
|
(target: Object, propertyKey?: string | symbol, parameterIndex?: number): void;
|
|
annotations: any[];
|
|
}
|
|
export interface ComponentDecorator extends TypeDecorator { }
|
|
export interface ComponentFactory {
|
|
(obj: {
|
|
selector?: string;
|
|
inputs?: string[];
|
|
outputs?: string[];
|
|
properties?: string[];
|
|
events?: string[];
|
|
host?: {
|
|
[key: string]: string;
|
|
};
|
|
bindings?: any[];
|
|
providers?: any[];
|
|
exportAs?: string;
|
|
moduleId?: string;
|
|
queries?: {
|
|
[key: string]: any;
|
|
};
|
|
viewBindings?: any[];
|
|
viewProviders?: any[];
|
|
templateUrl?: string;
|
|
template?: string;
|
|
styleUrls?: string[];
|
|
styles?: string[];
|
|
directives?: Array<Type | any[]>;
|
|
pipes?: Array<Type | any[]>;
|
|
}): ComponentDecorator;
|
|
}
|
|
export declare var Component: ComponentFactory;
|
|
export interface InputFactory {
|
|
(bindingPropertyName?: string): any;
|
|
new (bindingPropertyName?: string): any;
|
|
}
|
|
export declare var Input: InputFactory;
|
|
export interface InjectableFactory {
|
|
(): any;
|
|
}
|
|
export declare var Injectable: InjectableFactory;
|
|
export interface InjectFactory {
|
|
(binding?: any): any;
|
|
new (binding?: any): any;
|
|
}
|
|
export declare var Inject: InjectFactory;
|
|
export interface OnInit {
|
|
ngOnInit(): any;
|
|
}
|
|
export class Validators {
|
|
static required(): void;
|
|
}
|
|
`,
|
|
'common.d.ts': `
|
|
export declare class NgFor {
|
|
ngForOf: any;
|
|
ngForTemplate: any;
|
|
ngDoCheck(): void;
|
|
}
|
|
export declare class LowerCasePipe {
|
|
transform(value: string, args?: any[]): string;
|
|
}
|
|
export declare class UpperCasePipe {
|
|
transform(value: string, args?: any[]): string;
|
|
}
|
|
`
|
|
}
|
|
}
|
|
};
|
|
|
|
function createSource(text: string): ts.SourceFile {
|
|
return ts.createSourceFile('', text, ts.ScriptTarget.Latest, true);
|
|
}
|