refactor(ivy): ngcc - `Renderer` now manages d.ts transformation (#26082)
PR Close #26082
This commit is contained in:
parent
f7b17a4784
commit
632f66a461
|
@ -5,90 +5,59 @@
|
|||
* 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 {relative, resolve} from 'canonical-path';
|
||||
import {readFileSync} from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
import MagicString from 'magic-string';
|
||||
import {POST_NGCC_MARKER, PRE_NGCC_MARKER} from '../host/ngcc_host';
|
||||
import {AnalyzedClass} from '../analysis/decoration_analyzer';
|
||||
import {Renderer} from './renderer';
|
||||
|
||||
export class Esm2015Renderer extends Renderer {
|
||||
/**
|
||||
* Add the imports at the top of the file
|
||||
*/
|
||||
addImports(output: MagicString, imports: {name: string; as: string;}[]): void {
|
||||
// The imports get inserted at the very top of the file.
|
||||
imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); });
|
||||
import {DtsFileTransformer} from '../../../ngtsc/transform';
|
||||
import {DecorationAnalysis} from '../analysis/decoration_analyzer';
|
||||
import {SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {DtsMapper} from '../host/dts_mapper';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
|
||||
import {Fesm2015Renderer} from './fesm2015_renderer';
|
||||
import {FileInfo} from './renderer';
|
||||
|
||||
export class Esm2015Renderer extends Fesm2015Renderer {
|
||||
constructor(
|
||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||
protected targetPath: string, protected dtsMapper: DtsMapper) {
|
||||
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath);
|
||||
}
|
||||
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
renderFile(
|
||||
sourceFile: ts.SourceFile, decorationAnalysis: DecorationAnalysis|undefined,
|
||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, targetPath: string): FileInfo[] {
|
||||
const renderedFiles =
|
||||
super.renderFile(sourceFile, decorationAnalysis, switchMarkerAnalysis, targetPath);
|
||||
|
||||
// Transform the `.d.ts` files.
|
||||
// TODO(gkalpak): What about `.d.ts` source maps? (See
|
||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
|
||||
if (decorationAnalysis) {
|
||||
// Create a `DtsFileTransformer` for the source file and record the generated fields, which
|
||||
// will allow the corresponding `.d.ts` file to be transformed later.
|
||||
const dtsTransformer = new DtsFileTransformer(this.rewriteCoreImportsTo, IMPORT_PREFIX);
|
||||
decorationAnalysis.analyzedClasses.forEach(
|
||||
analyzedClass =>
|
||||
dtsTransformer.recordStaticField(analyzedClass.name, analyzedClass.compilation));
|
||||
|
||||
// Find the corresponding `.d.ts` file.
|
||||
const sourceFileName = sourceFile.fileName;
|
||||
const originalDtsFileName = this.dtsMapper.getDtsFileNameFor(sourceFileName);
|
||||
const originalDtsContents = readFileSync(originalDtsFileName, 'utf8');
|
||||
|
||||
// Transform the `.d.ts` file based on the recorded source file changes.
|
||||
const transformedDtsFileName =
|
||||
resolve(this.targetPath, relative(this.sourcePath, originalDtsFileName));
|
||||
const transformedDtsContents = dtsTransformer.transform(originalDtsContents, sourceFileName);
|
||||
|
||||
// Add the transformed `.d.ts` file to the list of output files.
|
||||
renderedFiles.push({path: transformedDtsFileName, contents: transformedDtsContents});
|
||||
}
|
||||
const insertionPoint = file.statements.reduce((prev, stmt) => {
|
||||
if (ts.isImportDeclaration(stmt) || ts.isImportEqualsDeclaration(stmt) ||
|
||||
ts.isNamespaceImport(stmt)) {
|
||||
return stmt.getEnd();
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
output.appendLeft(insertionPoint, '\n' + constants + '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the definitions to each decorated class
|
||||
*/
|
||||
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void {
|
||||
const classSymbol = this.host.getClassSymbol(analyzedClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Analyzed class does not have a valid symbol: ${analyzedClass.name}`);
|
||||
}
|
||||
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove static decorator properties from classes
|
||||
*/
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void {
|
||||
decoratorsToRemove.forEach((nodesToRemove, containerNode) => {
|
||||
if (ts.isArrayLiteralExpression(containerNode)) {
|
||||
const items = containerNode.elements;
|
||||
if (items.length === nodesToRemove.length) {
|
||||
// Remove the entire statement
|
||||
const statement = findStatement(containerNode);
|
||||
if (statement) {
|
||||
output.remove(statement.getFullStart(), statement.getEnd());
|
||||
}
|
||||
} else {
|
||||
nodesToRemove.forEach(node => {
|
||||
// remove any trailing comma
|
||||
const end = (output.slice(node.getEnd(), node.getEnd() + 1) === ',') ?
|
||||
node.getEnd() + 1 :
|
||||
node.getEnd();
|
||||
output.remove(node.getFullStart(), end);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rewriteSwitchableDeclarations(outputText: MagicString, sourceFile: ts.SourceFile): void {
|
||||
const declarations = this.host.getSwitchableDeclarations(sourceFile);
|
||||
declarations.forEach(declaration => {
|
||||
const start = declaration.initializer.getStart();
|
||||
const end = declaration.initializer.getEnd();
|
||||
const replacement = declaration.initializer.text.replace(PRE_NGCC_MARKER, POST_NGCC_MARKER);
|
||||
outputText.overwrite(start, end, replacement);
|
||||
});
|
||||
return renderedFiles;
|
||||
}
|
||||
}
|
||||
|
||||
function findStatement(node: ts.Node) {
|
||||
while (node) {
|
||||
if (ts.isExpressionStatement(node)) {
|
||||
return node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
* 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 {Esm2015Renderer} from './esm2015_renderer';
|
||||
import {Fesm2015Renderer} from './fesm2015_renderer';
|
||||
|
||||
export class Esm5Renderer extends Esm2015Renderer {}
|
||||
export class Esm5Renderer extends Fesm2015Renderer {}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @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 MagicString from 'magic-string';
|
||||
import {NgccReflectionHost, POST_NGCC_MARKER, PRE_NGCC_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {AnalyzedClass} from '../analysis/decoration_analyzer';
|
||||
import {Renderer} from './renderer';
|
||||
|
||||
export class Fesm2015Renderer extends Renderer {
|
||||
constructor(
|
||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||
protected targetPath: string) {
|
||||
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the imports at the top of the file
|
||||
*/
|
||||
addImports(output: MagicString, imports: {name: string; as: string;}[]): void {
|
||||
// The imports get inserted at the very top of the file.
|
||||
imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); });
|
||||
}
|
||||
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
}
|
||||
const insertionPoint = file.statements.reduce((prev, stmt) => {
|
||||
if (ts.isImportDeclaration(stmt) || ts.isImportEqualsDeclaration(stmt) ||
|
||||
ts.isNamespaceImport(stmt)) {
|
||||
return stmt.getEnd();
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
output.appendLeft(insertionPoint, '\n' + constants + '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the definitions to each decorated class
|
||||
*/
|
||||
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void {
|
||||
const classSymbol = this.host.getClassSymbol(analyzedClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Analyzed class does not have a valid symbol: ${analyzedClass.name}`);
|
||||
}
|
||||
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove static decorator properties from classes
|
||||
*/
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void {
|
||||
decoratorsToRemove.forEach((nodesToRemove, containerNode) => {
|
||||
if (ts.isArrayLiteralExpression(containerNode)) {
|
||||
const items = containerNode.elements;
|
||||
if (items.length === nodesToRemove.length) {
|
||||
// Remove the entire statement
|
||||
const statement = findStatement(containerNode);
|
||||
if (statement) {
|
||||
output.remove(statement.getFullStart(), statement.getEnd());
|
||||
}
|
||||
} else {
|
||||
nodesToRemove.forEach(node => {
|
||||
// remove any trailing comma
|
||||
const end = (output.slice(node.getEnd(), node.getEnd() + 1) === ',') ?
|
||||
node.getEnd() + 1 :
|
||||
node.getEnd();
|
||||
output.remove(node.getFullStart(), end);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
declarations: SwitchableVariableDeclaration[]): void {
|
||||
declarations.forEach(declaration => {
|
||||
const start = declaration.initializer.getStart();
|
||||
const end = declaration.initializer.getEnd();
|
||||
const replacement = declaration.initializer.text.replace(PRE_NGCC_MARKER, POST_NGCC_MARKER);
|
||||
outputText.overwrite(start, end, replacement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findStatement(node: ts.Node) {
|
||||
while (node) {
|
||||
if (ts.isExpressionStatement(node)) {
|
||||
return node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
|
@ -9,16 +9,17 @@ import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} fro
|
|||
import {SourceMapConverter, commentRegex, fromJSON, fromMapFileSource, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
||||
import {readFileSync, statSync} from 'fs';
|
||||
import MagicString from 'magic-string';
|
||||
import {basename, dirname} from 'canonical-path';
|
||||
import {basename, dirname, relative, resolve} from 'canonical-path';
|
||||
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../../ngtsc/host';
|
||||
import {translateStatement} from '../../../ngtsc/translator';
|
||||
import {AnalyzedClass, DecorationAnalysis} from '../analysis/decoration_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {NgccImportManager} from './ngcc_import_manager';
|
||||
import {AnalyzedClass, DecorationAnalysis, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
|
||||
interface SourceMapInfo {
|
||||
source: string;
|
||||
|
@ -30,10 +31,6 @@ interface SourceMapInfo {
|
|||
* The results of rendering an analyzed file.
|
||||
*/
|
||||
export interface RenderResult {
|
||||
/**
|
||||
* The file that has been rendered.
|
||||
*/
|
||||
file: DecorationAnalysis;
|
||||
/**
|
||||
* The rendered source file.
|
||||
*/
|
||||
|
@ -67,42 +64,77 @@ export interface FileInfo {
|
|||
export abstract class Renderer {
|
||||
constructor(
|
||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||
protected rewriteCoreImportsTo: ts.SourceFile|null) {}
|
||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||
protected targetPath: string) {}
|
||||
|
||||
renderProgram(
|
||||
program: ts.Program, decorationAnalyses: DecorationAnalyses,
|
||||
switchMarkerAnalyses: SwitchMarkerAnalyses): FileInfo[] {
|
||||
const renderedFiles: FileInfo[] = [];
|
||||
// Transform the source files and source maps.
|
||||
program.getSourceFiles().map(sourceFile => {
|
||||
const decorationAnalysis = decorationAnalyses.get(sourceFile);
|
||||
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
||||
|
||||
// Transform the source files and source maps.
|
||||
if (decorationAnalysis || switchMarkerAnalysis) {
|
||||
const targetPath = resolve(this.targetPath, relative(this.sourcePath, sourceFile.fileName));
|
||||
renderedFiles.push(
|
||||
...this.renderFile(sourceFile, decorationAnalysis, switchMarkerAnalysis, targetPath));
|
||||
}
|
||||
});
|
||||
return renderedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the source code and source-map for an Analyzed file.
|
||||
* @param file The analyzed file to render.
|
||||
* @param decorationAnalysis The analyzed file to render.
|
||||
* @param targetPath The absolute path where the rendered file will be written.
|
||||
*/
|
||||
renderFile(file: DecorationAnalysis, targetPath: string): RenderResult {
|
||||
const importManager =
|
||||
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
||||
const input = this.extractSourceMap(file.sourceFile);
|
||||
|
||||
renderFile(
|
||||
sourceFile: ts.SourceFile, decorationAnalysis: DecorationAnalysis|undefined,
|
||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, targetPath: string): FileInfo[] {
|
||||
const input = this.extractSourceMap(sourceFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
|
||||
file.analyzedClasses.forEach(clazz => {
|
||||
const renderedDefinition = renderDefinitions(file.sourceFile, clazz, importManager);
|
||||
this.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
||||
});
|
||||
if (switchMarkerAnalysis) {
|
||||
this.rewriteSwitchableDeclarations(
|
||||
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
||||
}
|
||||
|
||||
this.addConstants(
|
||||
outputText, renderConstantPool(file.sourceFile, file.constantPool, importManager),
|
||||
file.sourceFile);
|
||||
if (decorationAnalysis) {
|
||||
const importManager =
|
||||
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
|
||||
this.addImports(
|
||||
outputText,
|
||||
importManager.getAllImports(file.sourceFile.fileName, this.rewriteCoreImportsTo));
|
||||
decorationAnalysis.analyzedClasses.forEach(clazz => {
|
||||
const renderedDefinition =
|
||||
renderDefinitions(decorationAnalysis.sourceFile, clazz, importManager);
|
||||
this.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
||||
});
|
||||
|
||||
// TODO: remove contructor param metadata and property decorators (we need info from the
|
||||
// handlers to do this)
|
||||
this.removeDecorators(outputText, decoratorsToRemove);
|
||||
this.addConstants(
|
||||
outputText,
|
||||
renderConstantPool(
|
||||
decorationAnalysis.sourceFile, decorationAnalysis.constantPool, importManager),
|
||||
decorationAnalysis.sourceFile);
|
||||
|
||||
this.rewriteSwitchableDeclarations(outputText, file.sourceFile);
|
||||
this.addImports(
|
||||
outputText, importManager.getAllImports(
|
||||
decorationAnalysis.sourceFile.fileName, this.rewriteCoreImportsTo));
|
||||
|
||||
return this.renderSourceAndMap(file, input, outputText, targetPath);
|
||||
// TODO: remove contructor param metadata and property decorators (we need info from the
|
||||
// handlers to do this)
|
||||
this.removeDecorators(outputText, decoratorsToRemove);
|
||||
}
|
||||
|
||||
const {source, map} = this.renderSourceAndMap(sourceFile, input, outputText, targetPath);
|
||||
const renderedFiles = [source];
|
||||
if (map) {
|
||||
renderedFiles.push(map);
|
||||
}
|
||||
return renderedFiles;
|
||||
}
|
||||
|
||||
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||
|
@ -113,7 +145,8 @@ export abstract class Renderer {
|
|||
protected abstract removeDecorators(
|
||||
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
|
||||
protected abstract rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile): void;
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
declarations: SwitchableVariableDeclaration[]): void;
|
||||
|
||||
/**
|
||||
* Add the decorator nodes that are to be removed to a map
|
||||
|
@ -180,11 +213,11 @@ export abstract class Renderer {
|
|||
* with an appropriate source-map comment pointing to the merged source-map.
|
||||
*/
|
||||
protected renderSourceAndMap(
|
||||
file: DecorationAnalysis, input: SourceMapInfo, output: MagicString,
|
||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString,
|
||||
outputPath: string): RenderResult {
|
||||
const outputMapPath = `${outputPath}.map`;
|
||||
const outputMap = output.generateMap({
|
||||
source: file.sourceFile.fileName,
|
||||
source: sourceFile.fileName,
|
||||
includeContent: true,
|
||||
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
|
||||
// the merge algorithm.
|
||||
|
@ -198,13 +231,11 @@ export abstract class Renderer {
|
|||
|
||||
if (input.isInline) {
|
||||
return {
|
||||
file,
|
||||
source: {path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`},
|
||||
map: null
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
file,
|
||||
source: {
|
||||
path: outputPath,
|
||||
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue