refactor(ivy): ngcc - Renderer now manages d.ts transformation (#26082)

PR Close #26082
This commit is contained in:
Pete Bacon Darwin
2018-10-04 12:19:11 +01:00
committed by Miško Hevery
parent f7b17a4784
commit 632f66a461
8 changed files with 360 additions and 265 deletions

View File

@ -47,7 +47,7 @@ describe('SwitchMarkerAnalyzer', () => {
describe('analyzeProgram()', () => {
it('should check for switchable markers in all the files of the program', () => {
const program = makeProgram(...TEST_PROGRAM);
const host = new Fesm2015ReflectionHost(program.getTypeChecker());
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host);
const analysis = analyzer.analyzeProgram(program);

View File

@ -5,24 +5,28 @@
* 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 {dirname} from 'canonical-path';
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {DtsMapper} from '../../src/host/dts_mapper';
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
import {Esm2015Renderer} from '../../src/rendering/esm2015_renderer';
function setup(file: {name: string, contents: string}) {
function setup(file: {name: string, contents: string}, transformDts: boolean = false) {
const dir = dirname(file.name);
const dtsMapper = new DtsMapper(dir, dir);
const program = makeProgram(file);
const sourceFile = program.getSourceFile(file.name) !;
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const analyzer = new DecorationAnalyzer(program.getTypeChecker(), host, [''], false);
const renderer = new Esm2015Renderer(host, false, null);
return {analyzer, host, program, renderer};
}
function analyze(host: Fesm2015ReflectionHost, analyzer: DecorationAnalyzer, file: ts.SourceFile) {
const decoratedFiles = host.findDecoratedFiles(file);
return Array.from(decoratedFiles.values()).map(file => analyzer.analyzeFile(file))[0];
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new Esm2015Renderer(host, false, null, dir, dir, dtsMapper);
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
}
const PROGRAM = {
@ -133,13 +137,14 @@ export class A {}`);
describe('rewriteSwitchableDeclarations', () => {
it('should switch marked declaration initializers', () => {
const {renderer, program} = setup(PROGRAM);
const {renderer, program, switchMarkerAnalyses, sourceFile} = setup(PROGRAM);
const file = program.getSourceFile('some/file.js');
if (file === undefined) {
throw new Error(`Could not find source file`);
}
const output = new MagicString(PROGRAM.contents);
renderer.rewriteSwitchableDeclarations(output, file);
renderer.rewriteSwitchableDeclarations(
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
expect(output.toString())
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
expect(output.toString())
@ -157,10 +162,10 @@ export class A {}`);
describe('addDefinitions', () => {
it('should insert the definitions directly after the class declaration', () => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT');
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
expect(output.toString()).toContain(`
export class A {}
SOME DEFINITION TEXT
@ -175,10 +180,10 @@ A.decorators = [
describe('[static property declaration]', () => {
it('should delete the decorator (and following comma) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -194,10 +199,10 @@ A.decorators = [
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[1];
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -213,10 +218,10 @@ A.decorators = [
it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[2];
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -234,11 +239,10 @@ A.decorators = [
describe('[__decorate declarations]', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile =
analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'A') !;
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -252,11 +256,10 @@ A.decorators = [
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile =
analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'B') !;
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -271,11 +274,10 @@ A.decorators = [
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile =
analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'C') !;
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);

View File

@ -9,20 +9,19 @@ import * as ts from 'typescript';
import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
function setup(file: {name: string, contents: string}) {
const program = makeProgram(file);
const sourceFile = program.getSourceFile(file.name) !;
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const analyzer = new DecorationAnalyzer(program.getTypeChecker(), host, [''], false);
const renderer = new Esm5Renderer(host, false, null);
return {analyzer, host, program, renderer};
}
function analyze(host: Esm5ReflectionHost, analyzer: DecorationAnalyzer, file: ts.SourceFile) {
const decoratedFiles = host.findDecoratedFiles(file);
return Array.from(decoratedFiles.values()).map(file => analyzer.analyzeFile(file))[0];
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new Esm5Renderer(host, false, null, '', '');
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
}
const PROGRAM = {
@ -158,13 +157,14 @@ var A = (function() {`);
describe('rewriteSwitchableDeclarations', () => {
it('should switch marked declaration initializers', () => {
const {renderer, program} = setup(PROGRAM);
const {renderer, program, sourceFile, switchMarkerAnalyses} = setup(PROGRAM);
const file = program.getSourceFile('some/file.js');
if (file === undefined) {
throw new Error(`Could not find source file`);
}
const output = new MagicString(PROGRAM.contents);
renderer.rewriteSwitchableDeclarations(output, file);
renderer.rewriteSwitchableDeclarations(
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
expect(output.toString())
.not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
expect(output.toString())
@ -182,10 +182,10 @@ var A = (function() {`);
describe('addDefinitions', () => {
it('should insert the definitions directly after the class declaration', () => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT');
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
expect(output.toString()).toContain(`
function A() {}
SOME DEFINITION TEXT
@ -199,10 +199,10 @@ SOME DEFINITION TEXT
describe('removeDecorators', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -217,10 +217,10 @@ SOME DEFINITION TEXT
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[1];
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -236,10 +236,10 @@ SOME DEFINITION TEXT
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(host, analyzer, program.getSourceFile(PROGRAM.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[2];
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -257,11 +257,10 @@ SOME DEFINITION TEXT
describe('[__decorate declarations]', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile =
analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'A') !;
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -275,11 +274,10 @@ SOME DEFINITION TEXT
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile =
analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'B') !;
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
@ -294,11 +292,10 @@ SOME DEFINITION TEXT
it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis',
() => {
const {analyzer, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
const analyzedFile =
analyze(host, analyzer, program.getSourceFile(PROGRAM_DECORATE_HELPER.name) !);
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
const analyzedClass = analyzedFile.analyzedClasses.find(c => c.name === 'C') !;
const analyzedClass =
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);

View File

@ -11,11 +11,13 @@ import * as ts from 'typescript';
import MagicString from 'magic-string';
import {fromObject, generateMapFileComment} from 'convert-source-map';
import {makeProgram} from '../helpers/utils';
import {AnalyzedClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {AnalyzedClass, DecorationAnalyzer, DecorationAnalyses} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
import {Renderer} from '../../src/rendering/renderer';
class TestRenderer extends Renderer {
constructor(host: Fesm2015ReflectionHost) { super(host, false, null, '/src', '/dist'); }
addImports(output: MagicString, imports: {name: string, as: string}[]) {
output.prepend('\n// ADD IMPORTS\n');
}
@ -33,118 +35,113 @@ class TestRenderer extends Renderer {
}
}
function createTestRenderer() {
const renderer = new TestRenderer({} as Fesm2015ReflectionHost, false, null);
function createTestRenderer(file: {name: string, contents: string}) {
const program = makeProgram(file);
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new TestRenderer(host);
spyOn(renderer, 'addImports').and.callThrough();
spyOn(renderer, 'addDefinitions').and.callThrough();
spyOn(renderer, 'removeDecorators').and.callThrough();
return renderer as jasmine.SpyObj<TestRenderer>;
}
function analyze(file: {name: string, contents: string}) {
const program = makeProgram(file);
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
const analyzer = new DecorationAnalyzer(program.getTypeChecker(), host, [''], false);
const decoratedFiles = host.findDecoratedFiles(program.getSourceFile(file.name) !);
const analyzedFiles = Array.from(decoratedFiles.values()).map(file => analyzer.analyzeFile(file));
return {program, host, analyzer, decoratedFiles, analyzedFiles};
return {renderer, program, decorationAnalyses, switchMarkerAnalyses};
}
describe('Renderer', () => {
const INPUT_PROGRAM = {
name: '/file.js',
name: '/src/file.js',
contents:
`import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n`
};
const INPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': '/file.js',
'file': '/src/file.js',
'sourceRoot': '',
'sources': ['/file.ts'],
'sources': ['/src/file.ts'],
'names': [],
'mappings':
'AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,MAAM;IACF,GAAG,CAAC,CAAS;QACT,OAAO,CAAC,CAAC;IACb,CAAC;;AACM,YAAU,GAAG;IAChB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE;CACnD,CAAC',
'sourcesContent': [
'import { Directive } from \'@angular/core\';\nexport class A {\n foo(x: string): string {\n return x;\n }\n static decorators = [\n { type: Directive, args: [{ selector: \'[a]\' }] }\n ];\n}'
]
'sourcesContent': [INPUT_PROGRAM.contents]
});
const RENDERED_CONTENTS =
`\n// REWRITTEN DECLARATIONS\n\n// REMOVE DECORATORS\n\n// ADD IMPORTS\n\n// ADD CONSTANTS\n\n// ADD DEFINITIONS\n` +
`\n// REMOVE DECORATORS\n\n// ADD IMPORTS\n\n// ADD CONSTANTS\n\n// ADD DEFINITIONS\n` +
INPUT_PROGRAM.contents;
const OUTPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': '/output_file.js',
'sources': ['/file.js'],
'sourcesContent': [
'import { Directive } from \'@angular/core\';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: \'[a]\' }] }\n];\n'
],
'file': '/dist/file.js',
'sources': ['/src/file.js'],
'sourcesContent': [INPUT_PROGRAM.contents],
'names': [],
'mappings': ';;;;;;;;;;AAAA;;;;;;;;;'
'mappings': ';;;;;;;;AAAA;;;;;;;;;'
});
const MERGED_OUTPUT_PROGRAM_MAP = fromObject({
'version': 3,
'sources': ['/file.ts'],
'sources': ['/src/file.ts'],
'names': [],
'mappings': ';;;;;;;;;;AAAA',
'file': '/output_file.js',
'sourcesContent': [
'import { Directive } from \'@angular/core\';\nexport class A {\n foo(x: string): string {\n return x;\n }\n static decorators = [\n { type: Directive, args: [{ selector: \'[a]\' }] }\n ];\n}'
]
'mappings': ';;;;;;;;AAAA',
'file': '/dist/file.js',
'sourcesContent': [INPUT_PROGRAM.contents]
});
describe('renderFile()', () => {
describe('renderProgram()', () => {
it('should render the modified contents; and a new map file, if the original provided no map file.',
() => {
const renderer = createTestRenderer();
const {analyzedFiles} = analyze(INPUT_PROGRAM);
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/output_file.js.map'));
expect(result.map !.path).toEqual('/output_file.js.map');
expect(result.map !.contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON());
const {renderer, program, decorationAnalyses, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
expect(result[1].path).toEqual('/dist/file.js.map');
expect(result[1].contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON());
});
it('should call addImports with the source code and info about the core Angular library.',
() => {
const renderer = createTestRenderer();
const {analyzedFiles} = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.addImports.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(renderer.addImports.calls.first().args[1]).toEqual([
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(addImportsSpy.calls.first().args[1]).toEqual([
{name: '@angular/core', as: 'ɵngcc0'}
]);
});
it('should call addDefinitions with the source code, the analyzed class and the renderered definitions.',
() => {
const renderer = createTestRenderer();
const {analyzedFiles} = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.addDefinitions.calls.first().args[0].toString())
.toEqual(RENDERED_CONTENTS);
expect(renderer.addDefinitions.calls.first().args[1])
.toBe(analyzedFiles[0].analyzedClasses[0]);
expect(renderer.addDefinitions.calls.first().args[2])
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
name: 'A',
decorators: [jasmine.objectContaining({name: 'Directive'})],
}));
expect(addDefinitionsSpy.calls.first().args[2])
.toEqual(
`A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); }, features: [ɵngcc0.ɵPublicFeature] });`);
});
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
() => {
const renderer = createTestRenderer();
const {analyzedFiles} = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.removeDecorators.calls.first().args[0].toString())
.toEqual(RENDERED_CONTENTS);
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
// Each map key is the TS node of the decorator container
// Each map value is an array of TS nodes that are the decorators to remove
const map = renderer.removeDecorators.calls.first().args[1] as Map<ts.Node, ts.Node[]>;
const map = removeDecoratorsSpy.calls.first().args[1] as Map<ts.Node, ts.Node[]>;
const keys = Array.from(map.keys());
expect(keys.length).toEqual(1);
expect(keys[0].getText())
@ -157,34 +154,31 @@ describe('Renderer', () => {
it('should merge any inline source map from the original file and write the output as an inline source map',
() => {
const renderer = createTestRenderer();
const {analyzedFiles} = analyze({
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer({
...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
});
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents)
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
expect(result.map).toBe(null);
expect(result[1]).toBeUndefined();
});
it('should merge any external source map from the original file and write the output to an external source map',
() => {
// Mock out reading the map file from disk
const readFileSyncSpy =
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const renderer = createTestRenderer();
const {analyzedFiles} = analyze({
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer({
...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
});
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/output_file.js.map'));
expect(result.map !.path).toEqual('/output_file.js.map');
expect(result.map !.contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON());
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
expect(result[1].path).toEqual('/dist/file.js.map');
expect(result[1].contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON());
});
});
});