feat(compiler): generate proper reexports in .ngfactory.ts files to not need transitive deps for compiling .ngfactory.ts files. (#13524)

Note: This checks the constructors of `@Injectable` classes more strictly.
E.g this will fail now as the constructor argument has no `@Inject` nor is
the type of the argument a DI token.

```
@Injectable()
class MyService {
  constructor(dep: string) {}
}
```

Last part of #12787
Closes #12787
This commit is contained in:
Tobias Bosch
2016-12-27 09:36:47 -08:00
committed by Hans
parent 697690349f
commit 9c697030e6
37 changed files with 995 additions and 410 deletions

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import * as o from '@angular/compiler/src/output/output_ast';
@ -15,16 +16,17 @@ const someModuleUrl = 'somePackage/somePath';
const anotherModuleUrl = 'somePackage/someOtherPath';
const sameModuleIdentifier: CompileIdentifierMetadata = {
reference: {name: 'someLocalId', filePath: someModuleUrl}
reference: new StaticSymbol(someModuleUrl, 'someLocalId', [])
};
const externalModuleIdentifier: CompileIdentifierMetadata = {
reference: {name: 'someExternalId', filePath: anotherModuleUrl}
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', [])
};
class SimpleJsImportGenerator implements ImportResolver {
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
return importedUrlStr;
}
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
}
export function main() {
@ -33,11 +35,13 @@ export function main() {
// - declaring fields
describe('JavaScriptEmitter', () => {
let importResolver: ImportResolver;
let emitter: JavaScriptEmitter;
let someVar: o.ReadVarExpr;
beforeEach(() => {
emitter = new JavaScriptEmitter(new SimpleJsImportGenerator());
importResolver = new SimpleJsImportGenerator();
emitter = new JavaScriptEmitter(importResolver);
someVar = o.variable('someVar');
});
@ -124,6 +128,16 @@ export function main() {
].join('\n'));
});
it('should support `importAs` for external identifiers', () => {
spyOn(importResolver, 'getImportAs')
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt())).toEqual([
`var import0 = re` +
`quire('somePackage/importAsModule');`,
`import0.importAsName;`
].join('\n'));
});
it('should support operators', () => {
const lhs = o.variable('lhs');
const rhs = o.variable('rhs');

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import {assetUrl, createIdentifier} from '@angular/compiler/src/identifiers';
import * as o from '@angular/compiler/src/output/output_ast';
@ -262,4 +263,5 @@ export class SimpleJsImportGenerator implements ImportResolver {
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
return importedUrlStr;
}
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
}

View File

@ -74,4 +74,5 @@ class StubReflectorHost implements StaticSymbolResolverHost {
class StubImportResolver extends ImportResolver {
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { return ''; }
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import * as o from '@angular/compiler/src/output/output_ast';
import {ImportResolver} from '@angular/compiler/src/output/path_util';
@ -15,38 +16,42 @@ const someModuleUrl = 'somePackage/somePath';
const anotherModuleUrl = 'somePackage/someOtherPath';
const sameModuleIdentifier: CompileIdentifierMetadata = {
reference: {name: 'someLocalId', filePath: someModuleUrl}
reference: new StaticSymbol(someModuleUrl, 'someLocalId', [])
};
const externalModuleIdentifier: CompileIdentifierMetadata = {
reference: {name: 'someExternalId', filePath: anotherModuleUrl}
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', [])
};
class SimpleJsImportGenerator implements ImportResolver {
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
return importedUrlStr;
}
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
}
export function main() {
// Note supported features of our OutputAsti n TS:
// Not supported features of our OutputAst in TS:
// - real `const` like in Dart
// - final fields
describe('TypeScriptEmitter', () => {
let importResolver: ImportResolver;
let emitter: TypeScriptEmitter;
let someVar: o.ReadVarExpr;
beforeEach(() => {
emitter = new TypeScriptEmitter(new SimpleJsImportGenerator());
importResolver = new SimpleJsImportGenerator();
emitter = new TypeScriptEmitter(importResolver);
someVar = o.variable('someVar');
});
function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string {
function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string {
if (!exportedVars) {
exportedVars = [];
}
return emitter.emitStatements(someModuleUrl, [stmt], exportedVars);
const stmts = Array.isArray(stmt) ? stmt : [stmt];
return emitter.emitStatements(someModuleUrl, stmts, exportedVars);
}
it('should declare variables', () => {
@ -59,6 +64,79 @@ export function main() {
.toEqual(`var someVar:number = 1;`);
});
describe('declare variables with ExternExpressions as values', () => {
it('should create no reexport if the identifier is in the same module', () => {
// identifier is in the same module -> no reexport
expect(emitStmt(someVar.set(o.importExpr(sameModuleIdentifier)).toDeclStmt(), ['someVar']))
.toEqual('export var someVar:any = someLocalId;');
});
it('should create no reexport if the variable is not exported', () => {
expect(emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt())).toEqual([
`import * as import0 from 'somePackage/someOtherPath';`,
`var someVar:any = import0.someExternalId;`
].join('\n'));
});
it('should create no reexport if the variable is typed', () => {
expect(emitStmt(
someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(o.DYNAMIC_TYPE),
['someVar']))
.toEqual([
`import * as import0 from 'somePackage/someOtherPath';`,
`export var someVar:any = import0.someExternalId;`
].join('\n'));
});
it('should create no reexport if the identifier has members', () => {
const externalModuleIdentifierWithMembers: CompileIdentifierMetadata = {
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', ['a'])
};
expect(emitStmt(
someVar.set(o.importExpr(externalModuleIdentifierWithMembers)).toDeclStmt(),
['someVar']))
.toEqual([
`import * as import0 from 'somePackage/someOtherPath';`,
`export var someVar:any = import0.someExternalId.a;`
].join('\n'));
});
it('should create a reexport', () => {
expect(
emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(), ['someVar']))
.toEqual([
`export {someExternalId as someVar} from 'somePackage/someOtherPath';`, ``
].join('\n'));
});
it('should create multiple reexports from the same file', () => {
const someVar2 = o.variable('someVar2');
const externalModuleIdentifier2: CompileIdentifierMetadata = {
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId2', [])
};
expect(emitStmt(
[
someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(),
someVar2.set(o.importExpr(externalModuleIdentifier2)).toDeclStmt()
],
['someVar', 'someVar2']))
.toEqual([
`export {someExternalId as someVar,someExternalId2 as someVar2} from 'somePackage/someOtherPath';`,
``
].join('\n'));
});
it('should use `importAs` for reexports', () => {
spyOn(importResolver, 'getImportAs')
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
expect(
emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(), ['someVar']))
.toEqual([
`export {importAsName as someVar} from 'somePackage/importAsModule';`, ``
].join('\n'));
});
});
it('should read and write variables', () => {
expect(emitStmt(someVar.toStmt())).toEqual(`someVar;`);
expect(emitStmt(someVar.set(o.literal(1)).toStmt())).toEqual(`someVar = 1;`);
@ -134,6 +212,14 @@ export function main() {
].join('\n'));
});
it('should support `importAs` for external identifiers', () => {
spyOn(importResolver, 'getImportAs')
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt())).toEqual([
`import * as import0 from 'somePackage/importAsModule';`, `import0.importAsName;`
].join('\n'));
});
it('should support operators', () => {
const lhs = o.variable('lhs');
const rhs = o.variable('rhs');
@ -332,6 +418,16 @@ export function main() {
].join('\n'));
});
it('should support `importAs` for external types', () => {
spyOn(importResolver, 'getImportAs')
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
const writeVarExpr = o.variable('a').set(o.NULL_EXPR);
expect(emitStmt(writeVarExpr.toDeclStmt(o.importType(externalModuleIdentifier)))).toEqual([
`import * as import0 from 'somePackage/importAsModule';`,
`var a:import0.importAsName = (null as any);`
].join('\n'));
});
it('should support expression types', () => {
expect(emitStmt(o.variable('a')
.set(o.NULL_EXPR)