fix(compiler): Added support for '* as m' style imports. (#9077)

Also includes fixes for broken test.
This commit is contained in:
Chuck Jazdzewski 2016-06-09 13:23:29 -07:00
parent 9f506cd330
commit e178ee4ba0
5 changed files with 83 additions and 72 deletions

View File

@ -1,5 +1,5 @@
import {Component, Inject, OpaqueToken} from '@angular/core'; import {Component, Inject, OpaqueToken} from '@angular/core';
import {NgIf} from '@angular/common'; import * as common from '@angular/common';
export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken'); export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken');
@ -9,7 +9,7 @@ export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken');
providers: [ providers: [
{provide: 'strToken', useValue: 'strValue'}, {provide: 'strToken', useValue: 'strValue'},
{provide: SOME_OPAQUE_TOKEN, useValue: 10}, {provide: SOME_OPAQUE_TOKEN, useValue: 10},
{provide: 'reference', useValue: NgIf}, {provide: 'reference', useValue: common.NgIf},
{provide: 'complexToken', useValue: {a: 1, b: ['test', SOME_OPAQUE_TOKEN]}}, {provide: 'complexToken', useValue: {a: 1, b: ['test', SOME_OPAQUE_TOKEN]}},
] ]
}) })
@ -23,7 +23,7 @@ export class CompWithProviders {
<input #a>{{a.value}} <input #a>{{a.value}}
<div *ngIf="true">{{a.value}}</div> <div *ngIf="true">{{a.value}}</div>
`, `,
directives: [NgIf] directives: [common.NgIf]
}) })
export class CompWithReferences { export class CompWithReferences {
} }

View File

