diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts index 482b8ecba8..e7095d0698 100644 --- a/packages/compiler-cli/index.ts +++ b/packages/compiler-cli/index.ts @@ -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'; diff --git a/packages/compiler-cli/src/diagnostics/check_types.ts b/packages/compiler-cli/src/diagnostics/check_types.ts index 0029f2fe60..48584a8493 100644 --- a/packages/compiler-cli/src/diagnostics/check_types.ts +++ b/packages/compiler-cli/src/diagnostics/check_types.ts @@ -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 !); diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index 9e4f171760..28d9d86ab9 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -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 { + 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 { + 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 { - 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); } diff --git a/packages/compiler-cli/src/ngc.ts b/packages/compiler-cli/src/ngc.ts deleted file mode 100644 index 788b1f4cb1..0000000000 --- a/packages/compiler-cli/src/ngc.ts +++ /dev/null @@ -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))); -} \ No newline at end of file diff --git a/packages/compiler-cli/src/perform-compile.ts b/packages/compiler-cli/src/perform-compile.ts index b7efcb2bf9..258a094ce8 100644 --- a/packages/compiler-cli/src/perform-compile.ts +++ b/packages/compiler-cli/src/perform-compile.ts @@ -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; -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, - getCanonicalFileName: fileName => fileName, - getNewLine: () => ts.sys.newLine - }); - } else { - return diags - .map(d => { - let res = api.DiagnosticCategory[d.category]; + const tsFormatHost: ts.FormatDiagnosticsHost = { + getCurrentDirectory: () => options.basePath || process.cwd(), + getCanonicalFileName: fileName => fileName, + getNewLine: () => ts.sys.newLine + }; + return diags + .map(d => { + 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(); - } + } + }) + .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; - let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); + project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration { + try { + const {projectFile, basePath} = calcProjectFileAndBasePath(project); - if (error) checkFunc(basePath, [error]); - const parseConfigHost = { - useCaseSensitiveFileNames: true, - fileExists: fs.existsSync, - readDirectory: ts.sys.readDirectory, - readFile: ts.sys.readFile - }; - const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions); + let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); - checkFunc(basePath, parsed.errors); + 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 rootNames = parsed.fileNames.map(f => path.normalize(f)); - // 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({ - emitFlags: api.EmitFlags.Default | - ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) - }); - - checkFunc(basePath, result.diagnostics); - - return {errorCode: 0, result}; - } catch (e) { - if (isSyntaxError(e)) { - consoleError(e.message); - return {errorCode: 1}; + if (shouldEmit) { + const emitResult = program !.emit({ + emitFlags: api.EmitFlags.Default | + ((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) + }); + allDiagnostics.push(...emitResult.diagnostics); } - - throw e; + } catch (e) { + let errMsg: string; + if (isSyntaxError(e)) { + // don't report the stack for syntax errors as they are well known errors. + errMsg = e.message; + } else { + errMsg = e.stack; + } + allDiagnostics.push({ + category: ts.DiagnosticCategory.Error, + message: errMsg, + }); } + return {program, emitResult, diagnostics: allDiagnostics}; } \ No newline at end of file diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index c48c67327b..2c463c1437 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -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 { diff --git a/packages/compiler-cli/src/transformers/entry_points.ts b/packages/compiler-cli/src/transformers/entry_points.ts index 4e4f5b1721..b6d991a4c9 100644 --- a/packages/compiler-cli/src/transformers/entry_points.ts +++ b/packages/compiler-cli/src/transformers/entry_points.ts @@ -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); diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index c1c0fe2da6..9e1cc3a84c 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -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(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 }]; } } diff --git a/packages/compiler-cli/test/main_spec.ts b/packages/compiler-cli/test/main_spec.ts index a69e50c1b7..46b109846e 100644 --- a/packages/compiler-cli/test/main_spec.ts +++ b/packages/compiler-cli/test/main_spec.ts @@ -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( - `Error File '` + path.join(basePath, 'test.ts') + `' not found.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + `Error File '` + path.join(basePath, 'test.ts') + `' not found.`); 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( - 'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`); 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( - 'Error at ' + path.join(basePath, 'test.ts') + - `:1:23: Cannot find module './not-exist-deps'.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + + `:1:23: Cannot find module './not-exist-deps'.`); 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( - '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(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` + + path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`); 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( - '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(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(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); diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 165582a09c..893068872b 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -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( - `error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` + + '\n'); 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( - `test.ts(1,1): error TS2304: Cannot find name 'foo'.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `test.ts(1,1): error TS2304: Cannot find name 'foo'.` + + '\n'); 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( - `test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` + + '\n'); 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( - `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'); + 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(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( - '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'); + 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(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', '
flat module component
'); - 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'); }); }); diff --git a/packages/compiler-cli/tsconfig-build.json b/packages/compiler-cli/tsconfig-build.json index 6937d14a26..3f1d3a822c 100644 --- a/packages/compiler-cli/tsconfig-build.json +++ b/packages/compiler-cli/tsconfig-build.json @@ -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", diff --git a/packages/tsc-wrapped/src/compiler_host.ts b/packages/tsc-wrapped/src/compiler_host.ts index eaff682940..47f7358dc7 100644 --- a/packages/tsc-wrapped/src/compiler_host.ts +++ b/packages/tsc-wrapped/src/compiler_host.ts @@ -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( + 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); - } - - getSourceFile = - (fileName: string, languageVersion: ts.ScriptTarget, - onError?: (message: string) => void) => { - if (normalize(fileName) == this.normalSyntheticIndexName) { - return ts.createSourceFile(fileName, this.indexContent, languageVersion, true); + newHost.getSourceFile = + (fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => { + if (normalize(fileName) == normalSyntheticIndexName) { + return ts.createSourceFile(fileName, indexContent, languageVersion, true); } - return this.delegate.getSourceFile(fileName, languageVersion, onError); - } + return delegate.getSourceFile(fileName, languageVersion, onError); + }; - writeFile: ts.WriteFileCallback = - (fileName: string, data: string, writeByteOrderMark: boolean, - onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { - this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); - if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 && - normalize(sourceFiles[0].fileName) == this.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'}); - } - } + newHost.writeFile = + (fileName: string, data: string, writeByteOrderMark: boolean, + onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { + delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); + if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 && + 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, indexMetadata, {encoding: 'utf8'}); + } + }; + return newHost; } \ No newline at end of file diff --git a/packages/tsc-wrapped/src/main.ts b/packages/tsc-wrapped/src/main.ts index 10a5cf0fbb..d2882da481 100644 --- a/packages/tsc-wrapped/src/main.ts +++ b/packages/tsc-wrapped/src/main.ts @@ -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; } -export function createBundleIndexHost( +export function createBundleIndexHost( 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}; } diff --git a/tools/ngc-wrapped/index.ts b/tools/ngc-wrapped/index.ts index 0d32fce43b..3a6c0fb37c 100644 --- a/tools/ngc-wrapped/index.ts +++ b/tools/ngc-wrapped/index.ts @@ -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) {