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
|
* 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
|
* 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 * 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 {
|
import {DtsFileTransformer} from '../../../ngtsc/transform';
|
||||||
/**
|
import {DecorationAnalysis} from '../analysis/decoration_analyzer';
|
||||||
* Add the imports at the top of the file
|
import {SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||||
*/
|
import {IMPORT_PREFIX} from '../constants';
|
||||||
addImports(output: MagicString, imports: {name: string; as: string;}[]): void {
|
import {DtsMapper} from '../host/dts_mapper';
|
||||||
// The imports get inserted at the very top of the file.
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); });
|
|
||||||
|
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 {
|
renderFile(
|
||||||
if (constants === '') {
|
sourceFile: ts.SourceFile, decorationAnalysis: DecorationAnalysis|undefined,
|
||||||
return;
|
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, targetPath: string): FileInfo[] {
|
||||||
}
|
const renderedFiles =
|
||||||
const insertionPoint = file.statements.reduce((prev, stmt) => {
|
super.renderFile(sourceFile, decorationAnalysis, switchMarkerAnalysis, targetPath);
|
||||||
if (ts.isImportDeclaration(stmt) || ts.isImportEqualsDeclaration(stmt) ||
|
|
||||||
ts.isNamespaceImport(stmt)) {
|
// Transform the `.d.ts` files.
|
||||||
return stmt.getEnd();
|
// TODO(gkalpak): What about `.d.ts` source maps? (See
|
||||||
}
|
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
|
||||||
return prev;
|
if (decorationAnalysis) {
|
||||||
}, 0);
|
// Create a `DtsFileTransformer` for the source file and record the generated fields, which
|
||||||
output.appendLeft(insertionPoint, '\n' + constants + '\n');
|
// 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});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return renderedFiles;
|
||||||
* 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
* 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
|
* 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 {SourceMapConverter, commentRegex, fromJSON, fromMapFileSource, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
||||||
import {readFileSync, statSync} from 'fs';
|
import {readFileSync, statSync} from 'fs';
|
||||||
import MagicString from 'magic-string';
|
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 {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Decorator} from '../../../ngtsc/host';
|
import {Decorator} from '../../../ngtsc/host';
|
||||||
import {translateStatement} from '../../../ngtsc/translator';
|
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 {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 {
|
interface SourceMapInfo {
|
||||||
source: string;
|
source: string;
|
||||||
|
@ -30,10 +31,6 @@ interface SourceMapInfo {
|
||||||
* The results of rendering an analyzed file.
|
* The results of rendering an analyzed file.
|
||||||
*/
|
*/
|
||||||
export interface RenderResult {
|
export interface RenderResult {
|
||||||
/**
|
|
||||||
* The file that has been rendered.
|
|
||||||
*/
|
|
||||||
file: DecorationAnalysis;
|
|
||||||
/**
|
/**
|
||||||
* The rendered source file.
|
* The rendered source file.
|
||||||
*/
|
*/
|
||||||
|
@ -67,42 +64,77 @@ export interface FileInfo {
|
||||||
export abstract class Renderer {
|
export abstract class Renderer {
|
||||||
constructor(
|
constructor(
|
||||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
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.
|
* 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.
|
* @param targetPath The absolute path where the rendered file will be written.
|
||||||
*/
|
*/
|
||||||
renderFile(file: DecorationAnalysis, targetPath: string): RenderResult {
|
renderFile(
|
||||||
|
sourceFile: ts.SourceFile, decorationAnalysis: DecorationAnalysis|undefined,
|
||||||
|
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, targetPath: string): FileInfo[] {
|
||||||
|
const input = this.extractSourceMap(sourceFile);
|
||||||
|
const outputText = new MagicString(input.source);
|
||||||
|
|
||||||
|
if (switchMarkerAnalysis) {
|
||||||
|
this.rewriteSwitchableDeclarations(
|
||||||
|
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decorationAnalysis) {
|
||||||
const importManager =
|
const importManager =
|
||||||
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
||||||
const input = this.extractSourceMap(file.sourceFile);
|
|
||||||
|
|
||||||
const outputText = new MagicString(input.source);
|
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
|
|
||||||
file.analyzedClasses.forEach(clazz => {
|
decorationAnalysis.analyzedClasses.forEach(clazz => {
|
||||||
const renderedDefinition = renderDefinitions(file.sourceFile, clazz, importManager);
|
const renderedDefinition =
|
||||||
|
renderDefinitions(decorationAnalysis.sourceFile, clazz, importManager);
|
||||||
this.addDefinitions(outputText, clazz, renderedDefinition);
|
this.addDefinitions(outputText, clazz, renderedDefinition);
|
||||||
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addConstants(
|
this.addConstants(
|
||||||
outputText, renderConstantPool(file.sourceFile, file.constantPool, importManager),
|
outputText,
|
||||||
file.sourceFile);
|
renderConstantPool(
|
||||||
|
decorationAnalysis.sourceFile, decorationAnalysis.constantPool, importManager),
|
||||||
|
decorationAnalysis.sourceFile);
|
||||||
|
|
||||||
this.addImports(
|
this.addImports(
|
||||||
outputText,
|
outputText, importManager.getAllImports(
|
||||||
importManager.getAllImports(file.sourceFile.fileName, this.rewriteCoreImportsTo));
|
decorationAnalysis.sourceFile.fileName, this.rewriteCoreImportsTo));
|
||||||
|
|
||||||
// TODO: remove contructor param metadata and property decorators (we need info from the
|
// TODO: remove contructor param metadata and property decorators (we need info from the
|
||||||
// handlers to do this)
|
// handlers to do this)
|
||||||
this.removeDecorators(outputText, decoratorsToRemove);
|
this.removeDecorators(outputText, decoratorsToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
this.rewriteSwitchableDeclarations(outputText, file.sourceFile);
|
const {source, map} = this.renderSourceAndMap(sourceFile, input, outputText, targetPath);
|
||||||
|
const renderedFiles = [source];
|
||||||
return this.renderSourceAndMap(file, input, outputText, targetPath);
|
if (map) {
|
||||||
|
renderedFiles.push(map);
|
||||||
|
}
|
||||||
|
return renderedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||||
|
@ -113,7 +145,8 @@ export abstract class Renderer {
|
||||||
protected abstract removeDecorators(
|
protected abstract removeDecorators(
|
||||||
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
|
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
|
||||||
protected abstract rewriteSwitchableDeclarations(
|
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
|
* 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.
|
* with an appropriate source-map comment pointing to the merged source-map.
|
||||||
*/
|
*/
|
||||||
protected renderSourceAndMap(
|
protected renderSourceAndMap(
|
||||||
file: DecorationAnalysis, input: SourceMapInfo, output: MagicString,
|
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString,
|
||||||
outputPath: string): RenderResult {
|
outputPath: string): RenderResult {
|
||||||
const outputMapPath = `${outputPath}.map`;
|
const outputMapPath = `${outputPath}.map`;
|
||||||
const outputMap = output.generateMap({
|
const outputMap = output.generateMap({
|
||||||
source: file.sourceFile.fileName,
|
source: sourceFile.fileName,
|
||||||
includeContent: true,
|
includeContent: true,
|
||||||
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
|
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
|
||||||
// the merge algorithm.
|
// the merge algorithm.
|
||||||
|
@ -198,13 +231,11 @@ export abstract class Renderer {
|
||||||
|
|
||||||
if (input.isInline) {
|
if (input.isInline) {
|
||||||
return {
|
return {
|
||||||
file,
|
|
||||||
source: {path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`},
|
source: {path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`},
|
||||||
map: null
|
map: null
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
file,
|
|
||||||
source: {
|
source: {
|
||||||
path: outputPath,
|
path: outputPath,
|
||||||
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
|
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
|
||||||
|
|
|
@ -47,7 +47,7 @@ describe('SwitchMarkerAnalyzer', () => {
|
||||||
describe('analyzeProgram()', () => {
|
describe('analyzeProgram()', () => {
|
||||||
it('should check for switchable markers in all the files of the program', () => {
|
it('should check for switchable markers in all the files of the program', () => {
|
||||||
const program = makeProgram(...TEST_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 analyzer = new SwitchMarkerAnalyzer(host);
|
||||||
const analysis = analyzer.analyzeProgram(program);
|
const analysis = analyzer.analyzeProgram(program);
|
||||||
|
|
||||||
|
|
|
@ -5,24 +5,28 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {dirname} from 'canonical-path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import {makeProgram} from '../helpers/utils';
|
import {makeProgram} from '../helpers/utils';
|
||||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
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 {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
|
||||||
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}, transformDts: boolean = false) {
|
||||||
|
const dir = dirname(file.name);
|
||||||
|
const dtsMapper = new DtsMapper(dir, dir);
|
||||||
const program = makeProgram(file);
|
const program = makeProgram(file);
|
||||||
|
const sourceFile = program.getSourceFile(file.name) !;
|
||||||
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
|
const host = new Fesm2015ReflectionHost(false, program.getTypeChecker());
|
||||||
const analyzer = new DecorationAnalyzer(program.getTypeChecker(), host, [''], false);
|
const decorationAnalyses =
|
||||||
const renderer = new Esm2015Renderer(host, false, null);
|
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
||||||
return {analyzer, host, program, renderer};
|
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
|
||||||
}
|
const renderer = new Esm2015Renderer(host, false, null, dir, dir, dtsMapper);
|
||||||
|
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
|
||||||
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 PROGRAM = {
|
const PROGRAM = {
|
||||||
|
@ -133,13 +137,14 @@ export class A {}`);
|
||||||
|
|
||||||
describe('rewriteSwitchableDeclarations', () => {
|
describe('rewriteSwitchableDeclarations', () => {
|
||||||
it('should switch marked declaration initializers', () => {
|
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');
|
const file = program.getSourceFile('some/file.js');
|
||||||
if (file === undefined) {
|
if (file === undefined) {
|
||||||
throw new Error(`Could not find source file`);
|
throw new Error(`Could not find source file`);
|
||||||
}
|
}
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
renderer.rewriteSwitchableDeclarations(output, file);
|
renderer.rewriteSwitchableDeclarations(
|
||||||
|
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
|
||||||
expect(output.toString())
|
expect(output.toString())
|
||||||
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
|
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
|
||||||
expect(output.toString())
|
expect(output.toString())
|
||||||
|
@ -157,10 +162,10 @@ 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, host, program, renderer} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
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');
|
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
|
||||||
|
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
|
||||||
expect(output.toString()).toContain(`
|
expect(output.toString()).toContain(`
|
||||||
export class A {}
|
export class A {}
|
||||||
SOME DEFINITION TEXT
|
SOME DEFINITION TEXT
|
||||||
|
@ -175,10 +180,10 @@ 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, host, program, renderer} = setup(PROGRAM);
|
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = analyzedClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = analyzedClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = analyzedClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
|
@ -234,11 +239,10 @@ 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, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const analyzedFile =
|
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const analyzedFile =
|
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const analyzedFile =
|
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
|
|
|
@ -9,20 +9,19 @@ import * as ts from 'typescript';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import {makeProgram} from '../helpers/utils';
|
import {makeProgram} from '../helpers/utils';
|
||||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||||
|
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||||
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 sourceFile = program.getSourceFile(file.name) !;
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
const analyzer = new DecorationAnalyzer(program.getTypeChecker(), host, [''], false);
|
const decorationAnalyses =
|
||||||
const renderer = new Esm5Renderer(host, false, null);
|
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
||||||
return {analyzer, host, program, renderer};
|
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
|
||||||
}
|
const renderer = new Esm5Renderer(host, false, null, '', '');
|
||||||
|
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
|
||||||
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 PROGRAM = {
|
const PROGRAM = {
|
||||||
|
@ -158,13 +157,14 @@ var A = (function() {`);
|
||||||
|
|
||||||
describe('rewriteSwitchableDeclarations', () => {
|
describe('rewriteSwitchableDeclarations', () => {
|
||||||
it('should switch marked declaration initializers', () => {
|
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');
|
const file = program.getSourceFile('some/file.js');
|
||||||
if (file === undefined) {
|
if (file === undefined) {
|
||||||
throw new Error(`Could not find source file`);
|
throw new Error(`Could not find source file`);
|
||||||
}
|
}
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
renderer.rewriteSwitchableDeclarations(output, file);
|
renderer.rewriteSwitchableDeclarations(
|
||||||
|
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
|
||||||
expect(output.toString())
|
expect(output.toString())
|
||||||
.not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
|
.not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
|
||||||
expect(output.toString())
|
expect(output.toString())
|
||||||
|
@ -182,10 +182,10 @@ 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, host, program, renderer} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
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');
|
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
|
||||||
|
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
|
||||||
expect(output.toString()).toContain(`
|
expect(output.toString()).toContain(`
|
||||||
function A() {}
|
function A() {}
|
||||||
SOME DEFINITION TEXT
|
SOME DEFINITION TEXT
|
||||||
|
@ -199,10 +199,10 @@ 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, host, program, renderer} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = analyzedClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = analyzedClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = analyzedClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
|
@ -257,11 +257,10 @@ 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, host, program, renderer} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const analyzedFile =
|
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const analyzedFile =
|
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.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',
|
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 {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const analyzedFile =
|
|
||||||
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 =
|
||||||
|
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
|
|
|
@ -11,11 +11,13 @@ import * as ts from 'typescript';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||||
import {makeProgram} from '../helpers/utils';
|
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 {Fesm2015ReflectionHost} from '../../src/host/fesm2015_host';
|
||||||
import {Renderer} from '../../src/rendering/renderer';
|
import {Renderer} from '../../src/rendering/renderer';
|
||||||
|
|
||||||
class TestRenderer extends Renderer {
|
class TestRenderer extends Renderer {
|
||||||
|
constructor(host: Fesm2015ReflectionHost) { super(host, false, null, '/src', '/dist'); }
|
||||||
addImports(output: MagicString, imports: {name: string, as: string}[]) {
|
addImports(output: MagicString, imports: {name: string, as: string}[]) {
|
||||||
output.prepend('\n// ADD IMPORTS\n');
|
output.prepend('\n// ADD IMPORTS\n');
|
||||||
}
|
}
|
||||||
|
@ -33,118 +35,113 @@ class TestRenderer extends Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTestRenderer() {
|
function createTestRenderer(file: {name: string, contents: string}) {
|
||||||
const renderer = new TestRenderer({} as Fesm2015ReflectionHost, false, null);
|
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, 'addImports').and.callThrough();
|
||||||
spyOn(renderer, 'addDefinitions').and.callThrough();
|
spyOn(renderer, 'addDefinitions').and.callThrough();
|
||||||
spyOn(renderer, 'removeDecorators').and.callThrough();
|
spyOn(renderer, 'removeDecorators').and.callThrough();
|
||||||
return renderer as jasmine.SpyObj<TestRenderer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function analyze(file: {name: string, contents: string}) {
|
return {renderer, program, decorationAnalyses, switchMarkerAnalyses};
|
||||||
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};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
describe('Renderer', () => {
|
describe('Renderer', () => {
|
||||||
const INPUT_PROGRAM = {
|
const INPUT_PROGRAM = {
|
||||||
name: '/file.js',
|
name: '/src/file.js',
|
||||||
contents:
|
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`
|
`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({
|
const INPUT_PROGRAM_MAP = fromObject({
|
||||||
'version': 3,
|
'version': 3,
|
||||||
'file': '/file.js',
|
'file': '/src/file.js',
|
||||||
'sourceRoot': '',
|
'sourceRoot': '',
|
||||||
'sources': ['/file.ts'],
|
'sources': ['/src/file.ts'],
|
||||||
'names': [],
|
'names': [],
|
||||||
'mappings':
|
'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',
|
'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': [
|
'sourcesContent': [INPUT_PROGRAM.contents]
|
||||||
'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}'
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const RENDERED_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;
|
INPUT_PROGRAM.contents;
|
||||||
|
|
||||||
const OUTPUT_PROGRAM_MAP = fromObject({
|
const OUTPUT_PROGRAM_MAP = fromObject({
|
||||||
'version': 3,
|
'version': 3,
|
||||||
'file': '/output_file.js',
|
'file': '/dist/file.js',
|
||||||
'sources': ['/file.js'],
|
'sources': ['/src/file.js'],
|
||||||
'sourcesContent': [
|
'sourcesContent': [INPUT_PROGRAM.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'
|
|
||||||
],
|
|
||||||
'names': [],
|
'names': [],
|
||||||
'mappings': ';;;;;;;;;;AAAA;;;;;;;;;'
|
'mappings': ';;;;;;;;AAAA;;;;;;;;;'
|
||||||
});
|
});
|
||||||
|
|
||||||
const MERGED_OUTPUT_PROGRAM_MAP = fromObject({
|
const MERGED_OUTPUT_PROGRAM_MAP = fromObject({
|
||||||
'version': 3,
|
'version': 3,
|
||||||
'sources': ['/file.ts'],
|
'sources': ['/src/file.ts'],
|
||||||
'names': [],
|
'names': [],
|
||||||
'mappings': ';;;;;;;;;;AAAA',
|
'mappings': ';;;;;;;;AAAA',
|
||||||
'file': '/output_file.js',
|
'file': '/dist/file.js',
|
||||||
'sourcesContent': [
|
'sourcesContent': [INPUT_PROGRAM.contents]
|
||||||
'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}'
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renderFile()', () => {
|
describe('renderProgram()', () => {
|
||||||
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, program, decorationAnalyses, switchMarkerAnalyses} =
|
||||||
const {analyzedFiles} = analyze(INPUT_PROGRAM);
|
createTestRenderer(INPUT_PROGRAM);
|
||||||
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
|
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
|
||||||
expect(result.source.path).toEqual('/output_file.js');
|
expect(result[0].path).toEqual('/dist/file.js');
|
||||||
expect(result.source.contents)
|
expect(result[0].contents)
|
||||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/output_file.js.map'));
|
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
||||||
expect(result.map !.path).toEqual('/output_file.js.map');
|
expect(result[1].path).toEqual('/dist/file.js.map');
|
||||||
expect(result.map !.contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON());
|
expect(result[1].contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON());
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
|
||||||
const {analyzedFiles} = analyze(INPUT_PROGRAM);
|
createTestRenderer(INPUT_PROGRAM);
|
||||||
renderer.renderFile(analyzedFiles[0], '/output_file.js');
|
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
|
||||||
expect(renderer.addImports.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||||
expect(renderer.addImports.calls.first().args[1]).toEqual([
|
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||||
|
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||||
{name: '@angular/core', as: 'ɵngcc0'}
|
{name: '@angular/core', as: 'ɵngcc0'}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
|
||||||
const {analyzedFiles} = analyze(INPUT_PROGRAM);
|
createTestRenderer(INPUT_PROGRAM);
|
||||||
renderer.renderFile(analyzedFiles[0], '/output_file.js');
|
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
|
||||||
expect(renderer.addDefinitions.calls.first().args[0].toString())
|
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
|
||||||
.toEqual(RENDERED_CONTENTS);
|
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||||
expect(renderer.addDefinitions.calls.first().args[1])
|
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
|
||||||
.toBe(analyzedFiles[0].analyzedClasses[0]);
|
name: 'A',
|
||||||
expect(renderer.addDefinitions.calls.first().args[2])
|
decorators: [jasmine.objectContaining({name: 'Directive'})],
|
||||||
|
}));
|
||||||
|
expect(addDefinitionsSpy.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] });`);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
|
||||||
const {analyzedFiles} = analyze(INPUT_PROGRAM);
|
createTestRenderer(INPUT_PROGRAM);
|
||||||
renderer.renderFile(analyzedFiles[0], '/output_file.js');
|
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
|
||||||
expect(renderer.removeDecorators.calls.first().args[0].toString())
|
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
|
||||||
.toEqual(RENDERED_CONTENTS);
|
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||||
|
|
||||||
// Each map key is the TS node of the decorator container
|
// 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
|
// 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());
|
const keys = Array.from(map.keys());
|
||||||
expect(keys.length).toEqual(1);
|
expect(keys.length).toEqual(1);
|
||||||
expect(keys[0].getText())
|
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',
|
it('should merge any inline source map from the original file and write the output as an inline source map',
|
||||||
() => {
|
() => {
|
||||||
const renderer = createTestRenderer();
|
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer({
|
||||||
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()
|
||||||
});
|
});
|
||||||
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
|
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
|
||||||
expect(result.source.path).toEqual('/output_file.js');
|
expect(result[0].path).toEqual('/dist/file.js');
|
||||||
expect(result.source.contents)
|
expect(result[0].contents)
|
||||||
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
|
.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',
|
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
|
// Mock out reading the map file from disk
|
||||||
const readFileSyncSpy =
|
|
||||||
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
|
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
|
||||||
const renderer = createTestRenderer();
|
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer({
|
||||||
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'
|
||||||
});
|
});
|
||||||
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
|
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
|
||||||
expect(result.source.path).toEqual('/output_file.js');
|
expect(result[0].path).toEqual('/dist/file.js');
|
||||||
expect(result.source.contents)
|
expect(result[0].contents)
|
||||||
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/output_file.js.map'));
|
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map'));
|
||||||
expect(result.map !.path).toEqual('/output_file.js.map');
|
expect(result[1].path).toEqual('/dist/file.js.map');
|
||||||
expect(result.map !.contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON());
|
expect(result[1].contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue