diff --git a/modules/@angular/compiler-cli/integrationtest/src/features.ts b/modules/@angular/compiler-cli/integrationtest/src/features.ts
index 497887047e..1a4e72a39a 100644
--- a/modules/@angular/compiler-cli/integrationtest/src/features.ts
+++ b/modules/@angular/compiler-cli/integrationtest/src/features.ts
@@ -1,6 +1,8 @@
import * as common from '@angular/common';
import {Component, Inject, OpaqueToken} from '@angular/core';
+import {wrapInArray} from './funcs';
+
export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken');
@Component({
@@ -23,7 +25,7 @@ export class CompWithProviders {
{{a.value}}
{{a.value}}
`,
- directives: [common.NgIf]
+ directives: [wrapInArray(common.NgIf)]
})
export class CompWithReferences {
}
diff --git a/modules/@angular/compiler-cli/integrationtest/src/funcs.ts b/modules/@angular/compiler-cli/integrationtest/src/funcs.ts
new file mode 100644
index 0000000000..fb2e47d600
--- /dev/null
+++ b/modules/@angular/compiler-cli/integrationtest/src/funcs.ts
@@ -0,0 +1,3 @@
+export function wrapInArray(value: any): any[] {
+ return [value];
+}
\ No newline at end of file
diff --git a/modules/@angular/compiler-cli/src/reflector_host.ts b/modules/@angular/compiler-cli/src/reflector_host.ts
index f19d0cbb0e..c112be8523 100644
--- a/modules/@angular/compiler-cli/src/reflector_host.ts
+++ b/modules/@angular/compiler-cli/src/reflector_host.ts
@@ -29,6 +29,7 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/decorators',
diMetadata: '@angular/core/src/di/metadata',
+ diOpaqueToken: '@angular/core/src/di/opaque_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
diff --git a/modules/@angular/compiler-cli/src/static_reflector.ts b/modules/@angular/compiler-cli/src/static_reflector.ts
index ab2ac7cbd6..f8e0fb55c5 100644
--- a/modules/@angular/compiler-cli/src/static_reflector.ts
+++ b/modules/@angular/compiler-cli/src/static_reflector.ts
@@ -32,6 +32,7 @@ export interface StaticReflectorHost {
coreDecorators: string,
diDecorators: string,
diMetadata: string,
+ diOpaqueToken: string,
animationMetadata: string,
provider: string
};
@@ -56,6 +57,7 @@ export class StaticReflector implements ReflectorReader {
private parameterCache = new Map();
private metadataCache = new Map();
private conversionMap = new Map any>();
+ private opaqueToken: StaticSymbol;
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
@@ -177,8 +179,9 @@ export class StaticReflector implements ReflectorReader {
}
private initializeConversionMap(): void {
- const {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} =
+ const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
this.host.angularImportLocations();
+ this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken');
this.registerDecoratorOrConstructor(this.host.findDeclaration(provider, 'Provider'), Provider);
this.registerDecoratorOrConstructor(
@@ -245,147 +248,231 @@ export class StaticReflector implements ReflectorReader {
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
let _this = this;
+ let scope = BindingScope.empty;
+ let calling = new Map();
- function simplify(expression: any): any {
- if (isPrimitive(expression)) {
- return expression;
- }
- if (expression instanceof Array) {
- let result: any[] = [];
- for (let item of (expression)) {
- result.push(simplify(item));
+ function simplifyInContext(context: StaticSymbol, value: any): any {
+ function resolveReference(expression: any): StaticSymbol {
+ let staticSymbol: StaticSymbol;
+ if (expression['module']) {
+ staticSymbol = _this.host.findDeclaration(
+ expression['module'], expression['name'], context.filePath);
+ } else {
+ staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
}
- return result;
+ return staticSymbol;
}
- if (expression) {
- if (expression['__symbolic']) {
- let staticSymbol: StaticSymbol;
- switch (expression['__symbolic']) {
- case 'binop':
- let left = simplify(expression['left']);
- let right = simplify(expression['right']);
- switch (expression['operator']) {
- case '&&':
- return left && right;
- case '||':
- return left || right;
- case '|':
- return left | right;
- case '^':
- return left ^ right;
- case '&':
- return left & right;
- case '==':
- return left == right;
- case '!=':
- return left != right;
- case '===':
- return left === right;
- case '!==':
- return left !== right;
- case '<':
- return left < right;
- case '>':
- return left > right;
- case '<=':
- return left <= right;
- case '>=':
- return left >= right;
- case '<<':
- return left << right;
- case '>>':
- return left >> right;
- case '+':
- return left + right;
- case '-':
- return left - right;
- case '*':
- return left * right;
- case '/':
- return left / right;
- case '%':
- return left % right;
+
+ function isOpaqueToken(value: any): boolean {
+ if (value && value.__symbolic === 'new' && value.expression) {
+ let target = value.expression;
+ if (target.__symbolic == 'reference') {
+ return sameSymbol(resolveReference(target), _this.opaqueToken);
+ }
+ }
+ return false;
+ }
+
+ function simplifyCall(expression: any) {
+ if (expression['__symbolic'] == 'call') {
+ let target = expression['expression'];
+ let targetFunction = simplify(target);
+ if (targetFunction['__symbolic'] == 'function') {
+ if (calling.get(targetFunction)) {
+ throw new Error('Recursion not supported');
+ }
+ calling.set(targetFunction, true);
+ let value = targetFunction['value'];
+ if (value) {
+ // Determine the arguments
+ let args = (expression['arguments'] || []).map((arg: any) => simplify(arg));
+ let parameters: string[] = targetFunction['parameters'];
+ let functionScope = BindingScope.build();
+ for (let i = 0; i < parameters.length; i++) {
+ functionScope.define(parameters[i], args[i]);
}
- return null;
- case 'pre':
- let operand = simplify(expression['operand']);
- switch (expression['operator']) {
- case '+':
- return operand;
- case '-':
- return -operand;
- case '!':
- return !operand;
- case '~':
- return ~operand;
- }
- return null;
- case 'index':
- let indexTarget = simplify(expression['expression']);
- let index = simplify(expression['index']);
- if (indexTarget && isPrimitive(index)) return indexTarget[index];
- return null;
- case 'select':
- let selectTarget = simplify(expression['expression']);
- let member = simplify(expression['member']);
- if (selectTarget && isPrimitive(member)) return selectTarget[member];
- return null;
- case 'reference':
- if (expression['module']) {
- staticSymbol = _this.host.findDeclaration(
- expression['module'], expression['name'], context.filePath);
- } else {
- staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
- }
- let result = staticSymbol;
- let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
- let declarationValue =
- moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
- if (declarationValue) {
- result = _this.simplify(staticSymbol, declarationValue);
+ let oldScope = scope;
+ let result: any;
+ try {
+ scope = functionScope.done();
+ result = simplify(value);
+ } finally {
+ scope = oldScope;
}
return result;
- case 'class':
- return context;
- case 'new':
- case 'call':
- let target = expression['expression'];
- if (target['module']) {
- staticSymbol =
- _this.host.findDeclaration(target['module'], target['name'], context.filePath);
- } else {
- staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
- }
- let converter = _this.conversionMap.get(staticSymbol);
- if (converter) {
- let args = expression['arguments'];
- if (!args) {
- args = [];
- }
- return converter(context, args);
- } else {
- return context;
- }
- case 'error':
- let message = produceErrorMessage(expression);
- if (expression['line']) {
- message =
- `${message} (position ${expression['line']}:${expression['character']} in the original .ts file)`;
- }
- throw new Error(message);
+ }
+ calling.delete(targetFunction);
}
- return null;
}
- return mapStringMap(expression, (value, name) => simplify(value));
+
+ return simplify({__symbolic: 'error', message: 'Function call not supported'});
+ }
+
+ function simplify(expression: any): any {
+ if (isPrimitive(expression)) {
+ return expression;
+ }
+ if (expression instanceof Array) {
+ let result: any[] = [];
+ for (let item of (expression)) {
+ // Check for a spread expression
+ if (item && item.__symbolic === 'spread') {
+ let spreadArray = simplify(item.expression);
+ if (Array.isArray(spreadArray)) {
+ for (let spreadItem of spreadArray) {
+ result.push(spreadItem);
+ }
+ continue;
+ }
+ }
+ result.push(simplify(item));
+ }
+ return result;
+ }
+ if (expression) {
+ if (expression['__symbolic']) {
+ let staticSymbol: StaticSymbol;
+ switch (expression['__symbolic']) {
+ case 'binop':
+ let left = simplify(expression['left']);
+ let right = simplify(expression['right']);
+ switch (expression['operator']) {
+ case '&&':
+ return left && right;
+ case '||':
+ return left || right;
+ case '|':
+ return left | right;
+ case '^':
+ return left ^ right;
+ case '&':
+ return left & right;
+ case '==':
+ return left == right;
+ case '!=':
+ return left != right;
+ case '===':
+ return left === right;
+ case '!==':
+ return left !== right;
+ case '<':
+ return left < right;
+ case '>':
+ return left > right;
+ case '<=':
+ return left <= right;
+ case '>=':
+ return left >= right;
+ case '<<':
+ return left << right;
+ case '>>':
+ return left >> right;
+ case '+':
+ return left + right;
+ case '-':
+ return left - right;
+ case '*':
+ return left * right;
+ case '/':
+ return left / right;
+ case '%':
+ return left % right;
+ }
+ return null;
+ case 'pre':
+ let operand = simplify(expression['operand']);
+ switch (expression['operator']) {
+ case '+':
+ return operand;
+ case '-':
+ return -operand;
+ case '!':
+ return !operand;
+ case '~':
+ return ~operand;
+ }
+ return null;
+ case 'index':
+ let indexTarget = simplify(expression['expression']);
+ let index = simplify(expression['index']);
+ if (indexTarget && isPrimitive(index)) return indexTarget[index];
+ return null;
+ case 'select':
+ let selectTarget = simplify(expression['expression']);
+ let member = simplify(expression['member']);
+ if (selectTarget && isPrimitive(member)) return selectTarget[member];
+ return null;
+ case 'reference':
+ if (!expression.module) {
+ let name: string = expression['name'];
+ let localValue = scope.resolve(name);
+ if (localValue != BindingScope.missing) {
+ return localValue;
+ }
+ }
+ staticSymbol = resolveReference(expression);
+ let result: any = staticSymbol;
+ let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
+ let declarationValue =
+ moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
+ if (declarationValue) {
+ if (isOpaqueToken(declarationValue)) {
+ // If the referenced symbol is initalized by a new OpaqueToken we can keep the
+ // reference to the symbol.
+ return staticSymbol;
+ }
+ result = simplifyInContext(staticSymbol, declarationValue);
+ }
+ return result;
+ case 'class':
+ return context;
+ case 'function':
+ return expression;
+ case 'new':
+ case 'call':
+ // Determine if the function is a built-in conversion
+ let target = expression['expression'];
+ if (target['module']) {
+ staticSymbol = _this.host.findDeclaration(
+ target['module'], target['name'], context.filePath);
+ } else {
+ staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
+ }
+ let converter = _this.conversionMap.get(staticSymbol);
+ if (converter) {
+ let args = expression['arguments'];
+ if (!args) {
+ args = [];
+ }
+ return converter(context, args);
+ }
+
+ // Determine if the function is one we can simplify.
+ return simplifyCall(expression);
+
+ case 'error':
+ let message = produceErrorMessage(expression);
+ if (expression['line']) {
+ message =
+ `${message} (position ${expression['line']}:${expression['character']} in the original .ts file)`;
+ }
+ throw new Error(message);
+ }
+ return null;
+ }
+ return mapStringMap(expression, (value, name) => simplify(value));
+ }
+ return null;
+ }
+
+ try {
+ return simplify(value);
+ } catch (e) {
+ throw new Error(`${e.message}, resolving symbol ${context.name} in ${context.filePath}`);
}
- return null;
}
- try {
- return simplify(value);
- } catch (e) {
- throw new Error(`${e.message}, resolving symbol ${context.name} in ${context.filePath}`);
- }
+ return simplifyInContext(context, value);
}
/**
@@ -460,3 +547,40 @@ function mapStringMap(input: {[key: string]: any}, transform: (value: any, key:
function isPrimitive(o: any): boolean {
return o === null || (typeof o !== 'function' && typeof o !== 'object');
}
+
+interface BindingScopeBuilder {
+ define(name: string, value: any): BindingScopeBuilder;
+ done(): BindingScope;
+}
+
+abstract class BindingScope {
+ abstract resolve(name: string): any;
+ public static missing = {};
+ public static empty: BindingScope = {resolve: name => BindingScope.missing};
+
+ public static build(): BindingScopeBuilder {
+ let current = new Map();
+ let parent: BindingScope = undefined;
+ return {
+ define: function(name, value) {
+ current.set(name, value);
+ return this;
+ },
+ done: function() {
+ return current.size > 0 ? new PopulatedScope(current) : BindingScope.empty;
+ }
+ };
+ }
+}
+
+class PopulatedScope extends BindingScope {
+ constructor(private bindings: Map) { super(); }
+
+ resolve(name: string): any {
+ return this.bindings.has(name) ? this.bindings.get(name) : BindingScope.missing;
+ }
+}
+
+function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
+ return a === b || (a.name == b.name && a.filePath == b.filePath);
+}
diff --git a/modules/@angular/compiler-cli/test/static_reflector_spec.ts b/modules/@angular/compiler-cli/test/static_reflector_spec.ts
index 464cc1927a..f791a1f2ee 100644
--- a/modules/@angular/compiler-cli/test/static_reflector_spec.ts
+++ b/modules/@angular/compiler-cli/test/static_reflector_spec.ts
@@ -293,6 +293,41 @@ describe('StaticReflector', () => {
({__symbolic: 'reference', module: './extern', name: 'nonExisting'})))
.toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
});
+
+ it('should simplify values initialized with a function call', () => {
+ expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), {
+ __symbolic: 'reference',
+ name: 'one'
+ })).toEqual(['some-value']);
+ expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), {
+ __symbolic: 'reference',
+ name: 'two'
+ })).toEqual(2);
+ });
+
+ it('should error on direct recursive calls', () => {
+ expect(
+ () => simplify(
+ new StaticSymbol('/tmp/src/function-reference.ts', ''),
+ {__symbolic: 'reference', name: 'recursion'}))
+ .toThrow(new Error(
+ 'Recursion not supported, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
+ });
+
+ it('should error on indirect recursive calls', () => {
+ expect(
+ () => simplify(
+ new StaticSymbol('/tmp/src/function-reference.ts', ''),
+ {__symbolic: 'reference', name: 'indirectRecursion'}))
+ .toThrow(new Error(
+ 'Recursion not supported, resolving symbol indirectRecursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
+ });
+ it('should simplify a spread expression', () => {
+ expect(simplify(new StaticSymbol('/tmp/src/spread.ts', ''), {
+ __symbolic: 'reference',
+ name: 'spread'
+ })).toEqual([0, 1, 2, 3, 4, 5]);
+ });
});
class MockReflectorHost implements StaticReflectorHost {
@@ -303,6 +338,7 @@ class MockReflectorHost implements StaticReflectorHost {
coreDecorators: 'angular2/src/core/metadata',
diDecorators: 'angular2/src/core/di/decorators',
diMetadata: 'angular2/src/core/di/metadata',
+ diOpaqueToken: 'angular2/src/core/di/opaque_token',
animationMetadata: 'angular2/src/core/animation/metadata',
provider: 'angular2/src/core/di/provider'
};
@@ -624,6 +660,138 @@ class MockReflectorHost implements StaticReflectorHost {
character: 33
}
}
+ },
+ '/tmp/src/function-declaration.d.ts': {
+ __symbolic: 'module',
+ version: 1,
+ metadata: {
+ one: {
+ __symbolic: 'function',
+ parameters: ['a'],
+ value: [
+ {__symbolic: 'reference', name: 'a'}
+ ]
+ },
+ add: {
+ __symbolic: 'function',
+ parameters: ['a','b'],
+ value: {
+ __symbolic: 'binop',
+ operator: '+',
+ left: {__symbolic: 'reference', name: 'a'},
+ right: {__symbolic: 'reference', name: 'b'}
+ }
+ }
+ }
+ },
+ '/tmp/src/function-reference.ts': {
+ __symbolic: 'module',
+ version: 1,
+ metadata: {
+ one: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-declaration',
+ name: 'one'
+ },
+ arguments: ['some-value']
+ },
+ two: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-declaration',
+ name: 'add'
+ },
+ arguments: [1, 1]
+ },
+ recursion: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-recursive',
+ name: 'recursive'
+ },
+ arguments: [1]
+ },
+ indirectRecursion: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-recursive',
+ name: 'indirectRecursion1'
+ },
+ arguments: [1]
+ }
+ }
+ },
+ '/tmp/src/function-recursive.d.ts': {
+ __symbolic: 'modules',
+ version: 1,
+ metadata: {
+ recursive: {
+ __symbolic: 'function',
+ parameters: ['a'],
+ value: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-recursive',
+ name: 'recursive',
+ },
+ arguments: [
+ {
+ __symbolic: 'reference',
+ name: 'a'
+ }
+ ]
+ }
+ },
+ indirectRecursion1: {
+ __symbolic: 'function',
+ parameters: ['a'],
+ value: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-recursive',
+ name: 'indirectRecursion2',
+ },
+ arguments: [
+ {
+ __symbolic: 'reference',
+ name: 'a'
+ }
+ ]
+ }
+ },
+ indirectRecursion2: {
+ __symbolic: 'function',
+ parameters: ['a'],
+ value: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'reference',
+ module: './function-recursive',
+ name: 'indirectRecursion1',
+ },
+ arguments: [
+ {
+ __symbolic: 'reference',
+ name: 'a'
+ }
+ ]
+ }
+ }
+ },
+ },
+ '/tmp/src/spread.ts': {
+ __symbolic: 'module',
+ version: 1,
+ metadata: {
+ spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
+ }
}
};
return data[moduleId];
diff --git a/tools/@angular/tsc-wrapped/src/collector.ts b/tools/@angular/tsc-wrapped/src/collector.ts
index 9f83117bec..0506d68e58 100644
--- a/tools/@angular/tsc-wrapped/src/collector.ts
+++ b/tools/@angular/tsc-wrapped/src/collector.ts
@@ -152,6 +152,30 @@ export class MetadataCollector {
}
// Otherwise don't record metadata for the class.
break;
+ case ts.SyntaxKind.FunctionDeclaration:
+ // Record functions that return a single value. Record the parameter
+ // names substitution will be performed by the StaticReflector.
+ if (node.flags & ts.NodeFlags.Export) {
+ const functionDeclaration = node;
+ const functionName = functionDeclaration.name.text;
+ const functionBody = functionDeclaration.body;
+ if (functionBody && functionBody.statements.length == 1) {
+ const statement = functionBody.statements[0];
+ if (statement.kind === ts.SyntaxKind.ReturnStatement) {
+ const returnStatement = statement;
+ if (returnStatement.expression) {
+ if (!metadata) metadata = {};
+ metadata[functionName] = {
+ __symbolic: 'function',
+ parameters: namesOf(functionDeclaration.parameters),
+ value: evaluator.evaluateNode(returnStatement.expression)
+ };
+ }
+ }
+ }
+ }
+ // Otherwise don't record the function.
+ break;
case ts.SyntaxKind.VariableStatement:
const variableStatement = node;
for (let variableDeclaration of variableStatement.declarationList.declarations) {
@@ -209,3 +233,26 @@ export class MetadataCollector {
return metadata && {__symbolic: 'module', version: VERSION, metadata};
}
}
+
+// Collect parameter names from a function.
+function namesOf(parameters: ts.NodeArray): string[] {
+ let result: string[] = [];
+
+ function addNamesOf(name: ts.Identifier | ts.BindingPattern) {
+ if (name.kind == ts.SyntaxKind.Identifier) {
+ const identifier = name;
+ result.push(identifier.text);
+ } else {
+ const bindingPattern = name;
+ for (let element of bindingPattern.elements) {
+ addNamesOf(element.name);
+ }
+ }
+ }
+
+ for (let parameter of parameters) {
+ addNamesOf(parameter.name);
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/tools/@angular/tsc-wrapped/src/evaluator.ts b/tools/@angular/tsc-wrapped/src/evaluator.ts
index e6cdf29276..e5aabb6de5 100644
--- a/tools/@angular/tsc-wrapped/src/evaluator.ts
+++ b/tools/@angular/tsc-wrapped/src/evaluator.ts
@@ -1,6 +1,6 @@
import * as ts from 'typescript';
-import {MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression} from './schema';
+import {MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
@@ -187,7 +187,7 @@ export class Evaluator {
case ts.SyntaxKind.Identifier:
let identifier = node;
let reference = this.symbols.resolve(identifier.text);
- if (isPrimitive(reference)) {
+ if (reference !== undefined && isPrimitive(reference)) {
return true;
}
break;
@@ -207,14 +207,17 @@ export class Evaluator {
let obj: {[name: string]: any} = {};
ts.forEachChild(node, child => {
switch (child.kind) {
+ case ts.SyntaxKind.ShorthandPropertyAssignment:
case ts.SyntaxKind.PropertyAssignment:
- const assignment = child;
+ const assignment = child;
const propertyName = this.nameOf(assignment.name);
if (isMetadataError(propertyName)) {
error = propertyName;
return true;
}
- const propertyValue = this.evaluateNode(assignment.initializer);
+ const propertyValue = isPropertyAssignment(assignment) ?
+ this.evaluateNode(assignment.initializer) :
+ {__symbolic: 'reference', name: propertyName};
if (isMetadataError(propertyValue)) {
error = propertyValue;
return true; // Stop the forEachChild.
@@ -229,14 +232,31 @@ export class Evaluator {
let arr: MetadataValue[] = [];
ts.forEachChild(node, child => {
const value = this.evaluateNode(child);
+
+ // Check for error
if (isMetadataError(value)) {
error = value;
return true; // Stop the forEachChild.
}
+
+ // Handle spread expressions
+ if (isMetadataSymbolicSpreadExpression(value)) {
+ if (Array.isArray(value.expression)) {
+ for (let spreadValue of value.expression) {
+ arr.push(spreadValue);
+ }
+ return;
+ }
+ }
+
arr.push(value);
});
if (error) return error;
return arr;
+ case ts.SyntaxKind.SpreadElementExpression:
+ let spread = node;
+ let spreadExpression = this.evaluateNode(spread.expression);
+ return {__symbolic: 'spread', expression: spreadExpression};
case ts.SyntaxKind.CallExpression:
const callExpression = node;
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
@@ -296,7 +316,7 @@ export class Evaluator {
if (isMetadataError(member)) {
return member;
}
- if (this.isFoldable(propertyAccessExpression.expression))
+ if (expression && this.isFoldable(propertyAccessExpression.expression))
return (expression)[member];
if (isMetadataModuleReferenceExpression(expression)) {
// A select into a module refrence and be converted into a reference to the symbol
@@ -495,3 +515,7 @@ export class Evaluator {
return errorSymbol('Expression form not supported', node);
}
}
+
+function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
+ return node.kind == ts.SyntaxKind.PropertyAssignment;
+}
\ No newline at end of file
diff --git a/tools/@angular/tsc-wrapped/src/schema.ts b/tools/@angular/tsc-wrapped/src/schema.ts
index 4bb94790e8..431727e677 100644
--- a/tools/@angular/tsc-wrapped/src/schema.ts
+++ b/tools/@angular/tsc-wrapped/src/schema.ts
@@ -61,6 +61,15 @@ export function isConstructorMetadata(value: any): value is ConstructorMetadata
return value && value.__symbolic === 'constructor';
}
+export interface FunctionMetadata {
+ __symbolic: 'function';
+ parameters: string[];
+ result: MetadataValue;
+}
+export function isFunctionMetadata(value: any): value is FunctionMetadata {
+ return value && value.__symbolic === 'function';
+}
+
export type MetadataValue = string | number | boolean | MetadataObject | MetadataArray |
MetadataSymbolicExpression | MetadataError;
@@ -69,7 +78,7 @@ export interface MetadataObject { [name: string]: MetadataValue; }
export interface MetadataArray { [name: number]: MetadataValue; }
export interface MetadataSymbolicExpression {
- __symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'
+ __symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'|'spread'
}
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
if (value) {
@@ -81,6 +90,7 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
case 'pre':
case 'reference':
case 'select':
+ case 'spread':
return true;
}
}
@@ -190,6 +200,15 @@ export function isMetadataSymbolicSelectExpression(value: any):
return value && value.__symbolic === 'select';
}
+export interface MetadataSymbolicSpreadExpression extends MetadataSymbolicExpression {
+ __symbolic: 'spread';
+ expression: MetadataValue;
+}
+export function isMetadataSymbolicSpreadExpression(value: any):
+ value is MetadataSymbolicSpreadExpression {
+ return value && value.__symbolic === 'spread';
+}
+
export interface MetadataError {
__symbolic: 'error';
diff --git a/tools/@angular/tsc-wrapped/test/collector.spec.ts b/tools/@angular/tsc-wrapped/test/collector.spec.ts
index 5a32785922..4e76fd0113 100644
--- a/tools/@angular/tsc-wrapped/test/collector.spec.ts
+++ b/tools/@angular/tsc-wrapped/test/collector.spec.ts
@@ -6,6 +6,7 @@ import {ClassMetadata, ConstructorMetadata, ModuleMetadata} from '../src/schema'
import {Directory, Host, expectValidSources} from './typescript.mocks';
describe('Collector', () => {
+ let documentRegistry = ts.createDocumentRegistry();
let host: ts.LanguageServiceHost;
let service: ts.LanguageService;
let program: ts.Program;
@@ -14,9 +15,9 @@ describe('Collector', () => {
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', 'import-star.ts'
+ '/unsupported-1.ts', '/unsupported-2.ts', 'import-star.ts', 'exported-functions.ts'
]);
- service = ts.createLanguageService(host);
+ service = ts.createLanguageService(host, documentRegistry);
program = service.getProgram();
collector = new MetadataCollector();
});
@@ -246,6 +247,75 @@ describe('Collector', () => {
{__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
]);
});
+
+ it('should be able to record functions', () => {
+ let exportedFunctions = program.getSourceFile('/exported-functions.ts');
+ let metadata = collector.getMetadata(exportedFunctions);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 1,
+ 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'
+ }
+ }
+ }
+ }
+ }
+ });
+ });
+
+ it('should be able to handle import star type references', () => {
+ let importStar = program.getSourceFile('/import-star.ts');
+ let metadata = collector.getMetadata(importStar);
+ let someClass = metadata.metadata['SomeClass'];
+ let ctor = someClass.members['__ctor__'][0];
+ let parameters = ctor.parameters;
+ expect(parameters).toEqual([
+ {__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
+ ]);
+ });
});
// TODO: Do not use \` in a template literal as it confuses clang-format
@@ -447,9 +517,9 @@ const FILES: Directory = {
`,
'unsupported-2.ts': `
import {Injectable} from 'angular2/core';
-
+
class Foo {}
-
+
@Injectable()
export class Bar {
constructor(private f: Foo) {}
@@ -464,6 +534,20 @@ const FILES: Directory = {
constructor(private f: common.NgFor) {}
}
`,
+ '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;
+ }
+ `,
'node_modules': {
'angular2': {
'core.d.ts': `
diff --git a/tools/@angular/tsc-wrapped/test/evaluator.spec.ts b/tools/@angular/tsc-wrapped/test/evaluator.spec.ts
index f47841bf1f..88b48c6127 100644
--- a/tools/@angular/tsc-wrapped/test/evaluator.spec.ts
+++ b/tools/@angular/tsc-wrapped/test/evaluator.spec.ts
@@ -48,8 +48,14 @@ describe('Evaluator', () => {
it('should be able to fold expressions with foldable references', () => {
var expressions = program.getSourceFile('expressions.ts');
+ symbols.define('someName', 'some-name');
+ symbols.define('someBool', true);
+ symbols.define('one', 1);
+ symbols.define('two', 2);
expect(evaluator.isFoldable(findVar(expressions, 'three').initializer)).toBeTruthy();
expect(evaluator.isFoldable(findVar(expressions, 'four').initializer)).toBeTruthy();
+ symbols.define('three', 3);
+ symbols.define('four', 4);
expect(evaluator.isFoldable(findVar(expressions, 'obj').initializer)).toBeTruthy();
expect(evaluator.isFoldable(findVar(expressions, 'arr').initializer)).toBeTruthy();
});
@@ -183,6 +189,21 @@ describe('Evaluator', () => {
character: 11
});
});
+
+ it('should be able to fold an array spread', () => {
+ let expressions = program.getSourceFile('expressions.ts');
+ symbols.define('arr', [1, 2, 3, 4]);
+ let arrSpread = findVar(expressions, 'arrSpread');
+ expect(evaluator.evaluateNode(arrSpread.initializer)).toEqual([0, 1, 2, 3, 4, 5]);
+ });
+
+ it('should be able to produce a spread expression', () => {
+ let expressions = program.getSourceFile('expressions.ts');
+ let arrSpreadRef = findVar(expressions, 'arrSpreadRef');
+ expect(evaluator.evaluateNode(arrSpreadRef.initializer)).toEqual([
+ 0, {__symbolic: 'spread', expression: {__symbolic: 'reference', name: 'arrImport'}}, 5
+ ]);
+ });
});
const FILES: Directory = {
@@ -201,8 +222,11 @@ const FILES: Directory = {
export var someBool = true;
export var one = 1;
export var two = 2;
+ export var arrImport = [1, 2, 3, 4];
`,
'expressions.ts': `
+ import {arrImport} from './consts';
+
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
@@ -236,6 +260,10 @@ const FILES: Directory = {
export var bShiftRight = -1 >> 2; // -1
export var bShiftRightU = -1 >>> 2; // 0x3fffffff
+ export var arrSpread = [0, ...arr, 5];
+
+ export var arrSpreadRef = [0, ...arrImport, 5];
+
export var recursiveA = recursiveB;
export var recursiveB = recursiveA;
`,