refactor(compiler-cli): cleanup API for transformer based ngc

This is in preparation for watch mode.
This commit is contained in:
Tobias Bosch 2017-08-09 13:45:45 -07:00 committed by Hans
parent cac130eff9
commit 27d901a51d
14 changed files with 359 additions and 552 deletions

View File

@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
export * from './src/transformers/api';
export * from './src/transformers/entry_points';
export {performCompilation} from './src/perform-compile';
export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform-compile';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';

View File

@ -9,7 +9,7 @@
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
import * as ts from 'typescript';
import {Diagnostic, DiagnosticCategory} from '../transformers/api';
import {Diagnostic} from '../transformers/api';
interface FactoryInfo {
source: ts.SourceFile;
@ -143,7 +143,7 @@ export class TypeChecker {
const diagnosticsList = diagnosticsFor(fileName);
diagnosticsList.push({
message: diagnosticMessageToString(diagnostic.messageText),
category: diagnosticCategoryConverter(diagnostic.category), span
category: diagnostic.category, span
});
}
}
@ -166,11 +166,6 @@ function diagnosticMessageToString(message: ts.DiagnosticMessageChain | string):
return ts.flattenDiagnosticMessageText(message, '\n');
}
function diagnosticCategoryConverter(kind: ts.DiagnosticCategory) {
// The diagnostics kind matches ts.DiagnosticCategory. Review this code if this changes.
return kind as any as DiagnosticCategory;
}
function createFactoryInfo(emitter: TypeScriptEmitter, file: GeneratedFile): FactoryInfo {
const {sourceText, context} =
emitter.emitStatementsAndContext(file.srcFileUrl, file.genFileUrl, file.stmts !);

View File

@ -14,15 +14,87 @@ import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ngc from './ngc';
import * as api from './transformers/api';
import * as ngc from './transformers/entry_points';
import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile';
import {isSyntaxError} from '@angular/compiler';
import {readConfiguration} from './perform-compile';
import {CodeGenerator} from './codegen';
function codegen(
export function main(
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
const parsedArgs = require('minimist')(args);
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
if (configErrors.length) {
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
}
if (options.disableTransformerPipeline) {
return disabledTransformerPipelineNgcMain(parsedArgs, consoleError);
}
const {diagnostics: compileDiags} = performCompilation(rootNames, options);
return Promise.resolve(reportErrorsAndExit(options, compileDiags, consoleError));
}
export function mainSync(
args: string[], consoleError: (s: string) => void = console.error): number {
const parsedArgs = require('minimist')(args);
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
if (configErrors.length) {
return reportErrorsAndExit(options, configErrors, consoleError);
}
const {diagnostics: compileDiags} = performCompilation(rootNames, options);
return reportErrorsAndExit(options, compileDiags, consoleError);
}
function readCommandLineAndConfiguration(args: any): ParsedConfiguration {
const project = args.p || args.project || '.';
const allDiagnostics: Diagnostics = [];
const config = readConfiguration(project);
const options = mergeCommandLineParams(args, config.options);
return {rootNames: config.rootNames, options, errors: config.errors};
}
function reportErrorsAndExit(
options: api.CompilerOptions, allDiagnostics: Diagnostics,
consoleError: (s: string) => void = console.error): number {
const exitCode = allDiagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0;
if (allDiagnostics.length) {
consoleError(formatDiagnostics(options, allDiagnostics));
}
return exitCode;
}
function mergeCommandLineParams(
cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions {
// TODO: also merge in tsc command line parameters by calling
// ts.readCommandLine.
if (cliArgs.i18nFile) options.i18nInFile = cliArgs.i18nFile;
if (cliArgs.i18nFormat) options.i18nInFormat = cliArgs.i18nFormat;
if (cliArgs.locale) options.i18nInLocale = cliArgs.locale;
const mt = cliArgs.missingTranslation;
if (mt === 'error' || mt === 'warning' || mt === 'ignore') {
options.i18nInMissingTranslations = mt;
}
return options;
}
function disabledTransformerPipelineNgcMain(
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
const cliOptions = new tsc.NgcCliOptions(args);
const project = args.p || args.project || '.';
return tsc.main(project, cliOptions, disabledTransformerPipelineCodegen)
.then(() => 0)
.catch(e => {
if (e instanceof tsc.UserError || isSyntaxError(e)) {
consoleError(e.message);
} else {
consoleError(e.stack);
}
return Promise.resolve(1);
});
}
function disabledTransformerPipelineCodegen(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) {
if (ngOptions.enableSummariesForJit === undefined) {
@ -32,38 +104,8 @@ function codegen(
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
}
export function main(
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
const project = args.p || args.project || '.';
const cliOptions = new tsc.NgcCliOptions(args);
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
if (e instanceof tsc.UserError || isSyntaxError(e)) {
consoleError(e.message);
return Promise.resolve(1);
} else {
consoleError(e.stack);
consoleError('Compilation failed');
return Promise.resolve(1);
}
});
}
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2);
const parsedArgs = require('minimist')(args);
const project = parsedArgs.p || parsedArgs.project || '.';
const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project;
// file names in tsconfig are resolved relative to this absolute path
const basePath = path.resolve(process.cwd(), projectDir);
const {ngOptions} = readConfiguration(project, basePath);
if (ngOptions.disableTransformerPipeline) {
main(parsedArgs).then((exitCode: number) => process.exit(exitCode));
} else {
process.exit(ngc.main(args, s => console.error(s)));
}
main(args).then((exitCode: number) => process.exitCode = exitCode);
}

View File

@ -1,68 +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
*/
// Must be imported first, because Angular decorators throw on load.
import 'reflect-metadata';
import {isSyntaxError} from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
import {performCompilation, readConfiguration, throwOnDiagnostics} from './perform-compile';
import {CompilerOptions} from './transformers/api';
export function main(
args: string[], consoleError: (s: string) => void = console.error,
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics): number {
try {
const parsedArgs = require('minimist')(args);
const project = parsedArgs.p || parsedArgs.project || '.';
const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project;
// file names in tsconfig are resolved relative to this absolute path
const basePath = path.resolve(process.cwd(), projectDir);
const {parsed, ngOptions} = readConfiguration(project, basePath, checkFunc);
// CLI arguments can override the i18n options
const ngcOptions = mergeCommandLine(parsedArgs, ngOptions);
const res = performCompilation(
basePath, parsed.fileNames, parsed.options, ngcOptions, consoleError, checkFunc);
return res.errorCode;
} catch (e) {
if (isSyntaxError(e)) {
consoleError(e.message);
return 1;
}
consoleError(e.stack);
consoleError('Compilation failed');
return 2;
}
}
// Merge command line parameters
function mergeCommandLine(
parsedArgs: {[k: string]: string}, options: CompilerOptions): CompilerOptions {
if (parsedArgs.i18nFile) options.i18nInFile = parsedArgs.i18nFile;
if (parsedArgs.i18nFormat) options.i18nInFormat = parsedArgs.i18nFormat;
if (parsedArgs.locale) options.i18nInLocale = parsedArgs.locale;
const mt = parsedArgs.missingTranslation;
if (mt === 'error' || mt === 'warning' || mt === 'ignore') {
options.i18nInMissingTranslations = mt;
}
return options;
}
// CLI entry point
if (require.main === module) {
process.exit(main(process.argv.slice(2), s => console.error(s)));
}

View File

@ -17,24 +17,25 @@ import * as ng from './transformers/entry_points';
const TS_EXT = /\.ts$/;
export type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
return diagnostic && (diagnostic.file || diagnostic.messageText);
}
function formatDiagnostics(cwd: string, diags: Diagnostics): string {
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
if (diags && diags.length) {
if (isTsDiagnostics(diags)) {
return ts.formatDiagnostics(diags, {
getCurrentDirectory: () => cwd,
const tsFormatHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory: () => options.basePath || process.cwd(),
getCanonicalFileName: fileName => fileName,
getNewLine: () => ts.sys.newLine
});
} else {
};
return diags
.map(d => {
let res = api.DiagnosticCategory[d.category];
if (isTsDiagnostic(d)) {
return ts.formatDiagnostics([d], tsFormatHost);
} else {
let res = ts.DiagnosticCategory[d.category];
if (d.span) {
res +=
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
@ -45,134 +46,133 @@ function formatDiagnostics(cwd: string, diags: Diagnostics): string {
res += `: ${d.message}\n`;
}
return res;
}
})
.join();
}
} else
return '';
}
/**
* Throw a syntax error exception with a message formatted for output
* if the args parameter contains diagnostics errors.
*
* @param cwd The directory to report error as relative to.
* @param args A list of potentially empty diagnostic errors.
*/
export function throwOnDiagnostics(cwd: string, ...args: Diagnostics[]) {
if (args.some(diags => !!(diags && diags[0]))) {
throw syntaxError(args.map(diags => {
if (diags && diags[0]) {
return formatDiagnostics(cwd, diags);
}
})
.filter(message => !!message)
.join(''));
}
export interface ParsedConfiguration {
options: api.CompilerOptions;
rootNames: string[];
errors: Diagnostics;
}
export function calcProjectFileAndBasePath(project: string):
{projectFile: string, basePath: string} {
const projectIsDir = fs.lstatSync(project).isDirectory();
const projectFile = projectIsDir ? path.join(project, 'tsconfig.json') : project;
const projectDir = projectIsDir ? project : path.dirname(project);
const basePath = path.resolve(process.cwd(), projectDir);
return {projectFile, basePath};
}
export function createNgCompilerOptions(
basePath: string, config: any, tsOptions: ts.CompilerOptions): api.CompilerOptions {
return {...tsOptions, ...config.angularCompilerOptions, genDir: basePath, basePath};
}
export function readConfiguration(
project: string, basePath: string,
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
existingOptions?: ts.CompilerOptions) {
// Allow a directory containing tsconfig.json as the project value
// Note, TS@next returns an empty array, while earlier versions throw
const projectFile =
fs.lstatSync(project).isDirectory() ? path.join(project, 'tsconfig.json') : project;
project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration {
try {
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
if (error) checkFunc(basePath, [error]);
if (error) {
return {errors: [error], rootNames: [], options: {}};
}
const parseConfigHost = {
useCaseSensitiveFileNames: true,
fileExists: fs.existsSync,
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile
};
const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
const parsed =
ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
const rootNames = parsed.fileNames.map(f => path.normalize(f));
checkFunc(basePath, parsed.errors);
// Default codegen goes to the current directory
// Parsed options are already converted to absolute paths
const ngOptions = config.angularCompilerOptions || {};
// Ignore the genDir option
ngOptions.genDir = basePath;
return {parsed, ngOptions};
const options = createNgCompilerOptions(basePath, config, parsed.options);
return {rootNames, options, errors: parsed.errors};
} catch (e) {
const errors: Diagnostics = [{
category: ts.DiagnosticCategory.Error,
message: e.stack,
}];
return {errors, rootNames: [], options: {}};
}
}
/**
* Returns an object with two properties:
* - `errorCode` is 0 when the compilation was successful,
* - `result` is an `EmitResult` when the errorCode is 0, `undefined` otherwise.
*/
export function performCompilation(
basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: api.CompilerOptions,
consoleError: (s: string) => void = console.error,
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
tsCompilerHost?: ts.CompilerHost): {errorCode: number, result?: api.EmitResult} {
rootNames: string[], options: api.CompilerOptions, host?: api.CompilerHost,
oldProgram?: api.Program): {
program?: api.Program,
emitResult?: api.EmitResult,
diagnostics: Diagnostics,
} {
const [major, minor] = ts.version.split('.');
if (+major < 2 || (+major === 2 && +minor < 3)) {
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) {
throw new Error('Must use TypeScript > 2.3 to have transformer support');
}
const allDiagnostics: Diagnostics = [];
function checkDiagnostics(diags: Diagnostics | undefined) {
if (diags) {
allDiagnostics.push(...diags);
return diags.every(d => d.category !== ts.DiagnosticCategory.Error);
}
return true;
}
let program: api.Program|undefined;
let emitResult: api.EmitResult|undefined;
try {
ngOptions.basePath = basePath;
ngOptions.genDir = basePath;
let host = tsCompilerHost || ts.createCompilerHost(options, true);
host.realpath = p => p;
const rootFileNames = files.map(f => path.normalize(f));
const addGeneratedFileName = (fileName: string) => {
if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) {
rootFileNames.push(fileName);
}
};
if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
const {host: bundleHost, indexName, errors} =
createBundleIndexHost(ngOptions, rootFileNames, host);
if (errors) checkFunc(basePath, errors);
if (indexName) addGeneratedFileName(indexName);
host = bundleHost;
if (!host) {
host = ng.createNgCompilerHost({options});
}
const ngHostOptions = {...options, ...ngOptions};
const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});
const ngProgram =
ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});
program = ng.createProgram({rootNames, host, options, oldProgram});
let shouldEmit = true;
// Check parameter diagnostics
checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());
shouldEmit = shouldEmit && checkDiagnostics([
...program !.getTsOptionDiagnostics(), ...program !.getNgOptionDiagnostics()
]);
// Check syntactic diagnostics
checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());
shouldEmit = shouldEmit && checkDiagnostics(program !.getTsSyntacticDiagnostics());
// Check TypeScript semantic and Angular structure diagnostics
checkFunc(
basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());
shouldEmit =
shouldEmit &&
checkDiagnostics(
[...program !.getTsSemanticDiagnostics(), ...program !.getNgStructuralDiagnostics()]);
// Check Angular semantic diagnostics
checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());
shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics());
const result = ngProgram.emit({
if (shouldEmit) {
const emitResult = program !.emit({
emitFlags: api.EmitFlags.Default |
((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
});
checkFunc(basePath, result.diagnostics);
return {errorCode: 0, result};
allDiagnostics.push(...emitResult.diagnostics);
}
} catch (e) {
let errMsg: string;
if (isSyntaxError(e)) {
consoleError(e.message);
return {errorCode: 1};
// don't report the stack for syntax errors as they are well known errors.
errMsg = e.message;
} else {
errMsg = e.stack;
}
throw e;
allDiagnostics.push({
category: ts.DiagnosticCategory.Error,
message: errMsg,
});
}
return {program, emitResult, diagnostics: allDiagnostics};
}

View File

@ -9,16 +9,10 @@
import {ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript';
export enum DiagnosticCategory {
Warning = 0,
Error = 1,
Message = 2,
}
export interface Diagnostic {
message: string;
span?: ParseSourceSpan;
category: DiagnosticCategory;
category: ts.DiagnosticCategory;
}
export interface CompilerOptions extends ts.CompilerOptions {

View File

@ -13,8 +13,9 @@ import {createModuleFilenameResolver} from './module_filename_resolver';
export {createProgram} from './program';
export {createModuleFilenameResolver};
export function createHost({tsHost, options}: {tsHost: ts.CompilerHost, options: CompilerOptions}):
CompilerHost {
export function createNgCompilerHost(
{options, tsHost = ts.createCompilerHost(options, true)}:
{options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost {
const resolver = createModuleFilenameResolver(tsHost, options);
const host = Object.create(tsHost);

View File

@ -8,6 +8,7 @@
import {AotCompiler, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
import {MissingTranslationStrategy} from '@angular/core';
import {createBundleIndexHost} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as tsickle from 'tsickle';
@ -16,7 +17,7 @@ import * as ts from 'typescript';
import {CompilerHost as AotCompilerHost} from '../compiler_host';
import {TypeChecker} from '../diagnostics/check_types';
import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, EmitResult, Program} from './api';
import {CompilerHost, CompilerOptions, Diagnostic, EmitFlags, EmitResult, Program} from './api';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
@ -31,8 +32,6 @@ const emptyModules: NgAnalyzedModules = {
};
class AngularCompilerProgram implements Program {
// Initialized in the constructor
private oldTsProgram: ts.Program|undefined;
private tsProgram: ts.Program;
private aotCompilerHost: AotCompilerHost;
private compiler: AotCompiler;
@ -49,12 +48,26 @@ class AngularCompilerProgram implements Program {
private _generatedFileDiagnostics: Diagnostic[]|undefined;
private _typeChecker: TypeChecker|undefined;
private _semanticDiagnostics: Diagnostic[]|undefined;
private _optionsDiagnostics: Diagnostic[] = [];
constructor(
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
private oldProgram?: Program) {
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
this.tsProgram = ts.createProgram(rootNames, options, host, this.oldTsProgram);
if (options.flatModuleOutFile && !options.skipMetadataEmit) {
const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host);
if (errors) {
// TODO(tbosch): once we move MetadataBundler from tsc_wrapped into compiler_cli,
// directly create ng.Diagnostic instead of using ts.Diagnostic here.
this._optionsDiagnostics.push(
...errors.map(e => ({category: e.category, message: e.messageText as string})));
} else {
rootNames.push(indexName !);
this.host = host = bundleHost;
}
}
const oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
this.tsProgram = ts.createProgram(rootNames, options, host, oldTsProgram);
this.srcNames =
this.tsProgram.getSourceFiles()
.map(sf => sf.fileName)
@ -78,7 +91,7 @@ class AngularCompilerProgram implements Program {
}
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[] {
return getNgOptionDiagnostics(this.options);
return [...this._optionsDiagnostics, ...getNgOptionDiagnostics(this.options)];
}
getTsSyntacticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
@ -237,11 +250,11 @@ class AngularCompilerProgram implements Program {
this._structuralDiagnostics =
parserErrors.map<Diagnostic>(e => ({
message: e.contextualMessage(),
category: DiagnosticCategory.Error,
category: ts.DiagnosticCategory.Error,
span: e.span
}));
} else {
this._structuralDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}];
this._structuralDiagnostics = [{message: e.message, category: ts.DiagnosticCategory.Error}];
}
this._analyzedModules = emptyModules;
return emptyModules;
@ -272,7 +285,8 @@ class AngularCompilerProgram implements Program {
return this.options.skipTemplateCodegen ? [] : result;
} catch (e) {
if (isSyntaxError(e)) {
this._generatedFileDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}];
this._generatedFileDiagnostics =
[{message: e.message, category: ts.DiagnosticCategory.Error}];
return [];
}
throw e;
@ -395,7 +409,7 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
return [{
message:
'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"',
category: DiagnosticCategory.Error
category: ts.DiagnosticCategory.Error
}];
}
}

View File

@ -18,16 +18,22 @@ function getNgRootDir() {
return moduleFilename.substr(0, distIndex);
}
describe('compiler-cli', () => {
describe('compiler-cli with disableTransformerPipeline', () => {
let basePath: string;
let outDir: string;
let write: (fileName: string, content: string) => void;
let errorSpy: jasmine.Spy&((s: string) => void);
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
write('tsconfig.json', tsconfig);
const json = JSON.parse(tsconfig);
// Note: 'extends' does not work for "angularCompilerOptions" yet.
const ngOptions = json['angularCompilerOptions'] = json['angularCompilerOptions'] || {};
ngOptions['disableTransformerPipeline'] = true;
write('tsconfig.json', JSON.stringify(json));
}
beforeEach(() => {
errorSpy = jasmine.createSpy('consoleError');
basePath = makeTempDir();
write = (fileName: string, content: string) => {
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
@ -58,13 +64,9 @@ describe('compiler-cli', () => {
writeConfig();
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(mockConsole.error).not.toHaveBeenCalled();
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toEqual(0);
done();
})
@ -76,16 +78,11 @@ describe('compiler-cli', () => {
"extends": "./tsconfig-base.json",
"files": ["test.ts"]
}`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
expect(errorSpy).toHaveBeenCalledWith(
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
@ -96,16 +93,10 @@ describe('compiler-cli', () => {
writeConfig();
write('test.ts', 'foo;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
@ -116,17 +107,11 @@ describe('compiler-cli', () => {
writeConfig();
write('test.ts', `import {MyClass} from './not-exist-deps';`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') +
`:1:23: Cannot find module './not-exist-deps'.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
@ -138,17 +123,11 @@ describe('compiler-cli', () => {
write('empty-deps.ts', 'export const A = 1;');
write('test.ts', `import {MyClass} from './empty-deps';`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
@ -163,18 +142,12 @@ describe('compiler-cli', () => {
A();
`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') +
':3:7: Cannot invoke an expression whose type lacks a call signature. ' +
'Type \'String\' has no compatible call signatures.');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
@ -184,14 +157,11 @@ describe('compiler-cli', () => {
it('should print the stack trace on compiler internal errors', (done) => {
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: 'not-exist'}, mockConsole.error)
main(['-p', 'not-exist'], errorSpy)
.then((exitCode) => {
expect(mockConsole.error).toHaveBeenCalled();
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
expect(errorSpy).toHaveBeenCalled();
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
expect(exitCode).toEqual(1);
done();
})
@ -215,7 +185,7 @@ describe('compiler-cli', () => {
export class MyModule {}
`);
main({p: basePath})
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
@ -244,11 +214,7 @@ describe('compiler-cli', () => {
export class MyModule {}
`);
const mockConsole = {error: (s: string) => {}};
const errorSpy = spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0])
@ -280,7 +246,7 @@ describe('compiler-cli', () => {
export class MyModule {}
`);
main({p: basePath})
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
@ -307,7 +273,7 @@ describe('compiler-cli', () => {
export class MyModule {}
`);
main({p: basePath})
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(false);
@ -333,7 +299,7 @@ describe('compiler-cli', () => {
export class MyModule {}
`);
main({p: basePath})
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(true);

View File

@ -11,8 +11,7 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {main} from '../src/ngc';
import {performCompilation, readConfiguration} from '../src/perform-compile';
import {mainSync} from '../src/main';
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
@ -20,16 +19,18 @@ function getNgRootDir() {
return moduleFilename.substr(0, distIndex);
}
describe('ngc command-line', () => {
describe('ngc transformer command-line', () => {
let basePath: string;
let outDir: string;
let write: (fileName: string, content: string) => void;
let errorSpy: jasmine.Spy&((s: string) => void);
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
write('tsconfig.json', tsconfig);
}
beforeEach(() => {
errorSpy = jasmine.createSpy('consoleError');
basePath = makeTempDir();
write = (fileName: string, content: string) => {
const dir = path.dirname(fileName);
@ -66,35 +67,9 @@ describe('ngc command-line', () => {
writeConfig();
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const result = main(['-p', basePath], mockConsole.error);
expect(mockConsole.error).not.toHaveBeenCalled();
expect(result).toBe(0);
});
it('should be able to be called without a config file by passing options explicitly', () => {
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
expect(
() => performCompilation(
basePath, [path.join(basePath, 'test.ts')], {
experimentalDecorators: true,
skipLibCheck: true,
types: [],
outDir: path.join(basePath, 'built'),
declaration: true,
module: ts.ModuleKind.ES2015,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
},
{}))
.not.toThrow();
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
});
it('should not print the stack trace if user input file does not exist', () => {
@ -102,16 +77,11 @@ describe('ngc command-line', () => {
"extends": "./tsconfig-base.json",
"files": ["test.ts"]
}`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
expect(mockConsole.error)
.toHaveBeenCalledWith(
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` +
'\n');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
});
@ -119,16 +89,10 @@ describe('ngc command-line', () => {
writeConfig();
write('test.ts', 'foo;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
expect(mockConsole.error)
.toHaveBeenCalledWith(
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`test.ts(1,1): error TS2304: Cannot find name 'foo'.` +
'\n');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
});
@ -136,16 +100,10 @@ describe('ngc command-line', () => {
writeConfig();
write('test.ts', `import {MyClass} from './not-exist-deps';`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
expect(mockConsole.error)
.toHaveBeenCalledWith(
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` +
'\n');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
});
@ -154,17 +112,11 @@ describe('ngc command-line', () => {
write('empty-deps.ts', 'export const A = 1;');
write('test.ts', `import {MyClass} from './empty-deps';`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
expect(mockConsole.error)
.toHaveBeenCalledWith(
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') +
`"' has no exported member 'MyClass'.` +
'\n');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
});
@ -176,30 +128,21 @@ describe('ngc command-line', () => {
A();
`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
expect(mockConsole.error)
.toHaveBeenCalledWith(
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
'test.ts(3,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. ' +
'Type \'String\' has no compatible call signatures.\n');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
});
it('should print the stack trace on compiler internal errors', () => {
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
const exitCode = main(['-p', 'not-exist'], mockConsole.error);
expect(mockConsole.error).toHaveBeenCalled();
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(2);
const exitCode = mainSync(['-p', 'not-exist'], errorSpy);
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
expect(exitCode).toEqual(1);
});
describe('compile ngfactory files', () => {
@ -218,11 +161,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const mockConsole = {error: (s: string) => {}};
const errorSpy = spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0])
.toContain('Error at ng://' + path.join(basePath, 'mymodule.ts.MyComp.html'));
@ -253,11 +192,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const mockConsole = {error: (s: string) => {}};
const errorSpy = spyOn(mockConsole, 'error');
const exitCode = main(['-p', basePath], mockConsole.error);
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0])
.toContain('Error at ng://' + path.join(basePath, 'my.component.html(1,5):'));
@ -282,7 +217,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const exitCode = main(['-p', basePath]);
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
@ -307,7 +242,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
expect(fs.existsSync(path.resolve(
@ -332,8 +267,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const mockConsole = {error: (s: string) => {}};
const exitCode = main(['-p', basePath], mockConsole.error);
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
@ -362,8 +296,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const mockConsole = {error: (s: string) => {}};
const exitCode = main(['-p', basePath], mockConsole.error);
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
@ -392,8 +325,7 @@ describe('ngc command-line', () => {
export class MyModule {}
`);
const mockConsole = {error: (s: string) => {}};
const exitCode = main(['-p', basePath], mockConsole.error);
const exitCode = mainSync(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
@ -411,9 +343,9 @@ describe('ngc command-line', () => {
});
function compile(): number {
const errors: string[] = [];
const result = main(['-p', path.join(basePath, 'tsconfig.json')], s => errors.push(s));
expect(errors).toEqual([]);
errorSpy.calls.reset();
const result = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
return result;
}
@ -607,68 +539,12 @@ describe('ngc command-line', () => {
export class FlatModule {
}`);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
shouldExist('index.js');
shouldExist('index.metadata.json');
});
it('should be able to build a flat module passing explicit options', () => {
write('public-api.ts', `
export * from './src/flat.component';
export * from './src/flat.module';`);
write('src/flat.component.html', '<div>flat module component</div>');
write('src/flat.component.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'flat-comp',
templateUrl: 'flat.component.html',
})
export class FlatComponent {
}`);
write('src/flat.module.ts', `
import {NgModule} from '@angular/core';
import {FlatComponent} from './flat.component';
@NgModule({
declarations: [
FlatComponent,
],
exports: [
FlatComponent,
]
})
export class FlatModule {
}`);
const emitResult = performCompilation(
basePath, [path.join(basePath, 'public-api.ts')], {
target: ts.ScriptTarget.ES5,
experimentalDecorators: true,
noImplicitAny: true,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
rootDir: basePath,
declaration: true,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
baseUrl: basePath,
outDir: path.join(basePath, 'built'),
typeRoots: [path.join(basePath, 'node_modules/@types')]
},
{
genDir: 'ng',
flatModuleId: 'flat_module',
flatModuleOutFile: 'index.js',
skipTemplateCodegen: true
});
expect(emitResult.errorCode).toEqual(0);
shouldExist('index.js');
shouldExist('index.metadata.json');
});
describe('with a third-party library', () => {
const writeGenConfig = (skipCodegen: boolean) => {
writeConfig(`{
@ -756,7 +632,7 @@ describe('ngc command-line', () => {
it('should honor skip code generation', () => {
// First ensure that we skip code generation when requested;.
writeGenConfig(/* skipCodegen */ true);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
modules.forEach(moduleName => {
shouldExist(moduleName + '.js');
@ -772,7 +648,7 @@ describe('ngc command-line', () => {
it('should produce factories', () => {
// First ensure that we skip code generation when requested;.
writeGenConfig(/* skipCodegen */ false);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
modules.forEach(moduleName => {
shouldExist(moduleName + '.js');
@ -823,7 +699,7 @@ describe('ngc command-line', () => {
});
it('should compile without error', () => {
expect(main(['-p', path.join(basePath, 'tsconfig.json')])).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0);
});
});
@ -897,7 +773,7 @@ describe('ngc command-line', () => {
});
it('should be able to compile library 1', () => {
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
shouldExist('lib1/module.js');
shouldExist('lib1/module.ngsummary.json');
shouldExist('lib1/module.ngsummary.js');
@ -907,8 +783,8 @@ describe('ngc command-line', () => {
});
it('should be able to compile library 2', () => {
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0);
shouldExist('lib2/module.js');
shouldExist('lib2/module.ngsummary.json');
shouldExist('lib2/module.ngsummary.js');
@ -919,12 +795,12 @@ describe('ngc command-line', () => {
describe('building an application', () => {
beforeEach(() => {
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0);
});
it('should build without error', () => {
expect(main(['-p', path.join(basePath, 'app')])).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'app')], errorSpy)).toBe(0);
shouldExist('app/main.js');
});
});

View File

@ -32,7 +32,6 @@
"files": [
"index.ts",
"src/main.ts",
"src/ngc.ts",
"src/extract_i18n.ts",
"../../node_modules/@types/node/index.d.ts",
"../../node_modules/@types/jasmine/index.d.ts",

View File

@ -120,51 +120,40 @@ export class MetadataWriterHost extends DelegatingHost {
}
}
export class SyntheticIndexHost extends DelegatingHost {
private normalSyntheticIndexName: string;
private indexContent: string;
private indexMetadata: string;
export function createSyntheticIndexHost<H extends ts.CompilerHost>(
delegate: H, syntheticIndex: {name: string, content: string, metadata: string}): H {
const normalSyntheticIndexName = normalize(syntheticIndex.name);
const indexContent = syntheticIndex.content;
const indexMetadata = syntheticIndex.metadata;
constructor(
delegate: ts.CompilerHost,
syntheticIndex: {name: string, content: string, metadata: string}) {
super(delegate);
this.normalSyntheticIndexName = normalize(syntheticIndex.name);
this.indexContent = syntheticIndex.content;
this.indexMetadata = syntheticIndex.metadata;
}
const newHost = Object.create(delegate);
newHost.fileExists = (fileName: string): boolean => {
return normalize(fileName) == normalSyntheticIndexName || delegate.fileExists(fileName);
};
fileExists = (fileName: string):
boolean => {
return normalize(fileName) == this.normalSyntheticIndexName ||
this.delegate.fileExists(fileName);
}
newHost.readFile = (fileName: string) => {
return normalize(fileName) == normalSyntheticIndexName ? indexContent :
delegate.readFile(fileName);
};
readFile =
(fileName: string) => {
return normalize(fileName) == this.normalSyntheticIndexName ?
this.indexContent :
this.delegate.readFile(fileName);
newHost.getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
if (normalize(fileName) == normalSyntheticIndexName) {
return ts.createSourceFile(fileName, indexContent, languageVersion, true);
}
return delegate.getSourceFile(fileName, languageVersion, onError);
};
getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void) => {
if (normalize(fileName) == this.normalSyntheticIndexName) {
return ts.createSourceFile(fileName, this.indexContent, languageVersion, true);
}
return this.delegate.getSourceFile(fileName, languageVersion, onError);
}
writeFile: ts.WriteFileCallback =
newHost.writeFile =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
normalize(sourceFiles[0].fileName) == this.normalSyntheticIndexName) {
normalize(sourceFiles[0].fileName) == normalSyntheticIndexName) {
// If we are writing the synthetic index, write the metadata along side.
const metadataName = fileName.replace(DTS, '.metadata.json');
writeFileSync(metadataName, this.indexMetadata, {encoding: 'utf8'});
}
writeFileSync(metadataName, indexMetadata, {encoding: 'utf8'});
}
};
return newHost;
}

View File

@ -13,7 +13,7 @@ import * as ts from 'typescript';
import {CompilerHostAdapter, MetadataBundler} from './bundler';
import {CliOptions} from './cli_options';
import {MetadataWriterHost, SyntheticIndexHost} from './compiler_host';
import {MetadataWriterHost, createSyntheticIndexHost} from './compiler_host';
import {privateEntriesToIndex} from './index_writer';
import NgOptions from './options';
import {check, tsc} from './tsc';
@ -33,9 +33,9 @@ export interface CodegenExtension {
host: ts.CompilerHost): Promise<string[]>;
}
export function createBundleIndexHost(
export function createBundleIndexHost<H extends ts.CompilerHost>(
ngOptions: NgOptions, rootFiles: string[],
host: ts.CompilerHost): {host: ts.CompilerHost, indexName?: string, errors?: ts.Diagnostic[]} {
host: H): {host: H, indexName?: string, errors?: ts.Diagnostic[]} {
const files = rootFiles.filter(f => !DTS.test(f));
if (files.length != 1) {
return {
@ -61,7 +61,7 @@ export function createBundleIndexHost(
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
const libraryIndex = `./${path.basename(indexModule)}`;
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
host = new SyntheticIndexHost(host, {name, content, metadata});
host = createSyntheticIndexHost(host, {name, content, metadata});
return {host, indexName: name};
}

View File

@ -9,26 +9,25 @@
// TODO(chuckj): Remove the requirement for a fake 'reflect` implementation from
// the compiler
import 'reflect-metadata';
import {performCompilation} from '@angular/compiler-cli';
import {calcProjectFileAndBasePath, createNgCompilerOptions, formatDiagnostics, performCompilation} from '@angular/compiler-cli';
import * as fs from 'fs';
import * as path from 'path';
// Note, the tsc_wrapped module comes from rules_typescript, not from @angular/tsc-wrapped
import {parseTsconfig} from 'tsc_wrapped';
import * as ts from 'typescript';
function main(args: string[]) {
const [{options, bazelOpts, files, config}] = parseTsconfig(args[1]);
const ngOptions: {expectedOut: string[]} = (config as any).angularCompilerOptions;
const project = args[1];
const [{options: tsOptions, bazelOpts, files, config}] = parseTsconfig(project);
const {basePath} = calcProjectFileAndBasePath(project);
const ngOptions = createNgCompilerOptions(basePath, config, tsOptions);
const parsedArgs = require('minimist')(args);
const project = parsedArgs.p || parsedArgs.project || '.';
const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project;
// file names in tsconfig are resolved relative to this absolute path
const basePath = path.resolve(process.cwd(), projectDir);
const result = performCompilation(basePath, files, options, ngOptions, undefined);
return result.errorCode;
const {diagnostics} = performCompilation(files, ngOptions);
if (diagnostics.length) {
console.error(formatDiagnostics(ngOptions, diagnostics));
}
return diagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0;
}
if (require.main === module) {