@ -1,6 +1,6 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {MetadataValue, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataError, isMetadataError, isMetadataModuleReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataGlobalReferenceExpression,} from './schema'; import {MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression} from './schema';
import {Symbols} from './symbols'; import {Symbols} from './symbols';
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean { function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
@ -66,14 +66,21 @@ function getSourceFileOfNode(node: ts.Node): ts.SourceFile {
export function errorSymbol( export function errorSymbol(
message: string, node?: ts.Node, context?: {[name: string]: string}, message: string, node?: ts.Node, context?: {[name: string]: string},
sourceFile?: ts.SourceFile): MetadataError { sourceFile?: ts.SourceFile): MetadataError {
let result: MetadataError;
if (node) { if (node) {
sourceFile = sourceFile || getSourceFileOfNode(node); sourceFile = sourceFile || getSourceFileOfNode(node);
if (sourceFile) { if (sourceFile) {
let {line, character} = ts.getLineAndCharacterOfPosition(sourceFile, node.pos); let {line, character} = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
return {__symbolic: 'error', message, line, character, context}; result = {__symbolic: 'error', message, line, character};
}; };
} }
return {__symbolic: 'error', message, context}; if (!result) {
result = {__symbolic: 'error', message};
}
if (context) {
result.context = context;
}
return result;
} }
/** /**
@ -325,27 +332,36 @@ export class Evaluator {
case ts.SyntaxKind.TypeReference: case ts.SyntaxKind.TypeReference:
const typeReferenceNode = <ts.TypeReferenceNode>node; const typeReferenceNode = <ts.TypeReferenceNode>node;
const typeNameNode = typeReferenceNode.typeName; const typeNameNode = typeReferenceNode.typeName;
if (typeNameNode.kind != ts.SyntaxKind.Identifier) { const getReference: (typeNameNode: ts.Identifier | ts.QualifiedName) =>
return errorSymbol('Qualified type names not supported', node); MetadataSymbolicReferenceExpression | MetadataError = node => {
} if (typeNameNode.kind === ts.SyntaxKind.QualifiedName) {
const typeNameIdentifier = <ts.Identifier>typeReferenceNode.typeName; const qualifiedName = <ts.QualifiedName>node;
const typeName = typeNameIdentifier.text; const left = this.evaluateNode(qualifiedName.left);
const typeReference = this.symbols.resolve(typeName); if (isMetadataModuleReferenceExpression(left)) {
if (!typeReference) { return <MetadataImportedSymbolReferenceExpression> {
return errorSymbol('Could not resolve type', node, {typeName}); __symbolic: 'reference', module: left.module, name: qualifiedName.right.text
} }
if (typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) { }
const args = typeReferenceNode.typeArguments.map(element => this.evaluateNode(element)); return errorSymbol('Qualified type names not supported', node);
if (isMetadataImportedSymbolReferenceExpression(typeReference)) { } else {
return { const identifier = <ts.Identifier>typeNameNode;
__symbolic: 'reference', let symbol = this.symbols.resolve(identifier.text);
module: typeReference.module, if (isMetadataError(symbol) || isMetadataSymbolicReferenceExpression(symbol)) {
name: typeReference.name, return symbol;
arguments: args }
return errorSymbol('Could not resolve type', node, {typeName: identifier.text});
}
}; };
} else if (isMetadataGlobalReferenceExpression(typeReference)) { const typeReference = getReference(typeNameNode);
return {__symbolic: 'reference', name: typeReference.name, arguments: args}; if (isMetadataError(typeReference)) {
} return typeReference;
}
if (!isMetadataModuleReferenceExpression(typeReference) &&
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
const args = typeReferenceNode.typeArguments.map(element => this.evaluateNode(element));
// TODO: Remove typecast when upgraded to 2.0 as it will be corretly inferred.
// Some versions of 1.9 do not infer this correctly.
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
} }
return typeReference; return typeReference;
case ts.SyntaxKind.NoSubstitutionTemplateLiteral: case ts.SyntaxKind.NoSubstitutionTemplateLiteral:

View File

@ -165,6 +165,7 @@ export interface MetadataImportedDefaultReferenceExpression extends MetadataSymb
module: string; module: string;
default: default:
boolean; boolean;
arguments?: MetadataValue[];
} }
export function isMetadataImportDefaultReference(value: any): export function isMetadataImportDefaultReference(value: any):
value is MetadataImportedDefaultReferenceExpression { value is MetadataImportedDefaultReferenceExpression {

View File

@ -14,7 +14,7 @@ describe('Collector', () => {
beforeEach(() => { beforeEach(() => {
host = new Host(FILES, [ host = new Host(FILES, [
'/app/app.component.ts', '/app/cases-data.ts', '/app/error-cases.ts', '/promise.ts', '/app/app.component.ts', '/app/cases-data.ts', '/app/error-cases.ts', '/promise.ts',
'/unsupported-1.ts', '/unsupported-2.ts' '/unsupported-1.ts', '/unsupported-2.ts', 'import-star.ts'
]); ]);
service = ts.createLanguageService(host); service = ts.createLanguageService(host);
program = service.getProgram(); program = service.getProgram();
@ -212,41 +212,16 @@ describe('Collector', () => {
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 1,
metadata: { metadata: {
a: { a: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 16},
__symbolic: 'error', b: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 18},
message: 'Destructuring declarations cannot be referenced statically', c: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 16},
line: 1, d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 18},
character: 16 e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 14}
},
b: {
__symbolic: 'error',
message: 'Destructuring declarations cannot be referenced statically',
line: 1,
character: 18
},
c: {
__symbolic: 'error',
message: 'Destructuring declarations cannot be referenced statically',
line: 2,
character: 16
},
d: {
__symbolic: 'error',
message: 'Destructuring declarations cannot be referenced statically',
line: 2,
character: 18
},
e: {
__symbolic: 'error',
message: 'Only intialized variables and constants can be referenced statically',
line: 3,
character: 14
}
} }
}); });
}); });
it('should report an error for refrences to unexpected types', () => { it('should report an error for references to unexpected types', () => {
let unsupported1 = program.getSourceFile('/unsupported-2.ts'); let unsupported1 = program.getSourceFile('/unsupported-2.ts');
let metadata = collector.getMetadata(unsupported1); let metadata = collector.getMetadata(unsupported1);
let barClass = <ClassMetadata>metadata.metadata['Bar']; let barClass = <ClassMetadata>metadata.metadata['Bar'];
@ -254,11 +229,23 @@ describe('Collector', () => {
let parameter = ctor.parameters[0]; let parameter = ctor.parameters[0];
expect(parameter).toEqual({ expect(parameter).toEqual({
__symbolic: 'error', __symbolic: 'error',
message: 'Reference to non-exported class Foo', message: 'Reference to non-exported class',
line: 1, line: 1,
character: 45 character: 45,
context: {className: 'Foo'}
}); });
}); });
it('should be able to handle import star type references', () => {
let importStar = program.getSourceFile('/import-star.ts');
let metadata = collector.getMetadata(importStar);
let someClass = <ClassMetadata>metadata.metadata['SomeClass'];
let ctor = <ConstructorMetadata>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 // TODO: Do not use \` in a template literal as it confuses clang-format
@ -468,6 +455,15 @@ const FILES: Directory = {
constructor(private f: Foo) {} constructor(private f: Foo) {}
} }
`, `,
'import-star.ts': `
import {Injectable} from 'angular2/core';
import * as common from 'angular2/common';
@Injectable()
export class SomeClass {
constructor(private f: common.NgFor) {}
}
`,
'node_modules': { 'node_modules': {
'angular2': { 'angular2': {
'core.d.ts': ` 'core.d.ts': `

View File

@ -156,31 +156,29 @@ describe('Evaluator', () => {
character: 10 character: 10
}); });
let fDecl = findVar(errors, 'f'); let fDecl = findVar(errors, 'f');
expect(evaluator.evaluateNode(fDecl.initializer)).toEqual({ expect(evaluator.evaluateNode(fDecl.initializer))
__symbolic: 'error', .toEqual(
message: {__symbolic: 'error', message: 'Function call not supported', line: 6, character: 11});
'Functions cannot be evaluated statically; consider replacing with a reference to an exported function',
line: 6,
character: 11
});
let eDecl = findVar(errors, 'e'); let eDecl = findVar(errors, 'e');
expect(evaluator.evaluateNode(eDecl.type)).toEqual({ expect(evaluator.evaluateNode(eDecl.type)).toEqual({
__symbolic: 'error', __symbolic: 'error',
message: 'Could not resolve type NotFound', message: 'Could not resolve type',
line: 7, line: 7,
character: 10 character: 10,
context: {typeName: 'NotFound'}
}); });
let sDecl = findVar(errors, 's'); let sDecl = findVar(errors, 's');
expect(evaluator.evaluateNode(sDecl.initializer)).toEqual({ expect(evaluator.evaluateNode(sDecl.initializer)).toEqual({
__symbolic: 'error', __symbolic: 'error',
message: 'Name expected a string or an identifier but received "1"', message: 'Name expected',
line: 8, line: 8,
character: 13 character: 13,
context: {received: '1'}
}); });
let tDecl = findVar(errors, 't'); let tDecl = findVar(errors, 't');
expect(evaluator.evaluateNode(tDecl.initializer)).toEqual({ expect(evaluator.evaluateNode(tDecl.initializer)).toEqual({
__symbolic: 'error', __symbolic: 'error',
message: 'Expression form not supported statically', message: 'Expression form not supported',
line: 9, line: 9,
character: 11 character: 11
}); });