refactor(ivy): make shim generation generic in ngtsc (#26495)
This commit refactors the shim host to be agnostic to the shims being generated, and provides an API for generating additional shims besides the .ngfactory.js. This will be used in a following commit to generate .ngsummary.js shims. Testing strategy: this refactor introduces no new behavior, so it's sufficient that the existing tests for factory shim generation continue to pass. PR Close #26495
This commit is contained in:
parent
0b885ecaf7
commit
ce8053103e
|
@ -51,13 +51,17 @@ export class NgtscProgram implements api.Program {
|
||||||
this.resourceLoader = host.readResource !== undefined ?
|
this.resourceLoader = host.readResource !== undefined ?
|
||||||
new HostResourceLoader(host.readResource.bind(host)) :
|
new HostResourceLoader(host.readResource.bind(host)) :
|
||||||
new FileResourceLoader();
|
new FileResourceLoader();
|
||||||
const shouldGenerateFactories = options.allowEmptyCodegenFiles || false;
|
const shouldGenerateShims = options.allowEmptyCodegenFiles || false;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
let rootFiles = [...rootNames];
|
let rootFiles = [...rootNames];
|
||||||
if (shouldGenerateFactories) {
|
if (shouldGenerateShims) {
|
||||||
const generator = new FactoryGenerator();
|
// Summary generation.
|
||||||
const factoryFileMap = generator.computeFactoryFileMap(rootNames);
|
|
||||||
rootFiles.push(...Array.from(factoryFileMap.keys()));
|
// Factory generation.
|
||||||
|
const factoryGenerator = FactoryGenerator.forRootFiles(rootNames);
|
||||||
|
const factoryFileMap = factoryGenerator.factoryFileMap;
|
||||||
|
const factoryFileNames = Array.from(factoryFileMap.keys());
|
||||||
|
rootFiles.push(...factoryFileNames);
|
||||||
this.factoryToSourceInfo = new Map<string, FactoryInfo>();
|
this.factoryToSourceInfo = new Map<string, FactoryInfo>();
|
||||||
this.sourceToFactorySymbols = new Map<string, Set<string>>();
|
this.sourceToFactorySymbols = new Map<string, Set<string>>();
|
||||||
factoryFileMap.forEach((sourceFilePath, factoryPath) => {
|
factoryFileMap.forEach((sourceFilePath, factoryPath) => {
|
||||||
|
@ -65,7 +69,7 @@ export class NgtscProgram implements api.Program {
|
||||||
this.sourceToFactorySymbols !.set(sourceFilePath, moduleSymbolNames);
|
this.sourceToFactorySymbols !.set(sourceFilePath, moduleSymbolNames);
|
||||||
this.factoryToSourceInfo !.set(factoryPath, {sourceFilePath, moduleSymbolNames});
|
this.factoryToSourceInfo !.set(factoryPath, {sourceFilePath, moduleSymbolNames});
|
||||||
});
|
});
|
||||||
this.host = new GeneratedShimsHostWrapper(host, generator, factoryFileMap);
|
this.host = new GeneratedShimsHostWrapper(host, [factoryGenerator]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tsProgram =
|
this.tsProgram =
|
||||||
|
|
|
@ -8,6 +8,5 @@
|
||||||
|
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
|
|
||||||
export {FactoryGenerator} from './src/generator';
|
export {FactoryGenerator, FactoryInfo, generatedFactoryTransform} from './src/factory_generator';
|
||||||
export {GeneratedShimsHostWrapper} from './src/host';
|
export {GeneratedShimsHostWrapper} from './src/host';
|
||||||
export {FactoryInfo, generatedFactoryTransform} from './src/transform';
|
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
/**
|
||||||
|
* @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 path from 'path';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {relativePathBetween} from '../../util/src/path';
|
||||||
|
|
||||||
|
import {ShimGenerator} from './host';
|
||||||
|
import {isNonDeclarationTsFile} from './util';
|
||||||
|
|
||||||
|
const TS_DTS_SUFFIX = /(\.d)?\.ts$/;
|
||||||
|
const STRIP_NG_FACTORY = /(.*)NgFactory$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates ts.SourceFiles which contain variable declarations for NgFactories for every exported
|
||||||
|
* class of an input ts.SourceFile.
|
||||||
|
*/
|
||||||
|
export class FactoryGenerator implements ShimGenerator {
|
||||||
|
private constructor(private map: Map<string, string>) {}
|
||||||
|
|
||||||
|
get factoryFileMap(): Map<string, string> { return this.map; }
|
||||||
|
|
||||||
|
getOriginalSourceOfShim(fileName: string): string|null { return this.map.get(fileName) || null; }
|
||||||
|
|
||||||
|
generate(original: ts.SourceFile, genFilePath: string): ts.SourceFile {
|
||||||
|
const relativePathToSource =
|
||||||
|
'./' + path.posix.basename(original.fileName).replace(TS_DTS_SUFFIX, '');
|
||||||
|
// Collect a list of classes that need to have factory types emitted for them. This list is
|
||||||
|
// overly broad as at this point the ts.TypeChecker hasn't been created, and can't be used to
|
||||||
|
// semantically understand which decorated types are actually decorated with Angular decorators.
|
||||||
|
//
|
||||||
|
// The exports generated here are pruned in the factory transform during emit.
|
||||||
|
const symbolNames = original
|
||||||
|
.statements
|
||||||
|
// Pick out top level class declarations...
|
||||||
|
.filter(ts.isClassDeclaration)
|
||||||
|
// which are named, exported, and have decorators.
|
||||||
|
.filter(
|
||||||
|
decl => isExported(decl) && decl.decorators !== undefined &&
|
||||||
|
decl.name !== undefined)
|
||||||
|
// Grab the symbol name.
|
||||||
|
.map(decl => decl.name !.text);
|
||||||
|
|
||||||
|
// For each symbol name, generate a constant export of the corresponding NgFactory.
|
||||||
|
// This will encompass a lot of symbols which don't need factories, but that's okay
|
||||||
|
// because it won't miss any that do.
|
||||||
|
const varLines = symbolNames.map(
|
||||||
|
name => `export const ${name}NgFactory = new i0.ɵNgModuleFactory(${name});`);
|
||||||
|
const sourceText = [
|
||||||
|
// This might be incorrect if the current package being compiled is Angular core, but it's
|
||||||
|
// okay to leave in at type checking time. TypeScript can handle this reference via its path
|
||||||
|
// mapping, but downstream bundlers can't. If the current package is core itself, this will be
|
||||||
|
// replaced in the factory transformer before emit.
|
||||||
|
`import * as i0 from '@angular/core';`,
|
||||||
|
`import {${symbolNames.join(', ')}} from '${relativePathToSource}';`,
|
||||||
|
...varLines,
|
||||||
|
].join('\n');
|
||||||
|
return ts.createSourceFile(
|
||||||
|
genFilePath, sourceText, original.languageVersion, true, ts.ScriptKind.TS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static forRootFiles(files: ReadonlyArray<string>): FactoryGenerator {
|
||||||
|
const map = new Map<string, string>();
|
||||||
|
files.filter(sourceFile => isNonDeclarationTsFile(sourceFile))
|
||||||
|
.forEach(sourceFile => map.set(sourceFile.replace(/\.ts$/, '.ngfactory.ts'), sourceFile));
|
||||||
|
return new FactoryGenerator(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExported(decl: ts.Declaration): boolean {
|
||||||
|
return decl.modifiers !== undefined &&
|
||||||
|
decl.modifiers.some(mod => mod.kind == ts.SyntaxKind.ExportKeyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FactoryInfo {
|
||||||
|
sourceFilePath: string;
|
||||||
|
moduleSymbolNames: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generatedFactoryTransform(
|
||||||
|
factoryMap: Map<string, FactoryInfo>,
|
||||||
|
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
||||||
|
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||||
|
return (file: ts.SourceFile): ts.SourceFile => {
|
||||||
|
return transformFactorySourceFile(factoryMap, context, coreImportsFrom, file);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformFactorySourceFile(
|
||||||
|
factoryMap: Map<string, FactoryInfo>, context: ts.TransformationContext,
|
||||||
|
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
||||||
|
// If this is not a generated file, it won't have factory info associated with it.
|
||||||
|
if (!factoryMap.has(file.fileName)) {
|
||||||
|
// Don't transform non-generated code.
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {moduleSymbolNames, sourceFilePath} = factoryMap.get(file.fileName) !;
|
||||||
|
|
||||||
|
const clone = ts.getMutableClone(file);
|
||||||
|
|
||||||
|
const transformedStatements = file.statements.map(stmt => {
|
||||||
|
if (coreImportsFrom !== null && ts.isImportDeclaration(stmt) &&
|
||||||
|
ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === '@angular/core') {
|
||||||
|
const path = relativePathBetween(sourceFilePath, coreImportsFrom.fileName);
|
||||||
|
if (path !== null) {
|
||||||
|
return ts.updateImportDeclaration(
|
||||||
|
stmt, stmt.decorators, stmt.modifiers, stmt.importClause, ts.createStringLiteral(path));
|
||||||
|
} else {
|
||||||
|
return ts.createNotEmittedStatement(stmt);
|
||||||
|
}
|
||||||
|
} else if (ts.isVariableStatement(stmt) && stmt.declarationList.declarations.length === 1) {
|
||||||
|
const decl = stmt.declarationList.declarations[0];
|
||||||
|
if (ts.isIdentifier(decl.name)) {
|
||||||
|
const match = STRIP_NG_FACTORY.exec(decl.name.text);
|
||||||
|
if (match === null || !moduleSymbolNames.has(match[1])) {
|
||||||
|
// Remove the given factory as it wasn't actually for an NgModule.
|
||||||
|
return ts.createNotEmittedStatement(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stmt;
|
||||||
|
} else {
|
||||||
|
return stmt;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!transformedStatements.some(ts.isVariableStatement)) {
|
||||||
|
// If the resulting file has no factories, include an empty export to
|
||||||
|
// satisfy closure compiler.
|
||||||
|
transformedStatements.push(ts.createVariableStatement(
|
||||||
|
[ts.createModifier(ts.SyntaxKind.ExportKeyword)],
|
||||||
|
ts.createVariableDeclarationList(
|
||||||
|
[ts.createVariableDeclaration('ɵNonEmptyModule', undefined, ts.createTrue())],
|
||||||
|
ts.NodeFlags.Const)));
|
||||||
|
}
|
||||||
|
clone.statements = ts.createNodeArray(transformedStatements);
|
||||||
|
return clone;
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
const TS_DTS_SUFFIX = /(\.d)?\.ts$/;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates ts.SourceFiles which contain variable declarations for NgFactories for every exported
|
|
||||||
* class of an input ts.SourceFile.
|
|
||||||
*/
|
|
||||||
export class FactoryGenerator {
|
|
||||||
factoryFor(original: ts.SourceFile, genFilePath: string): ts.SourceFile {
|
|
||||||
const relativePathToSource =
|
|
||||||
'./' + path.posix.basename(original.fileName).replace(TS_DTS_SUFFIX, '');
|
|
||||||
// Collect a list of classes that need to have factory types emitted for them.
|
|
||||||
const symbolNames = original
|
|
||||||
.statements
|
|
||||||
// Pick out top level class declarations...
|
|
||||||
.filter(ts.isClassDeclaration)
|
|
||||||
// which are named, exported, and have decorators.
|
|
||||||
.filter(
|
|
||||||
decl => isExported(decl) && decl.decorators !== undefined &&
|
|
||||||
decl.name !== undefined)
|
|
||||||
// Grab the symbol name.
|
|
||||||
.map(decl => decl.name !.text);
|
|
||||||
|
|
||||||
// For each symbol name, generate a constant export of the corresponding NgFactory.
|
|
||||||
// This will encompass a lot of symbols which don't need factories, but that's okay
|
|
||||||
// because it won't miss any that do.
|
|
||||||
const varLines = symbolNames.map(
|
|
||||||
name => `export const ${name}NgFactory = new i0.ɵNgModuleFactory(${name});`);
|
|
||||||
const sourceText = [
|
|
||||||
// This might be incorrect if the current package being compiled is Angular core, but it's
|
|
||||||
// okay to leave in at type checking time. TypeScript can handle this reference via its path
|
|
||||||
// mapping, but downstream bundlers can't. If the current package is core itself, this will be
|
|
||||||
// replaced in the factory transformer before emit.
|
|
||||||
`import * as i0 from '@angular/core';`,
|
|
||||||
`import {${symbolNames.join(', ')}} from '${relativePathToSource}';`,
|
|
||||||
...varLines,
|
|
||||||
].join('\n');
|
|
||||||
return ts.createSourceFile(
|
|
||||||
genFilePath, sourceText, original.languageVersion, true, ts.ScriptKind.TS);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeFactoryFileMap(files: ReadonlyArray<string>): Map<string, string> {
|
|
||||||
const map = new Map<string, string>();
|
|
||||||
files.filter(sourceFile => !sourceFile.endsWith('.d.ts'))
|
|
||||||
.forEach(sourceFile => map.set(sourceFile.replace(/\.ts$/, '.ngfactory.ts'), sourceFile));
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isExported(decl: ts.Declaration): boolean {
|
|
||||||
return decl.modifiers !== undefined &&
|
|
||||||
decl.modifiers.some(mod => mod.kind == ts.SyntaxKind.ExportKeyword);
|
|
||||||
}
|
|
|
@ -9,15 +9,26 @@
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {FactoryGenerator} from './generator';
|
export interface ShimGenerator {
|
||||||
|
/**
|
||||||
|
* Get the original source file for the given shim path, the contents of which determine the
|
||||||
|
* contents of the shim file.
|
||||||
|
*
|
||||||
|
* If this returns `null` then the given file was not a shim file handled by this generator.
|
||||||
|
*/
|
||||||
|
getOriginalSourceOfShim(fileName: string): string|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a shim's `ts.SourceFile` for the given original file.
|
||||||
|
*/
|
||||||
|
generate(original: ts.SourceFile, genFileName: string): ts.SourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around a `ts.CompilerHost` which supports generated files.
|
* A wrapper around a `ts.CompilerHost` which supports generated files.
|
||||||
*/
|
*/
|
||||||
export class GeneratedShimsHostWrapper implements ts.CompilerHost {
|
export class GeneratedShimsHostWrapper implements ts.CompilerHost {
|
||||||
constructor(
|
constructor(private delegate: ts.CompilerHost, private shimGenerators: ShimGenerator[]) {
|
||||||
private delegate: ts.CompilerHost, private generator: FactoryGenerator,
|
|
||||||
private factoryToSourceMap: Map<string, string>) {
|
|
||||||
if (delegate.resolveTypeReferenceDirectives) {
|
if (delegate.resolveTypeReferenceDirectives) {
|
||||||
// Backward compatibility with TypeScript 2.9 and older since return
|
// Backward compatibility with TypeScript 2.9 and older since return
|
||||||
// type has changed from (ts.ResolvedTypeReferenceDirective | undefined)[]
|
// type has changed from (ts.ResolvedTypeReferenceDirective | undefined)[]
|
||||||
|
@ -38,14 +49,20 @@ export class GeneratedShimsHostWrapper implements ts.CompilerHost {
|
||||||
onError?: ((message: string) => void)|undefined,
|
onError?: ((message: string) => void)|undefined,
|
||||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||||
const canonical = this.getCanonicalFileName(fileName);
|
const canonical = this.getCanonicalFileName(fileName);
|
||||||
if (this.factoryToSourceMap.has(canonical)) {
|
for (let i = 0; i < this.shimGenerators.length; i++) {
|
||||||
const sourceFileName = this.getCanonicalFileName(this.factoryToSourceMap.get(canonical) !);
|
const generator = this.shimGenerators[i];
|
||||||
const sourceFile = this.delegate.getSourceFile(
|
const originalFile = generator.getOriginalSourceOfShim(canonical);
|
||||||
sourceFileName, languageVersion, onError, shouldCreateNewSourceFile);
|
if (originalFile !== null) {
|
||||||
if (sourceFile === undefined) {
|
// This shim generator has recognized the filename being requested, and is now responsible
|
||||||
|
// for generating its contents, based on the contents of the original file it has requested.
|
||||||
|
const originalSource = this.delegate.getSourceFile(
|
||||||
|
originalFile, languageVersion, onError, shouldCreateNewSourceFile);
|
||||||
|
if (originalSource === undefined) {
|
||||||
|
// The original requested file doesn't exist, so the shim cannot exist either.
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.generator.factoryFor(sourceFile, fileName);
|
return generator.generate(originalSource, fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this.delegate.getSourceFile(
|
return this.delegate.getSourceFile(
|
||||||
fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||||
|
@ -75,7 +92,11 @@ export class GeneratedShimsHostWrapper implements ts.CompilerHost {
|
||||||
getNewLine(): string { return this.delegate.getNewLine(); }
|
getNewLine(): string { return this.delegate.getNewLine(); }
|
||||||
|
|
||||||
fileExists(fileName: string): boolean {
|
fileExists(fileName: string): boolean {
|
||||||
return this.factoryToSourceMap.has(fileName) || this.delegate.fileExists(fileName);
|
const canonical = this.getCanonicalFileName(fileName);
|
||||||
|
// Consider the file as existing whenever 1) it really does exist in the delegate host, or
|
||||||
|
// 2) at least one of the shim generators recognizes it.
|
||||||
|
return this.delegate.fileExists(fileName) ||
|
||||||
|
this.shimGenerators.some(gen => gen.getOriginalSourceOfShim(canonical) !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
|
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
import {relativePathBetween} from '../../util/src/path';
|
|
||||||
|
|
||||||
const STRIP_NG_FACTORY = /(.*)NgFactory$/;
|
|
||||||
|
|
||||||
export interface FactoryInfo {
|
|
||||||
sourceFilePath: string;
|
|
||||||
moduleSymbolNames: Set<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generatedFactoryTransform(
|
|
||||||
factoryMap: Map<string, FactoryInfo>,
|
|
||||||
coreImportsFrom: ts.SourceFile | null): ts.TransformerFactory<ts.SourceFile> {
|
|
||||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
|
||||||
return (file: ts.SourceFile): ts.SourceFile => {
|
|
||||||
return transformFactorySourceFile(factoryMap, context, coreImportsFrom, file);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformFactorySourceFile(
|
|
||||||
factoryMap: Map<string, FactoryInfo>, context: ts.TransformationContext,
|
|
||||||
coreImportsFrom: ts.SourceFile | null, file: ts.SourceFile): ts.SourceFile {
|
|
||||||
// If this is not a generated file, it won't have factory info associated with it.
|
|
||||||
if (!factoryMap.has(file.fileName)) {
|
|
||||||
// Don't transform non-generated code.
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {moduleSymbolNames, sourceFilePath} = factoryMap.get(file.fileName) !;
|
|
||||||
|
|
||||||
const clone = ts.getMutableClone(file);
|
|
||||||
|
|
||||||
const transformedStatements = file.statements.map(stmt => {
|
|
||||||
if (coreImportsFrom !== null && ts.isImportDeclaration(stmt) &&
|
|
||||||
ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === '@angular/core') {
|
|
||||||
const path = relativePathBetween(sourceFilePath, coreImportsFrom.fileName);
|
|
||||||
if (path !== null) {
|
|
||||||
return ts.updateImportDeclaration(
|
|
||||||
stmt, stmt.decorators, stmt.modifiers, stmt.importClause, ts.createStringLiteral(path));
|
|
||||||
} else {
|
|
||||||
return ts.createNotEmittedStatement(stmt);
|
|
||||||
}
|
|
||||||
} else if (ts.isVariableStatement(stmt) && stmt.declarationList.declarations.length === 1) {
|
|
||||||
const decl = stmt.declarationList.declarations[0];
|
|
||||||
if (ts.isIdentifier(decl.name)) {
|
|
||||||
const match = STRIP_NG_FACTORY.exec(decl.name.text);
|
|
||||||
if (match === null || !moduleSymbolNames.has(match[1])) {
|
|
||||||
// Remove the given factory as it wasn't actually for an NgModule.
|
|
||||||
return ts.createNotEmittedStatement(stmt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stmt;
|
|
||||||
} else {
|
|
||||||
return stmt;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!transformedStatements.some(ts.isVariableStatement)) {
|
|
||||||
// If the resulting file has no factories, include an empty export to
|
|
||||||
// satisfy closure compiler.
|
|
||||||
transformedStatements.push(ts.createVariableStatement(
|
|
||||||
[ts.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
||||||
ts.createVariableDeclarationList(
|
|
||||||
[ts.createVariableDeclaration('ɵNonEmptyModule', undefined, ts.createTrue())],
|
|
||||||
ts.NodeFlags.Const)));
|
|
||||||
}
|
|
||||||
clone.statements = ts.createNodeArray(transformedStatements);
|
|
||||||
return clone;
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TS_FILE = /\.tsx?$/;
|
||||||
|
const D_TS_FILE = /\.d\.ts$/;
|
||||||
|
|
||||||
|
export function isNonDeclarationTsFile(file: string): boolean {
|
||||||
|
return TS_FILE.exec(file) !== null && D_TS_FILE.exec(file) === null;
|
||||||
|
}
|
Loading…
Reference in New Issue