Revert "feat(compiler-cli): add watch mode to ngc (#18818)"

This reverts commit 06d01b2287.
This commit is contained in:
Jason Aden
2017-08-30 19:02:03 -07:00
parent c2136d18bd
commit 3a6d270bb8
18 changed files with 54 additions and 539 deletions

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 {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from '../transformers/api';
import {Diagnostic} from '../transformers/api';
interface FactoryInfo {
source: ts.SourceFile;
@ -142,10 +142,8 @@ export class TypeChecker {
const fileName = span.start.file.url;
const diagnosticsList = diagnosticsFor(fileName);
diagnosticsList.push({
messageText: diagnosticMessageToString(diagnostic.messageText),
category: diagnostic.category, span,
source: SOURCE,
code: DEFAULT_ERROR_CODE
message: diagnosticMessageToString(diagnostic.messageText),
category: diagnostic.category, span
});
}
}

View File

@ -17,19 +17,14 @@ import * as path from 'path';
import * as tsickle from 'tsickle';
import * as api from './transformers/api';
import * as ngc from './transformers/entry_points';
import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile';
import {calcProjectFileAndBasePath, exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult} from './perform_compile';
import {performWatchCompilation, createPerformWatchHost} from './perform_watch';
import {isSyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen';
export function main(
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
const parsedArgs = require('minimist')(args);
if (parsedArgs.w || parsedArgs.watch) {
const result = watchMode(parsedArgs, consoleError);
return Promise.resolve(exitCodeFromResult(result.firstCompileResult));
}
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
if (configErrors.length) {
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
@ -88,16 +83,12 @@ function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback {
});
}
function projectOf(args: any): string {
return (args && (args.p || args.project)) || '.';
}
function readCommandLineAndConfiguration(args: any): ParsedConfiguration {
const project = projectOf(args);
const project = args.p || args.project || '.';
const allDiagnostics: Diagnostics = [];
const config = readConfiguration(project);
const options = mergeCommandLineParams(args, config.options);
return {project, rootNames: config.rootNames, options, errors: config.errors};
return {rootNames: config.rootNames, options, errors: config.errors};
}
function reportErrorsAndExit(
@ -110,15 +101,6 @@ function reportErrorsAndExit(
return exitCode;
}
export function watchMode(args: any, consoleError: (s: string) => void) {
const project = projectOf(args);
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
const config = readConfiguration(project);
return performWatchCompilation(createPerformWatchHost(projectFile, diagnostics => {
consoleError(formatDiagnostics(config.options, diagnostics));
}, options => createEmitCallback(options)));
}
function mergeCommandLineParams(
cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions {
// TODO: also merge in tsc command line parameters by calling

View File

@ -20,7 +20,7 @@ const TS_EXT = /\.ts$/;
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
return diagnostic && diagnostic.source != 'angular';
return diagnostic && (diagnostic.file || diagnostic.messageText);
}
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
@ -41,9 +41,9 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
}
if (d.span && d.span.details) {
res += `: ${d.span.details}, ${d.messageText}\n`;
res += `: ${d.span.details}, ${d.message}\n`;
} else {
res += `: ${d.messageText}\n`;
res += `: ${d.message}\n`;
}
return res;
}
@ -54,7 +54,6 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
}
export interface ParsedConfiguration {
project: string;
options: api.CompilerOptions;
rootNames: string[];
errors: Diagnostics;
@ -82,7 +81,7 @@ export function readConfiguration(
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
if (error) {
return {project, errors: [error], rootNames: [], options: {}};
return {errors: [error], rootNames: [], options: {}};
}
const parseConfigHost = {
useCaseSensitiveFileNames: true,
@ -95,40 +94,16 @@ export function readConfiguration(
const rootNames = parsed.fileNames.map(f => path.normalize(f));
const options = createNgCompilerOptions(basePath, config, parsed.options);
return {project: projectFile, rootNames, options, errors: parsed.errors};
return {rootNames, options, errors: parsed.errors};
} catch (e) {
const errors: Diagnostics = [{
category: ts.DiagnosticCategory.Error,
messageText: e.stack,
source: api.SOURCE,
code: api.UNKNOWN_ERROR_CODE
message: e.stack,
}];
return {project: '', errors, rootNames: [], options: {}};
return {errors, rootNames: [], options: {}};
}
}
export interface PerformCompilationResult {
diagnostics: Diagnostics;
program?: api.Program;
emitResult?: ts.EmitResult;
}
export function exitCodeFromResult(result: PerformCompilationResult | undefined): number {
if (!result) {
// If we didn't get a result we should return failure.
return 1;
}
if (!result.diagnostics || result.diagnostics.length === 0) {
// If we have a result and didn't get any errors, we succeeded.
return 0;
}
// Return 2 if any of the errors were unknown.
return result.diagnostics.some(d => d.source === 'angular' && d.code === api.UNKNOWN_ERROR_CODE) ?
2 :
1;
}
export function performCompilation(
{rootNames, options, host, oldProgram, emitCallback, customTransformers}: {
rootNames: string[],
@ -137,7 +112,11 @@ export function performCompilation(
oldProgram?: api.Program,
emitCallback?: api.TsEmitCallback,
customTransformers?: api.CustomTransformers
}): PerformCompilationResult {
}): {
program?: api.Program,
emitResult?: ts.EmitResult,
diagnostics: Diagnostics,
} {
const [major, minor] = ts.version.split('.');
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) {
@ -189,24 +168,19 @@ export function performCompilation(
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
});
allDiagnostics.push(...emitResult.diagnostics);
return {diagnostics: allDiagnostics, program, emitResult};
}
return {diagnostics: allDiagnostics, program};
} catch (e) {
let errMsg: string;
let code: number;
if (isSyntaxError(e)) {
// don't report the stack for syntax errors as they are well known errors.
errMsg = e.message;
code = api.DEFAULT_ERROR_CODE;
} else {
errMsg = e.stack;
// It is not a syntax error we might have a program with unknown state, discard it.
program = undefined;
code = api.UNKNOWN_ERROR_CODE;
}
allDiagnostics.push(
{category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: api.SOURCE});
return {diagnostics: allDiagnostics, program};
allDiagnostics.push({
category: ts.DiagnosticCategory.Error,
message: errMsg,
});
}
return {program, emitResult, diagnostics: allDiagnostics};
}

View File

@ -1,223 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as chokidar from 'chokidar';
import * as path from 'path';
import * as ts from 'typescript';
import {Diagnostics, ParsedConfiguration, PerformCompilationResult, exitCodeFromResult, performCompilation, readConfiguration} from './perform_compile';
import * as api from './transformers/api';
import {createCompilerHost} from './transformers/entry_points';
const ChangeDiagnostics = {
Compilation_complete_Watching_for_file_changes: {
category: ts.DiagnosticCategory.Message,
messageText: 'Compilation complete. Watching for file changes.',
code: api.DEFAULT_ERROR_CODE,
source: api.SOURCE
},
Compilation_failed_Watching_for_file_changes: {
category: ts.DiagnosticCategory.Message,
messageText: 'Compilation failed. Watching for file changes.',
code: api.DEFAULT_ERROR_CODE,
source: api.SOURCE
},
File_change_detected_Starting_incremental_compilation: {
category: ts.DiagnosticCategory.Message,
messageText: 'File change detected. Starting incremental compilation.',
code: api.DEFAULT_ERROR_CODE,
source: api.SOURCE
},
};
export enum FileChangeEvent {
Change,
CreateDelete
}
export interface PerformWatchHost {
reportDiagnostics(diagnostics: Diagnostics): void;
readConfiguration(): ParsedConfiguration;
createCompilerHost(options: api.CompilerOptions): api.CompilerHost;
createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined;
onFileChange(listener: (event: FileChangeEvent, fileName: string) => void):
{close: () => void, ready: (cb: () => void) => void};
setTimeout(callback: () => void, ms: number): any;
clearTimeout(timeoutId: any): void;
}
export function createPerformWatchHost(
configFileName: string, reportDiagnostics: (diagnostics: Diagnostics) => void,
createEmitCallback?: (options: api.CompilerOptions) => api.TsEmitCallback): PerformWatchHost {
return {
reportDiagnostics: reportDiagnostics,
createCompilerHost: options => createCompilerHost({options}),
readConfiguration: () => readConfiguration(configFileName),
createEmitCallback: options => createEmitCallback ? createEmitCallback(options) : undefined,
onFileChange: (listeners) => {
const parsed = readConfiguration(configFileName);
function stubReady(cb: () => void) { process.nextTick(cb); }
if (parsed.errors && parsed.errors.length) {
reportDiagnostics(parsed.errors);
return {close: () => {}, ready: stubReady};
}
if (!parsed.options.basePath) {
reportDiagnostics([{
category: ts.DiagnosticCategory.Error,
messageText: 'Invalid configuration option. baseDir not specified',
source: api.SOURCE,
code: api.DEFAULT_ERROR_CODE
}]);
return {close: () => {}, ready: stubReady};
}
const watcher = chokidar.watch(parsed.options.basePath, {
// ignore .dotfiles, .js and .map files.
// can't ignore other files as we e.g. want to recompile if an `.html` file changes as well.
ignored: /((^[\/\\])\..)|(\.js$)|(\.map$)|(\.metadata\.json)/,
ignoreInitial: true,
persistent: true,
});
watcher.on('all', (event: string, path: string) => {
switch (event) {
case 'change':
listeners(FileChangeEvent.Change, path);
break;
case 'unlink':
case 'add':
listeners(FileChangeEvent.CreateDelete, path);
break;
}
});
function ready(cb: () => void) { watcher.on('ready', cb); }
return {close: () => watcher.close(), ready};
},
setTimeout: (ts.sys.clearTimeout && ts.sys.setTimeout) || setTimeout,
clearTimeout: (ts.sys.setTimeout && ts.sys.clearTimeout) || clearTimeout,
};
}
/**
* The logic in this function is adapted from `tsc.ts` from TypeScript.
*/
export function performWatchCompilation(host: PerformWatchHost): {
close: () => void,
ready: (cb: () => void) => void,
firstCompileResult: PerformCompilationResult | undefined
} {
let cachedProgram: api.Program|undefined; // Program cached from last compilation
let cachedCompilerHost: api.CompilerHost|undefined; // CompilerHost cached from last compilation
let cachedOptions: ParsedConfiguration|undefined; // CompilerOptions cached from last compilation
let timerHandleForRecompilation: any; // Handle for 0.25s wait timer to trigger recompilation
// Watch basePath, ignoring .dotfiles
const fileWatcher = host.onFileChange(watchedFileChanged);
const ingoreFilesForWatch = new Set<string>();
const firstCompileResult = doCompilation();
const readyPromise = new Promise(resolve => fileWatcher.ready(resolve));
return {close, ready: cb => readyPromise.then(cb), firstCompileResult};
function close() {
fileWatcher.close();
if (timerHandleForRecompilation) {
host.clearTimeout(timerHandleForRecompilation);
timerHandleForRecompilation = undefined;
}
}
// Invoked to perform initial compilation or re-compilation in watch mode
function doCompilation() {
if (!cachedOptions) {
cachedOptions = host.readConfiguration();
}
if (cachedOptions.errors && cachedOptions.errors.length) {
host.reportDiagnostics(cachedOptions.errors);
return;
}
if (!cachedCompilerHost) {
// TODO(chuckj): consider avoiding re-generating factories for libraries.
// Consider modifying the AotCompilerHost to be able to remember the summary files
// generated from previous compiliations and return false from isSourceFile for
// .d.ts files for which a summary file was already generated.å
cachedCompilerHost = host.createCompilerHost(cachedOptions.options);
const originalWriteFileCallback = cachedCompilerHost.writeFile;
cachedCompilerHost.writeFile = function(
fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) {
ingoreFilesForWatch.add(path.normalize(fileName));
return originalWriteFileCallback(fileName, data, writeByteOrderMark, onError, sourceFiles);
};
}
ingoreFilesForWatch.clear();
const compileResult = performCompilation({
rootNames: cachedOptions.rootNames,
options: cachedOptions.options,
host: cachedCompilerHost,
oldProgram: cachedProgram,
emitCallback: host.createEmitCallback(cachedOptions.options)
});
if (compileResult.diagnostics.length) {
host.reportDiagnostics(compileResult.diagnostics);
}
const exitCode = exitCodeFromResult(compileResult);
if (exitCode == 0) {
cachedProgram = compileResult.program;
host.reportDiagnostics([ChangeDiagnostics.Compilation_complete_Watching_for_file_changes]);
} else {
host.reportDiagnostics([ChangeDiagnostics.Compilation_failed_Watching_for_file_changes]);
}
return compileResult;
}
function resetOptions() {
cachedProgram = undefined;
cachedCompilerHost = undefined;
cachedOptions = undefined;
}
function watchedFileChanged(event: FileChangeEvent, fileName: string) {
if (cachedOptions && event === FileChangeEvent.Change &&
// TODO(chuckj): validate that this is sufficient to skip files that were written.
// This assumes that the file path we write is the same file path we will receive in the
// change notification.
path.normalize(fileName) === path.normalize(cachedOptions.project)) {
// If the configuration file changes, forget everything and start the recompilation timer
resetOptions();
} else if (event === FileChangeEvent.CreateDelete) {
// If a file was added or removed, reread the configuration
// to determine the new list of root files.
cachedOptions = undefined;
}
if (!ingoreFilesForWatch.has(path.normalize(fileName))) {
// Ignore the file if the file is one that was written by the compiler.
startTimerForRecompilation();
}
}
// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
// operations (such as saving all modified files in an editor) a chance to complete before we kick
// off a new compilation.
function startTimerForRecompilation() {
if (timerHandleForRecompilation) {
host.clearTimeout(timerHandleForRecompilation);
}
timerHandleForRecompilation = host.setTimeout(recompile, 250);
}
function recompile() {
timerHandleForRecompilation = undefined;
host.reportDiagnostics(
[ChangeDiagnostics.File_change_detected_Starting_incremental_compilation]);
doCompilation();
}
}

