refactor(ivy): remove ngcc Parser and use NgccReflectionHost instead (#26082)

PR Close #26082
This commit is contained in:
Pete Bacon Darwin 2018-09-28 14:57:50 +01:00 committed by Miško Hevery
parent 7f03528dbc
commit 26209fca49
15 changed files with 85 additions and 386 deletions

View File

@ -131,7 +131,7 @@ export class Esm5ReflectionHost extends Fesm2015ReflectionHost {
* @returns A collection of files objects that hold info about the decorated classes and import * @returns A collection of files objects that hold info about the decorated classes and import
* information. * information.
*/ */
findDecoratedFiles(entryPoint: ts.SourceFile): DecoratedFile[] { findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile> {
const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint); const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint);
const map = new Map<ts.SourceFile, DecoratedFile>(); const map = new Map<ts.SourceFile, DecoratedFile>();
const getParsedClass = (declaration: ts.VariableDeclaration) => { const getParsedClass = (declaration: ts.VariableDeclaration) => {
@ -158,7 +158,7 @@ export class Esm5ReflectionHost extends Fesm2015ReflectionHost {
map.get(file) !.decoratedClasses.push(clazz); map.get(file) !.decoratedClasses.push(clazz);
}); });
} }
return Array.from(map.values()); return map;
} }
///////////// Protected Helpers ///////////// ///////////// Protected Helpers /////////////

View File

@ -302,7 +302,7 @@ export class Fesm2015ReflectionHost extends TypeScriptReflectionHost implements
* @returns A collection of files objects that hold info about the decorated classes and import * @returns A collection of files objects that hold info about the decorated classes and import
* information. * information.
*/ */
findDecoratedFiles(entryPoint: ts.SourceFile): DecoratedFile[] { findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile> {
const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint); const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint);
const map = new Map<ts.SourceFile, DecoratedFile>(); const map = new Map<ts.SourceFile, DecoratedFile>();
if (moduleSymbol) { if (moduleSymbol) {
@ -335,7 +335,7 @@ export class Fesm2015ReflectionHost extends TypeScriptReflectionHost implements
map.get(file) !.decoratedClasses.push(clazz); map.get(file) !.decoratedClasses.push(clazz);
}); });
} }
return Array.from(map.values()); return map;
} }
///////////// Protected Helpers ///////////// ///////////// Protected Helpers /////////////

View File

@ -45,5 +45,5 @@ export interface NgccReflectionHost extends ReflectionHost {
* @returns A collection of files objects that hold info about the decorated classes and import * @returns A collection of files objects that hold info about the decorated classes and import
* information. * information.
*/ */
findDecoratedFiles(entryPoint: ts.SourceFile): DecoratedFile[]; findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile>;
} }

View File

@ -18,9 +18,6 @@ import {Esm2015ReflectionHost} from '../host/esm2015_host';
import {Esm5ReflectionHost} from '../host/esm5_host'; import {Esm5ReflectionHost} from '../host/esm5_host';
import {Fesm2015ReflectionHost} from '../host/fesm2015_host'; import {Fesm2015ReflectionHost} from '../host/fesm2015_host';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
import {Esm2015FileParser} from '../parsing/esm2015_parser';
import {Esm5FileParser} from '../parsing/esm5_parser';
import {FileParser} from '../parsing/file_parser';
import {Esm2015Renderer} from '../rendering/esm2015_renderer'; import {Esm2015Renderer} from '../rendering/esm2015_renderer';
import {Esm5Renderer} from '../rendering/esm5_renderer'; import {Esm5Renderer} from '../rendering/esm5_renderer';
import {FileInfo, Renderer} from '../rendering/renderer'; import {FileInfo, Renderer} from '../rendering/renderer';
@ -87,15 +84,15 @@ export class Transformer {
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsMapper); const reflectionHost = this.getHost(isCore, format, packageProgram, dtsMapper);
const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null; const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null;
const parser = this.getFileParser(format, packageProgram, reflectionHost);
const analyzer = new Analyzer(typeChecker, reflectionHost, rootDirs, isCore); const analyzer = new Analyzer(typeChecker, reflectionHost, rootDirs, isCore);
const renderer = const renderer =
this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile); this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile);
// Parse and analyze the files. // Parse and analyze the files.
const entryPointFile = packageProgram.getSourceFile(entryPointFilePath) !; const entryPointFile = packageProgram.getSourceFile(entryPointFilePath) !;
const parsedFiles = parser.parseFile(entryPointFile); const decoratedFiles = reflectionHost.findDecoratedFiles(entryPointFile);
const analyzedFiles = parsedFiles.map(parsedFile => analyzer.analyzeFile(parsedFile)); const analyzedFiles = Array.from(decoratedFiles.values())
.map(decoratedFile => analyzer.analyzeFile(decoratedFile));
// Transform the source files and source maps. // Transform the source files and source maps.
outputFiles.push( outputFiles.push(
@ -131,19 +128,6 @@ export class Transformer {
} }
} }
getFileParser(format: string, program: ts.Program, host: NgccReflectionHost): FileParser {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015FileParser(program, host);
case 'esm5':
case 'fesm5':
return new Esm5FileParser(program, host);
default:
throw new Error(`File parser for "${format}" not yet implemented.`);
}
}
getRenderer( getRenderer(
format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean, format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean,
rewriteCoreImportsTo: ts.SourceFile|null): Renderer { rewriteCoreImportsTo: ts.SourceFile|null): Renderer {

View File

@ -1,58 +0,0 @@
/**
* @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 {DecoratedClass} from '../host/decorated_class';
import {DecoratedFile} from '../host/decorated_file';
import {NgccReflectionHost} from '../host/ngcc_host';
import {getOriginalSymbol, isDefined} from '../utils';
import {FileParser} from './file_parser';
export class Esm2015FileParser implements FileParser {
checker = this.program.getTypeChecker();
constructor(protected program: ts.Program, protected host: NgccReflectionHost) {}
parseFile(file: ts.SourceFile): DecoratedFile[] {
const moduleSymbol = this.checker.getSymbolAtLocation(file);
const map = new Map<ts.SourceFile, DecoratedFile>();
if (moduleSymbol) {
const exportedSymbols =
this.checker.getExportsOfModule(moduleSymbol).map(getOriginalSymbol(this.checker));
const exportedDeclarations =
exportedSymbols.map(exportSymbol => exportSymbol.valueDeclaration).filter(isDefined);
const decoratedClasses =
exportedDeclarations
.map(declaration => {
if (ts.isClassDeclaration(declaration) || ts.isVariableDeclaration(declaration)) {
const name = declaration.name && ts.isIdentifier(declaration.name) ?
declaration.name.text :
undefined;
const decorators = this.host.getDecoratorsOfDeclaration(declaration);
return decorators && isDefined(name) ?
new DecoratedClass(name, declaration, decorators) :
undefined;
}
return undefined;
})
.filter(isDefined);
decoratedClasses.forEach(clazz => {
const file = clazz.declaration.getSourceFile();
if (!map.has(file)) {
map.set(file, new DecoratedFile(file));
}
map.get(file) !.decoratedClasses.push(clazz);
});
}
return Array.from(map.values());
}
}

View File

@ -1,59 +0,0 @@
/**
* @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 {DecoratedClass} from '../host/decorated_class';
import {DecoratedFile} from '../host/decorated_file';
import {NgccReflectionHost} from '../host/ngcc_host';
import {getNameText, getOriginalSymbol, isDefined} from '../utils';
import {FileParser} from './file_parser';
/**
* Parses ESM5 package files for decoratrs classes.
* ESM5 "classes" are actually functions wrapped by and returned
* from an IFEE.
*/
export class Esm5FileParser implements FileParser {
checker = this.program.getTypeChecker();
constructor(protected program: ts.Program, protected host: NgccReflectionHost) {}
parseFile(file: ts.SourceFile): DecoratedFile[] {
const moduleSymbol = this.checker.getSymbolAtLocation(file);
const map = new Map<ts.SourceFile, DecoratedFile>();
const getParsedClass = (declaration: ts.VariableDeclaration) => {
const decorators = this.host.getDecoratorsOfDeclaration(declaration);
if (decorators) {
return new DecoratedClass(getNameText(declaration.name), declaration, decorators);
}
};
if (moduleSymbol) {
const classDeclarations = this.checker.getExportsOfModule(moduleSymbol)
.map(getOriginalSymbol(this.checker))
.map(exportSymbol => exportSymbol.valueDeclaration)
.filter(isDefined)
.filter(ts.isVariableDeclaration);
const decoratedClasses = classDeclarations.map(getParsedClass).filter(isDefined);
decoratedClasses.forEach(clazz => {
const file = clazz.declaration.getSourceFile();
if (!map.has(file)) {
map.set(file, new DecoratedFile(file));
}
map.get(file) !.decoratedClasses.push(clazz);
});
}
return Array.from(map.values());
}
}

View File

@ -1,35 +0,0 @@
/**
* @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 {DecoratedFile} from '../host/decorated_file';
/**
* Classes that implement this interface can parse a file in a package to
* find the "declarations" (representing exported classes), that are decorated with core
* decorators, such as `@Component`, `@Injectable`, etc.
*
* Identifying classes can be different depending upon the format of the source file.
*
* For example:
*
* - ES2015 files contain `class Xxxx {...}` style declarations
* - ES5 files contain `var Xxxx = (function () { function Xxxx() { ... }; return Xxxx; })();` style
* declarations
* - UMD have similar declarations to ES5 files but the whole thing is wrapped in IIFE module
* wrapper
* function.
*/
export interface FileParser {
/**
* Parse a file to identify the decorated classes.
*
* @param file The the entry point file for identifying classes to process.
* @returns A `ParsedFiles` collection that holds the decorated classes and import information.
*/
parseFile(file: ts.SourceFile): DecoratedFile[];
}

View File

@ -124,16 +124,20 @@ describe('Esm2015ReflectionHost', () => {
const program = makeProgram(...DECORATED_FILES); const program = makeProgram(...DECORATED_FILES);
const dtsMapper = new DtsMapper('/src', '/typings'); const dtsMapper = new DtsMapper('/src', '/typings');
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsMapper); const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsMapper);
const decoratedFiles = const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
host.findDecoratedFiles(program.getSourceFile(DECORATED_FILES[0].name) !); const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
expect(decoratedFiles.length).toEqual(2); const decoratedFiles = host.findDecoratedFiles(primaryFile);
const primary = decoratedFiles[0];
expect(decoratedFiles.size).toEqual(2);
const primary = decoratedFiles.get(primaryFile) !;
expect(primary.decoratedClasses.length).toEqual(1); expect(primary.decoratedClasses.length).toEqual(1);
const classA = primary.decoratedClasses.find(c => c.name === 'A') !; const classA = primary.decoratedClasses.find(c => c.name === 'A') !;
expect(classA.name).toEqual('A'); expect(classA.name).toEqual('A');
expect(ts.isClassDeclaration(classA.declaration)).toBeTruthy(); expect(ts.isClassDeclaration(classA.declaration)).toBeTruthy();
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']); expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const secondary = decoratedFiles[1];
const secondary = decoratedFiles.get(secondaryFile) !;
expect(secondary.decoratedClasses.length).toEqual(1); expect(secondary.decoratedClasses.length).toEqual(1);
const classD = secondary.decoratedClasses.find(c => c.name === 'D') !; const classD = secondary.decoratedClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D'); expect(classD.name).toEqual('D');

View File

@ -452,7 +452,7 @@ const DECORATED_FILES = [
function C() {} function C() {}
return C; return C;
}); });
export { A, x, C }; export { A, x, C };
export { D } from '/secondary'; export { D } from '/secondary';
` `
}, },
@ -467,7 +467,7 @@ const DECORATED_FILES = [
]; ];
return D; return D;
}()); }());
export { D }; export { D };
` `
} }
]; ];
@ -1257,17 +1257,19 @@ describe('Esm5ReflectionHost', () => {
() => { () => {
const program = makeProgram(...DECORATED_FILES); const program = makeProgram(...DECORATED_FILES);
const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const decoratedFiles = const primary = program.getSourceFile(DECORATED_FILES[0].name) !;
host.findDecoratedFiles(program.getSourceFile(DECORATED_FILES[0].name) !); const decoratedFiles = host.findDecoratedFiles(primary);
expect(decoratedFiles.length).toEqual(2); expect(decoratedFiles.size).toEqual(2);
const primary = decoratedFiles[0]; const primaryClasses = decoratedFiles.get(primary) !.decoratedClasses;
expect(primary.decoratedClasses.length).toEqual(1); expect(primaryClasses.length).toEqual(1);
const classA = primary.decoratedClasses[0]; const classA = primaryClasses.find(c => c.name === 'A') !;
expect(classA.name).toEqual('A'); expect(classA.name).toEqual('A');
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']); expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
const secondary = decoratedFiles[1];
expect(secondary.decoratedClasses.length).toEqual(1); const secondary = program.getSourceFile(DECORATED_FILES[1].name) !;
const classD = secondary.decoratedClasses[0]; const secondaryClasses = decoratedFiles.get(secondary) !.decoratedClasses;
expect(secondaryClasses.length).toEqual(1);
const classD = secondaryClasses.find(c => c.name === 'D') !;
expect(classD.name).toEqual('D'); expect(classD.name).toEqual('D');
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']); expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
}); });