View File

@ -9,16 +9,10 @@
import {ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript';
export const DEFAULT_ERROR_CODE = 100;
export const UNKNOWN_ERROR_CODE = 500;
export const SOURCE = 'angular' as 'angular';
export interface Diagnostic {
messageText: string;
message: string;
span?: ParseSourceSpan;
category: ts.DiagnosticCategory;
code: number;
source: 'angular';
}
export interface CompilerOptions extends ts.CompilerOptions {

View File

@ -15,7 +15,7 @@ import * as ts from 'typescript';
import {BaseAotCompilerHost} from '../compiler_host';
import {TypeChecker} from '../diagnostics/check_types';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {CompilerHost, CompilerOptions, CustomTransformers, Diagnostic, EmitFlags, Program, TsEmitArguments, TsEmitCallback} from './api';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
@ -61,12 +61,8 @@ class AngularCompilerProgram implements Program {
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,
messageText: e.messageText as string,
source: SOURCE,
code: DEFAULT_ERROR_CODE
})));
this._optionsDiagnostics.push(
...errors.map(e => ({category: e.category, message: e.messageText as string})));
} else {
rootNames.push(indexName !);
this.host = host = bundleHost;
@ -223,19 +219,12 @@ class AngularCompilerProgram implements Program {
if (parserErrors && parserErrors.length) {
this._structuralDiagnostics =
parserErrors.map<Diagnostic>(e => ({
messageText: e.contextualMessage(),
message: e.contextualMessage(),
category: ts.DiagnosticCategory.Error,
span: e.span,
source: SOURCE,
code: DEFAULT_ERROR_CODE
span: e.span
}));
} else {
this._structuralDiagnostics = [{
messageText: e.message,
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}];
this._structuralDiagnostics = [{message: e.message, category: ts.DiagnosticCategory.Error}];
}
this._analyzedModules = emptyModules;
return emptyModules;
@ -263,12 +252,8 @@ class AngularCompilerProgram implements Program {
return this.options.skipTemplateCodegen ? [] : result;
} catch (e) {
if (isSyntaxError(e)) {
this._generatedFileDiagnostics = [{
messageText: e.message,
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}];
this._generatedFileDiagnostics =
[{message: e.message, category: ts.DiagnosticCategory.Error}];
return [];
}
throw e;
@ -432,11 +417,9 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
break;
default:
return [{
messageText:
message:
'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"',
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
category: ts.DiagnosticCategory.Error
}];
}
}