View File

@ -1189,10 +1189,11 @@ describe('Fesm2015ReflectionHost', () => {
() => { () => {
const program = makeProgram(DECORATED_FILE); const program = makeProgram(DECORATED_FILE);
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker()); const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const decoratedFiles = const primaryFile = program.getSourceFile(DECORATED_FILE.name) !;
host.findDecoratedFiles(program.getSourceFile(DECORATED_FILE.name) !); const decoratedFiles = host.findDecoratedFiles(primaryFile);
expect(decoratedFiles.length).toEqual(1);
const decoratedClasses = decoratedFiles[0].decoratedClasses; expect(decoratedFiles.size).toEqual(1);
const decoratedClasses = decoratedFiles.get(primaryFile) !.decoratedClasses;
expect(decoratedClasses.length).toEqual(2); expect(decoratedClasses.length).toEqual(2);
const decoratedClassA = decoratedClasses.find(c => c.name === 'A') !; const decoratedClassA = decoratedClasses.find(c => c.name === 'A') !;

View File

@ -1,72 +0,0 @@
/**
* @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 {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
import {Esm2015FileParser} from '../../src/parsing/esm2015_parser';
import {makeProgram} from '../helpers/utils';
const BASIC_FILE = {
name: '/primary.js',
contents: `
import {Directive} from '@angular/core';
class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
];
class B {}
B.decorators = [
{ type: Directive, args: [{ selector: '[b]' }] }
];
function x() {}
function y() {}
class C {}
let D = class D {}
D = tslib_1.__decorate([
Directive({ selector: '[d]' }),
OtherD()
], D);
export {D};
export { A, x, C };
`
};
describe('Esm2015FileParser', () => {
describe('parseFile()', () => {
it('should return an array of object for each class that is exported and decorated', () => {
const program = makeProgram(BASIC_FILE);
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const parser = new Esm2015FileParser(program, host);
const parsedFiles = parser.parseFile(program.getSourceFile(BASIC_FILE.name) !);
expect(parsedFiles.length).toEqual(1);
const decoratedClasses = parsedFiles[0].decoratedClasses;
expect(decoratedClasses.length).toEqual(2);
const decoratedClassA = decoratedClasses.find(c => c.name === 'A') !;
expect(decoratedClassA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
expect(decoratedClassA.decorators.map(
decorator => decorator.args && decorator.args.map(arg => arg.getText())))
.toEqual([[`{ selector: '[a]' }`]]);
const decoratedClassD = decoratedClasses.find(c => c.name === 'D') !;
expect(decoratedClassD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
expect(decoratedClassD.decorators.map(
decorator => decorator.args && decorator.args.map(arg => arg.getText())))
.toEqual([[`{ selector: '[d]' }`]]);
});
});
});

View File

@ -1,65 +0,0 @@
/**
* @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 {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5FileParser} from '../../src/parsing/esm5_parser';
import {makeProgram} from '../helpers/utils';
const BASIC_FILE = {
name: '/primary.js',
contents: `
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
];
return A;
}());
var B = (function() {
function B() {}
B.decorators = [
{ type: Directive, args: [{ selector: '[b]' }] }
];
return B;
}());
function x() {}
function y() {}
var C = (function() {
function C() {}
return C;
});
export { A, x, C };
`
};
describe('Esm5FileParser', () => {
describe('getDecoratedClasses()', () => {
it('should return an array of object for each class that is exported and decorated', () => {
const program = makeProgram(BASIC_FILE);
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const parser = new Esm5FileParser(program, host);
const parsedFiles = parser.parseFile(program.getSourceFile(BASIC_FILE.name) !);
expect(parsedFiles.length).toEqual(1);
const decoratedClasses = parsedFiles[0].decoratedClasses;
expect(decoratedClasses.length).toEqual(1);
const decoratedClass = decoratedClasses[0];
expect(decoratedClass.name).toEqual('A');
expect(decoratedClass.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
});
});

View File

@ -10,21 +10,19 @@ import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils'; import {makeProgram} from '../helpers/utils';
import {Analyzer} from '../../src/analyzer'; import {Analyzer} from '../../src/analyzer';
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host'; import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
import {Esm2015FileParser} from '../../src/parsing/esm2015_parser';
import {Esm2015Renderer} from '../../src/rendering/esm2015_renderer'; import {Esm2015Renderer} from '../../src/rendering/esm2015_renderer';
function setup(file: {name: string, contents: string}) { function setup(file: {name: string, contents: string}) {
const program = makeProgram(file); const program = makeProgram(file);
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker()); const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const parser = new Esm2015FileParser(program, host);
const analyzer = new Analyzer(program.getTypeChecker(), host, [''], false); const analyzer = new Analyzer(program.getTypeChecker(), host, [''], false);
const renderer = new Esm2015Renderer(host, false, null); const renderer = new Esm2015Renderer(host, false, null);
return {analyzer, host, parser, program, renderer}; return {analyzer, host, program, renderer};
} }
function analyze(parser: Esm2015FileParser, analyzer: Analyzer, file: ts.SourceFile) { function analyze(host: Fesm2015ReflectionHost, analyzer: Analyzer, file: ts.SourceFile) {
const parsedFiles = parser.parseFile(file); const decoratedFiles = host.findDecoratedFiles(file);
return parsedFiles.map(file => analyzer.analyzeFile(file))[0]; return Array.from(decoratedFiles.values()).map(file => analyzer.analyzeFile(file))[0];
} }
const PROGRAM = { const PROGRAM = {
@ -159,8 +157,8 @@ export class A {}`);
describe('addDefinitions', () => { describe('addDefinitions', () => {
it('should insert the definitions directly after the class declaration', () => { it('should insert the definitions directly after the class declaration', () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT'); renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT');
expect(output.toString()).toContain(` expect(output.toString()).toContain(`
@ -177,8 +175,8 @@ A.decorators = [
describe('[static property declaration]', () => { describe('[static property declaration]', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', it('should delete the decorator (and following comma) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0]; const analyzedClass = analyzedFile.analyzedClasses[0];
const decorator = analyzedClass.decorators[0]; const decorator = analyzedClass.decorators[0];
@ -196,8 +194,8 @@ A.decorators = [
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[1]; const analyzedClass = analyzedFile.analyzedClasses[1];
const decorator = analyzedClass.decorators[0]; const decorator = analyzedClass.decorators[0];
@ -215,8 +213,8 @@ A.decorators = [
it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[2]; const analyzedClass = analyzedFile.analyzedClasses[2];
const decorator = analyzedClass.decorators[0]; const decorator = analyzedClass.decorators[0];
@ -236,9 +234,9 @@ A.decorators = [
describe('[__decorate declarations]', () => { describe('[__decorate declarations]', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => { it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM_DECORATE_HELPER); const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile = const analyzedFile =
analyze(parser, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !); analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'A') !; const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !; const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
@ -254,9 +252,9 @@ A.decorators = [
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM_DECORATE_HELPER); const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile = const analyzedFile =
analyze(parser, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !); analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'B') !; const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !; const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
@ -273,9 +271,9 @@ A.decorators = [
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM_DECORATE_HELPER); const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile = const analyzedFile =
analyze(parser, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !); analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'C') !; const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !; const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;

View File

@ -10,21 +10,19 @@ import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils'; import {makeProgram} from '../helpers/utils';
import {Analyzer} from '../../src/analyzer'; import {Analyzer} from '../../src/analyzer';
import {Esm5ReflectionHost} from '../../src/host/esm5_host'; import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5FileParser} from '../../src/parsing/esm5_parser';
import {Esm5Renderer} from '../../src/rendering/esm5_renderer'; import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
function setup(file: {name: string, contents: string}) { function setup(file: {name: string, contents: string}) {
const program = makeProgram(file); const program = makeProgram(file);
const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const parser = new Esm5FileParser(program, host);
const analyzer = new Analyzer(program.getTypeChecker(), host, [''], false); const analyzer = new Analyzer(program.getTypeChecker(), host, [''], false);
const renderer = new Esm5Renderer(host, false, null); const renderer = new Esm5Renderer(host, false, null);
return {analyzer, host, parser, program, renderer}; return {analyzer, host, program, renderer};
} }
function analyze(parser: Esm5FileParser, analyzer: Analyzer, file: ts.SourceFile) { function analyze(host: Esm5ReflectionHost, analyzer: Analyzer, file: ts.SourceFile) {
const parsedFiles = parser.parseFile(file); const decoratedFiles = host.findDecoratedFiles(file);
return parsedFiles.map(file => analyzer.analyzeFile(file))[0]; return Array.from(decoratedFiles.values()).map(file => analyzer.analyzeFile(file))[0];
} }
const PROGRAM = { const PROGRAM = {
@ -184,8 +182,8 @@ var A = (function() {`);
describe('addDefinitions', () => { describe('addDefinitions', () => {
it('should insert the definitions directly after the class declaration', () => { it('should insert the definitions directly after the class declaration', () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT'); renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT');
expect(output.toString()).toContain(` expect(output.toString()).toContain(`
@ -201,8 +199,8 @@ SOME DEFINITION TEXT
describe('removeDecorators', () => { describe('removeDecorators', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => { it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0]; const analyzedClass = analyzedFile.analyzedClasses[0];
const decorator = analyzedClass.decorators[0]; const decorator = analyzedClass.decorators[0];
@ -219,8 +217,8 @@ SOME DEFINITION TEXT
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[1]; const analyzedClass = analyzedFile.analyzedClasses[1];
const decorator = analyzedClass.decorators[0]; const decorator = analyzedClass.decorators[0];
@ -238,8 +236,8 @@ SOME DEFINITION TEXT
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis', it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM); const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !); const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[2]; const analyzedClass = analyzedFile.analyzedClasses[2];
const decorator = analyzedClass.decorators[0]; const decorator = analyzedClass.decorators[0];
@ -259,9 +257,9 @@ SOME DEFINITION TEXT
describe('[__decorate declarations]', () => { describe('[__decorate declarations]', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => { it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM_DECORATE_HELPER); const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile = const analyzedFile =
analyze(parser, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !); analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'A') !; const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !; const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
@ -277,9 +275,9 @@ SOME DEFINITION TEXT
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis', it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM_DECORATE_HELPER); const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile = const analyzedFile =
analyze(parser, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !); analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'B') !; const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !; const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
@ -296,9 +294,9 @@ SOME DEFINITION TEXT
it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis',
() => { () => {
const {analyzer, parser, program, renderer} = setup(PROGRAM_DECORATE_HELPER); const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile = const analyzedFile =
analyze(parser, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !); analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents); const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'C') !; const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !; const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;

View File

@ -13,7 +13,6 @@ import {fromObject, generateMapFileComment} from 'convert-source-map';
import {makeProgram} from '../helpers/utils'; import {makeProgram} from '../helpers/utils';
import {AnalyzedClass, Analyzer} from '../../src/analyzer'; import {AnalyzedClass, Analyzer} from '../../src/analyzer';
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host'; import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
import {Esm2015FileParser} from '../../src/parsing/esm2015_parser';
import {Renderer} from '../../src/rendering/renderer'; import {Renderer} from '../../src/rendering/renderer';
class TestRenderer extends Renderer { class TestRenderer extends Renderer {
@ -45,13 +44,15 @@ function createTestRenderer() {
function analyze(file: {name: string, contents: string}) { function analyze(file: {name: string, contents: string}) {
const program = makeProgram(file); const program = makeProgram(file);
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker()); const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const parser = new Esm2015FileParser(program, host);
const analyzer = new Analyzer(program.getTypeChecker(), host, [''], false); const analyzer = new Analyzer(program.getTypeChecker(), host, [''], false);
const parsedFiles = parser.parseFile(program.getSourceFile(file.name) !); const decoratedFiles = host.findDecoratedFiles(program.getSourceFile(file.name) !);
return parsedFiles.map(file => analyzer.analyzeFile(file)); const analyzedFiles = Array.from(decoratedFiles.values()).map(file => analyzer.analyzeFile(file));
return {program, host, analyzer, decoratedFiles, analyzedFiles};
} }
describe('Renderer', () => { describe('Renderer', () => {
const INPUT_PROGRAM = { const INPUT_PROGRAM = {
name: '/file.js', name: '/file.js',
@ -99,7 +100,7 @@ describe('Renderer', () => {
it('should render the modified contents; and a new map file, if the original provided no map file.', it('should render the modified contents; and a new map file, if the original provided no map file.',
() => { () => {
const renderer = createTestRenderer(); const renderer = createTestRenderer();
const analyzedFiles = analyze(INPUT_PROGRAM); const {analyzedFiles} = analyze(INPUT_PROGRAM);
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js'); const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js'); expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents) expect(result.source.contents)
@ -111,7 +112,7 @@ describe('Renderer', () => {
it('should call addImports with the source code and info about the core Angular library.', it('should call addImports with the source code and info about the core Angular library.',
() => { () => {
const renderer = createTestRenderer(); const renderer = createTestRenderer();
const analyzedFiles = analyze(INPUT_PROGRAM); const {analyzedFiles} = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFiles[0], '/output_file.js'); renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.addImports.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); expect(renderer.addImports.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(renderer.addImports.calls.first().args[1]).toEqual([ expect(renderer.addImports.calls.first().args[1]).toEqual([
@ -122,12 +123,12 @@ describe('Renderer', () => {
it('should call addDefinitions with the source code, the analyzed class and the renderered definitions.', it('should call addDefinitions with the source code, the analyzed class and the renderered definitions.',
() => { () => {
const renderer = createTestRenderer(); const renderer = createTestRenderer();
const analyzedFile = analyze(INPUT_PROGRAM)[0]; const {analyzedFiles} = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFile, '/output_file.js'); renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.addDefinitions.calls.first().args[0].toString()) expect(renderer.addDefinitions.calls.first().args[0].toString())
.toEqual(RENDERED_CONTENTS); .toEqual(RENDERED_CONTENTS);
expect(renderer.addDefinitions.calls.first().args[1]) expect(renderer.addDefinitions.calls.first().args[1])
.toBe(analyzedFile.analyzedClasses[0]); .toBe(analyzedFiles[0].analyzedClasses[0]);
expect(renderer.addDefinitions.calls.first().args[2]) expect(renderer.addDefinitions.calls.first().args[2])
.toEqual( .toEqual(
`A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); }, features: [ɵngcc0.ɵPublicFeature] });`); `A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); }, features: [ɵngcc0.ɵPublicFeature] });`);
@ -136,8 +137,8 @@ describe('Renderer', () => {
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed', it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
() => { () => {
const renderer = createTestRenderer(); const renderer = createTestRenderer();
const analyzedFile = analyze(INPUT_PROGRAM)[0]; const {analyzedFiles} = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFile, '/output_file.js'); renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.removeDecorators.calls.first().args[0].toString()) expect(renderer.removeDecorators.calls.first().args[0].toString())
.toEqual(RENDERED_CONTENTS); .toEqual(RENDERED_CONTENTS);
@ -157,7 +158,7 @@ describe('Renderer', () => {
it('should merge any inline source map from the original file and write the output as an inline source map', it('should merge any inline source map from the original file and write the output as an inline source map',
() => { () => {
const renderer = createTestRenderer(); const renderer = createTestRenderer();
const analyzedFiles = analyze({ const {analyzedFiles} = analyze({
...INPUT_PROGRAM, ...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment() contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
}); });
@ -174,7 +175,7 @@ describe('Renderer', () => {
const readFileSyncSpy = const readFileSyncSpy =
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON()); spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const renderer = createTestRenderer(); const renderer = createTestRenderer();
const analyzedFiles = analyze({ const {analyzedFiles} = analyze({
...INPUT_PROGRAM, ...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map' contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
}); });