Merge pull request #29161 from Microsoft/incrementalBuild
Supports incremental build in tsc --b --w mode
This commit is contained in:
commit
9bd23652ef
|
@ -49,7 +49,27 @@ namespace ts {
|
|||
/**
|
||||
* program corresponding to this state
|
||||
*/
|
||||
program: Program;
|
||||
program: Program | undefined;
|
||||
/**
|
||||
* compilerOptions for the program
|
||||
*/
|
||||
compilerOptions: CompilerOptions;
|
||||
/**
|
||||
* Files pending to be emitted
|
||||
*/
|
||||
affectedFilesPendingEmit: ReadonlyArray<Path> | undefined;
|
||||
/**
|
||||
* Current index to retrieve pending affected file
|
||||
*/
|
||||
affectedFilesPendingEmitIndex: number | undefined;
|
||||
/**
|
||||
* Already seen affected files
|
||||
*/
|
||||
seenEmittedFiles: Map<true> | undefined;
|
||||
/**
|
||||
* true if program has been emitted
|
||||
*/
|
||||
programEmitComplete?: true;
|
||||
}
|
||||
|
||||
function hasSameKeys<T, U>(map1: ReadonlyMap<T> | undefined, map2: ReadonlyMap<U> | undefined): boolean {
|
||||
|
@ -64,6 +84,7 @@ namespace ts {
|
|||
const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState;
|
||||
state.program = newProgram;
|
||||
const compilerOptions = newProgram.getCompilerOptions();
|
||||
state.compilerOptions = compilerOptions;
|
||||
// With --out or --outFile, any change affects all semantic diagnostics so no need to cache them
|
||||
// With --isolatedModules, emitting changed file doesnt emit dependent files so we cant know of dependent files to retrieve errors so dont cache the errors
|
||||
if (!compilerOptions.outFile && !compilerOptions.out && !compilerOptions.isolatedModules) {
|
||||
|
@ -72,7 +93,7 @@ namespace ts {
|
|||
state.changedFilesSet = createMap<true>();
|
||||
|
||||
const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState);
|
||||
const oldCompilerOptions = useOldState ? oldState!.program.getCompilerOptions() : undefined;
|
||||
const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined;
|
||||
const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile &&
|
||||
!compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!);
|
||||
if (useOldState) {
|
||||
|
@ -87,6 +108,10 @@ namespace ts {
|
|||
|
||||
// Copy old state's changed files set
|
||||
copyEntries(oldState!.changedFilesSet, state.changedFilesSet);
|
||||
if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) {
|
||||
state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit;
|
||||
state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Update changed files and copy semantic diagnostics if we can
|
||||
|
@ -112,7 +137,7 @@ namespace ts {
|
|||
state.changedFilesSet.set(sourceFilePath, true);
|
||||
}
|
||||
else if (canCopySemanticDiagnostics) {
|
||||
const sourceFile = state.program.getSourceFileByPath(sourceFilePath as Path)!;
|
||||
const sourceFile = newProgram.getSourceFileByPath(sourceFilePath as Path)!;
|
||||
|
||||
if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; }
|
||||
if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; }
|
||||
|
@ -132,6 +157,38 @@ namespace ts {
|
|||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases program and other related not needed properties
|
||||
*/
|
||||
function releaseCache(state: BuilderProgramState) {
|
||||
BuilderState.releaseCache(state);
|
||||
state.program = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the state
|
||||
*/
|
||||
function cloneBuilderProgramState(state: Readonly<BuilderProgramState>): BuilderProgramState {
|
||||
const newState = BuilderState.clone(state) as BuilderProgramState;
|
||||
newState.semanticDiagnosticsPerFile = cloneMapOrUndefined(state.semanticDiagnosticsPerFile);
|
||||
newState.changedFilesSet = cloneMap(state.changedFilesSet);
|
||||
newState.affectedFiles = state.affectedFiles;
|
||||
newState.affectedFilesIndex = state.affectedFilesIndex;
|
||||
newState.currentChangedFilePath = state.currentChangedFilePath;
|
||||
newState.currentAffectedFilesSignatures = cloneMapOrUndefined(state.currentAffectedFilesSignatures);
|
||||
newState.currentAffectedFilesExportedModulesMap = cloneMapOrUndefined(state.currentAffectedFilesExportedModulesMap);
|
||||
newState.seenAffectedFiles = cloneMapOrUndefined(state.seenAffectedFiles);
|
||||
newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles;
|
||||
newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState);
|
||||
newState.program = state.program;
|
||||
newState.compilerOptions = state.compilerOptions;
|
||||
newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit;
|
||||
newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
|
||||
newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles);
|
||||
newState.programEmitComplete = state.programEmitComplete;
|
||||
return newState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that source file is ok to be used in calls that arent handled by next
|
||||
*/
|
||||
|
@ -182,10 +239,11 @@ namespace ts {
|
|||
|
||||
// With --out or --outFile all outputs go into single file
|
||||
// so operations are performed directly on program, return program
|
||||
const compilerOptions = state.program.getCompilerOptions();
|
||||
const program = Debug.assertDefined(state.program);
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
if (compilerOptions.outFile || compilerOptions.out) {
|
||||
Debug.assert(!state.semanticDiagnosticsPerFile);
|
||||
return state.program;
|
||||
return program;
|
||||
}
|
||||
|
||||
// Get next batch of affected files
|
||||
|
@ -193,13 +251,34 @@ namespace ts {
|
|||
if (state.exportedModulesMap) {
|
||||
state.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap || createMap<BuilderState.ReferencedSet | false>();
|
||||
}
|
||||
state.affectedFiles = BuilderState.getFilesAffectedBy(state, state.program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap);
|
||||
state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap);
|
||||
state.currentChangedFilePath = nextKey.value as Path;
|
||||
state.affectedFilesIndex = 0;
|
||||
state.seenAffectedFiles = state.seenAffectedFiles || createMap<true>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet
|
||||
*/
|
||||
function getNextAffectedFilePendingEmit(state: BuilderProgramState): SourceFile | undefined {
|
||||
const { affectedFilesPendingEmit } = state;
|
||||
if (affectedFilesPendingEmit) {
|
||||
const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap());
|
||||
for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) {
|
||||
const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]);
|
||||
if (affectedFile && !seenEmittedFiles.has(affectedFile.path)) {
|
||||
// emit this file
|
||||
state.affectedFilesPendingEmitIndex = i;
|
||||
return affectedFile;
|
||||
}
|
||||
}
|
||||
state.affectedFilesPendingEmit = undefined;
|
||||
state.affectedFilesPendingEmitIndex = undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the semantic diagnostics cached from old state for affected File and the files that are referencing modules that export entities from affected file
|
||||
*/
|
||||
|
@ -212,9 +291,10 @@ namespace ts {
|
|||
// Clean lib file diagnostics if its all files excluding default files to emit
|
||||
if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles && !state.cleanedDiagnosticsOfLibFiles) {
|
||||
state.cleanedDiagnosticsOfLibFiles = true;
|
||||
const options = state.program.getCompilerOptions();
|
||||
if (forEach(state.program.getSourceFiles(), f =>
|
||||
state.program.isSourceFileDefaultLibrary(f) &&
|
||||
const program = Debug.assertDefined(state.program);
|
||||
const options = program.getCompilerOptions();
|
||||
if (forEach(program.getSourceFiles(), f =>
|
||||
program.isSourceFileDefaultLibrary(f) &&
|
||||
!skipTypeChecking(f, options) &&
|
||||
removeSemanticDiagnosticsOf(state, f.path)
|
||||
)) {
|
||||
|
@ -317,21 +397,27 @@ namespace ts {
|
|||
* This is called after completing operation on the next affected file.
|
||||
* The operations here are postponed to ensure that cancellation during the iteration is handled correctly
|
||||
*/
|
||||
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program) {
|
||||
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean) {
|
||||
if (affected === state.program) {
|
||||
state.changedFilesSet.clear();
|
||||
state.programEmitComplete = true;
|
||||
}
|
||||
else {
|
||||
state.seenAffectedFiles!.set((affected as SourceFile).path, true);
|
||||
state.affectedFilesIndex!++;
|
||||
if (isPendingEmit) {
|
||||
state.affectedFilesPendingEmitIndex!++;
|
||||
}
|
||||
else {
|
||||
state.affectedFilesIndex!++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result with affected file
|
||||
*/
|
||||
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> {
|
||||
doneWithAffectedFile(state, affected);
|
||||
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean): AffectedFileResult<T> {
|
||||
doneWithAffectedFile(state, affected, isPendingEmit);
|
||||
return { result, affected };
|
||||
}
|
||||
|
||||
|
@ -350,7 +436,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
// Diagnostics werent cached, get them from program, and cache the result
|
||||
const diagnostics = state.program.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
const diagnostics = Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
if (state.semanticDiagnosticsPerFile) {
|
||||
state.semanticDiagnosticsPerFile.set(path, diagnostics);
|
||||
}
|
||||
|
@ -386,7 +472,7 @@ namespace ts {
|
|||
rootNames: newProgramOrRootNames,
|
||||
options: hostOrOptions as CompilerOptions,
|
||||
host: oldProgramOrHost as CompilerHost,
|
||||
oldProgram: oldProgram && oldProgram.getProgram(),
|
||||
oldProgram: oldProgram && oldProgram.getProgramOrUndefined(),
|
||||
configFileParsingDiagnostics,
|
||||
projectReferences
|
||||
});
|
||||
|
@ -419,28 +505,31 @@ namespace ts {
|
|||
/**
|
||||
* Computing hash to for signature verification
|
||||
*/
|
||||
const computeHash = host.createHash || identity;
|
||||
const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
|
||||
const computeHash = host.createHash || generateDjb2Hash;
|
||||
let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
|
||||
let backupState: BuilderProgramState | undefined;
|
||||
|
||||
// To ensure that we arent storing any references to old program or new program without state
|
||||
newProgram = undefined!; // TODO: GH#18217
|
||||
oldProgram = undefined;
|
||||
oldState = undefined;
|
||||
|
||||
const result: BuilderProgram = {
|
||||
getState: () => state,
|
||||
getProgram: () => state.program,
|
||||
getCompilerOptions: () => state.program.getCompilerOptions(),
|
||||
getSourceFile: fileName => state.program.getSourceFile(fileName),
|
||||
getSourceFiles: () => state.program.getSourceFiles(),
|
||||
getOptionsDiagnostics: cancellationToken => state.program.getOptionsDiagnostics(cancellationToken),
|
||||
getGlobalDiagnostics: cancellationToken => state.program.getGlobalDiagnostics(cancellationToken),
|
||||
getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics || state.program.getConfigFileParsingDiagnostics(),
|
||||
getSyntacticDiagnostics: (sourceFile, cancellationToken) => state.program.getSyntacticDiagnostics(sourceFile, cancellationToken),
|
||||
getSemanticDiagnostics,
|
||||
emit,
|
||||
getAllDependencies: sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile),
|
||||
getCurrentDirectory: () => state.program.getCurrentDirectory()
|
||||
const result = createRedirectedBuilderProgram(state, configFileParsingDiagnostics);
|
||||
result.getState = () => state;
|
||||
result.backupState = () => {
|
||||
Debug.assert(backupState === undefined);
|
||||
backupState = cloneBuilderProgramState(state);
|
||||
};
|
||||
result.restoreState = () => {
|
||||
state = Debug.assertDefined(backupState);
|
||||
backupState = undefined;
|
||||
};
|
||||
result.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.assertDefined(state.program), sourceFile);
|
||||
result.getSemanticDiagnostics = getSemanticDiagnostics;
|
||||
result.emit = emit;
|
||||
result.releaseProgram = () => {
|
||||
releaseCache(state);
|
||||
backupState = undefined;
|
||||
};
|
||||
|
||||
if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
|
||||
|
@ -461,18 +550,39 @@ namespace ts {
|
|||
* in that order would be used to write the files
|
||||
*/
|
||||
function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
|
||||
const affected = getNextAffectedFile(state, cancellationToken, computeHash);
|
||||
let affected = getNextAffectedFile(state, cancellationToken, computeHash);
|
||||
let isPendingEmitFile = false;
|
||||
if (!affected) {
|
||||
// Done
|
||||
return undefined;
|
||||
if (!state.compilerOptions.out && !state.compilerOptions.outFile) {
|
||||
affected = getNextAffectedFilePendingEmit(state);
|
||||
if (!affected) {
|
||||
return undefined;
|
||||
}
|
||||
isPendingEmitFile = true;
|
||||
}
|
||||
else {
|
||||
const program = Debug.assertDefined(state.program);
|
||||
// Check if program uses any prepend project references, if thats the case we cant track of the js files of those, so emit even though there are no changes
|
||||
if (state.programEmitComplete || !some(program.getProjectReferences(), ref => !!ref.prepend)) {
|
||||
state.programEmitComplete = true;
|
||||
return undefined;
|
||||
}
|
||||
affected = program;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark seen emitted files if there are pending files to be emitted
|
||||
if (state.affectedFilesPendingEmit && state.program !== affected) {
|
||||
(state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, true);
|
||||
}
|
||||
|
||||
return toAffectedFileResult(
|
||||
state,
|
||||
// When whole program is affected, do emit only once (eg when --out or --outFile is specified)
|
||||
// Otherwise just affected file
|
||||
state.program.emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers),
|
||||
affected
|
||||
Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers),
|
||||
affected,
|
||||
isPendingEmitFile
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -512,7 +622,7 @@ namespace ts {
|
|||
};
|
||||
}
|
||||
}
|
||||
return state.program.emit(targetSourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
||||
return Debug.assertDefined(state.program).emit(targetSourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -560,33 +670,74 @@ namespace ts {
|
|||
*/
|
||||
function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
|
||||
assertSourceFileOkWithoutNextAffectedCall(state, sourceFile);
|
||||
const compilerOptions = state.program.getCompilerOptions();
|
||||
const compilerOptions = Debug.assertDefined(state.program).getCompilerOptions();
|
||||
if (compilerOptions.outFile || compilerOptions.out) {
|
||||
Debug.assert(!state.semanticDiagnosticsPerFile);
|
||||
// We dont need to cache the diagnostics just return them from program
|
||||
return state.program.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
return Debug.assertDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
if (sourceFile) {
|
||||
return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
|
||||
// When semantic builder asks for diagnostics of the whole program,
|
||||
// ensure that all the affected files are handled
|
||||
let affected: SourceFile | Program | undefined;
|
||||
while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) {
|
||||
doneWithAffectedFile(state, affected);
|
||||
// When semantic builder asks for diagnostics of the whole program,
|
||||
// ensure that all the affected files are handled
|
||||
let affected: SourceFile | Program | undefined;
|
||||
let affectedFilesPendingEmit: Path[] | undefined;
|
||||
while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) {
|
||||
if (affected !== state.program && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
|
||||
(affectedFilesPendingEmit || (affectedFilesPendingEmit = [])).push((affected as SourceFile).path);
|
||||
}
|
||||
doneWithAffectedFile(state, affected);
|
||||
}
|
||||
|
||||
// In case of emit builder, cache the files to be emitted
|
||||
if (affectedFilesPendingEmit) {
|
||||
state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit);
|
||||
// affectedFilesPendingEmitIndex === undefined
|
||||
// - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files
|
||||
// so start from 0 as array would be affectedFilesPendingEmit
|
||||
// else, continue to iterate from existing index, the current set is appended to existing files
|
||||
if (state.affectedFilesPendingEmitIndex === undefined) {
|
||||
state.affectedFilesPendingEmitIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let diagnostics: Diagnostic[] | undefined;
|
||||
for (const sourceFile of state.program.getSourceFiles()) {
|
||||
for (const sourceFile of Debug.assertDefined(state.program).getSourceFiles()) {
|
||||
diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken));
|
||||
}
|
||||
return diagnostics || emptyArray;
|
||||
}
|
||||
}
|
||||
|
||||
export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray<Diagnostic>): BuilderProgram {
|
||||
return {
|
||||
getState: notImplemented,
|
||||
backupState: noop,
|
||||
restoreState: noop,
|
||||
getProgram,
|
||||
getProgramOrUndefined: () => state.program,
|
||||
releaseProgram: () => state.program = undefined,
|
||||
getCompilerOptions: () => state.compilerOptions,
|
||||
getSourceFile: fileName => getProgram().getSourceFile(fileName),
|
||||
getSourceFiles: () => getProgram().getSourceFiles(),
|
||||
getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken),
|
||||
getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken),
|
||||
getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics,
|
||||
getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken),
|
||||
getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken),
|
||||
getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken),
|
||||
emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers),
|
||||
getAllDependencies: notImplemented,
|
||||
getCurrentDirectory: () => getProgram().getCurrentDirectory()
|
||||
};
|
||||
|
||||
function getProgram() {
|
||||
return Debug.assertDefined(state.program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
|
@ -614,10 +765,24 @@ namespace ts {
|
|||
export interface BuilderProgram {
|
||||
/*@internal*/
|
||||
getState(): BuilderProgramState;
|
||||
/*@internal*/
|
||||
backupState(): void;
|
||||
/*@internal*/
|
||||
restoreState(): void;
|
||||
/**
|
||||
* Returns current program
|
||||
*/
|
||||
getProgram(): Program;
|
||||
/**
|
||||
* Returns current program that could be undefined if the program was released
|
||||
*/
|
||||
/*@internal*/
|
||||
getProgramOrUndefined(): Program | undefined;
|
||||
/**
|
||||
* Releases reference to the program, making all the other operations that need program to fail.
|
||||
*/
|
||||
/*@internal*/
|
||||
releaseProgram(): void;
|
||||
/**
|
||||
* Get compiler options of the program
|
||||
*/
|
||||
|
@ -646,10 +811,15 @@ namespace ts {
|
|||
* Get the syntax diagnostics, for all source files if source file is not supplied
|
||||
*/
|
||||
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
|
||||
/**
|
||||
* Get the declaration diagnostics, for all source files if source file is not supplied
|
||||
*/
|
||||
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
||||
/**
|
||||
* Get all the dependencies of the file
|
||||
*/
|
||||
getAllDependencies(sourceFile: SourceFile): ReadonlyArray<string>;
|
||||
|
||||
/**
|
||||
* Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
|
||||
* The semantic diagnostics are cached and managed here
|
||||
|
@ -726,22 +896,7 @@ namespace ts {
|
|||
export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): BuilderProgram;
|
||||
export function createAbstractBuilder(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): BuilderProgram;
|
||||
export function createAbstractBuilder(newProgramOrRootNames: Program | ReadonlyArray<string> | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: ReadonlyArray<Diagnostic> | BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): BuilderProgram {
|
||||
const { newProgram: program } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences);
|
||||
return {
|
||||
// Only return program, all other methods are not implemented
|
||||
getProgram: () => program,
|
||||
getState: notImplemented,
|
||||
getCompilerOptions: notImplemented,
|
||||
getSourceFile: notImplemented,
|
||||
getSourceFiles: notImplemented,
|
||||
getOptionsDiagnostics: notImplemented,
|
||||
getGlobalDiagnostics: notImplemented,
|
||||
getConfigFileParsingDiagnostics: notImplemented,
|
||||
getSyntacticDiagnostics: notImplemented,
|
||||
getSemanticDiagnostics: notImplemented,
|
||||
emit: notImplemented,
|
||||
getAllDependencies: notImplemented,
|
||||
getCurrentDirectory: notImplemented
|
||||
};
|
||||
const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences);
|
||||
return createRedirectedBuilderProgram({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, newConfigFileParsingDiagnostics);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,11 +50,15 @@ namespace ts {
|
|||
/**
|
||||
* Cache of all files excluding default library file for the current program
|
||||
*/
|
||||
allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
|
||||
allFilesExcludingDefaultLibraryFile?: ReadonlyArray<SourceFile>;
|
||||
/**
|
||||
* Cache of all the file names
|
||||
*/
|
||||
allFileNames: ReadonlyArray<string> | undefined;
|
||||
allFileNames?: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export function cloneMapOrUndefined<T>(map: ReadonlyMap<T> | undefined) {
|
||||
return map ? cloneMap(map) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,9 +234,32 @@ namespace ts.BuilderState {
|
|||
fileInfos,
|
||||
referencedMap,
|
||||
exportedModulesMap,
|
||||
hasCalledUpdateShapeSignature,
|
||||
allFilesExcludingDefaultLibraryFile: undefined,
|
||||
allFileNames: undefined
|
||||
hasCalledUpdateShapeSignature
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases needed properties
|
||||
*/
|
||||
export function releaseCache(state: BuilderState) {
|
||||
state.allFilesExcludingDefaultLibraryFile = undefined;
|
||||
state.allFileNames = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the state
|
||||
*/
|
||||
export function clone(state: Readonly<BuilderState>): BuilderState {
|
||||
const fileInfos = createMap<FileInfo>();
|
||||
state.fileInfos.forEach((value, key) => {
|
||||
fileInfos.set(key, { ...value });
|
||||
});
|
||||
// Dont need to backup allFiles info since its cache anyway
|
||||
return {
|
||||
fileInfos,
|
||||
referencedMap: cloneMapOrUndefined(state.referencedMap),
|
||||
exportedModulesMap: cloneMapOrUndefined(state.exportedModulesMap),
|
||||
hasCalledUpdateShapeSignature: cloneMap(state.hasCalledUpdateShapeSignature),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -505,14 +532,14 @@ namespace ts.BuilderState {
|
|||
|
||||
// Start with the paths this file was referenced by
|
||||
seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape);
|
||||
const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.path);
|
||||
const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath);
|
||||
while (queue.length > 0) {
|
||||
const currentPath = queue.pop()!;
|
||||
if (!seenFileNamesMap.has(currentPath)) {
|
||||
const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!;
|
||||
seenFileNamesMap.set(currentPath, currentSourceFile);
|
||||
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash!, exportedModulesMapCache)) { // TODO: GH#18217
|
||||
queue.push(...getReferencedByPaths(state, currentPath));
|
||||
queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1387,6 +1387,18 @@ namespace ts {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function copyProperties<T1 extends T2, T2>(first: T1, second: T2) {
|
||||
for (const id in second) {
|
||||
if (hasOwnProperty.call(second, id)) {
|
||||
(first as any)[id] = second[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function maybeBind<T, A extends any[], R>(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined {
|
||||
return fn ? fn.bind(obj) : undefined;
|
||||
}
|
||||
|
||||
export interface MultiMap<T> extends Map<T[]> {
|
||||
/**
|
||||
* Adds the value to an array of values associated with the key, and returns the array.
|
||||
|
|
|
@ -3957,6 +3957,10 @@
|
|||
"category": "Error",
|
||||
"code": 6370
|
||||
},
|
||||
"Updating unchanged output timestamps of project '{0}'...": {
|
||||
"category": "Message",
|
||||
"code": 6371
|
||||
},
|
||||
|
||||
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
|
||||
"category": "Message",
|
||||
|
|
|
@ -69,6 +69,7 @@ namespace ts {
|
|||
export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
|
||||
return createCompilerHostWorker(options, setParentNodes);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
// TODO(shkamat): update this after reworking ts build API
|
||||
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
|
||||
|
@ -93,7 +94,6 @@ namespace ts {
|
|||
}
|
||||
text = "";
|
||||
}
|
||||
|
||||
return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined;
|
||||
}
|
||||
|
||||
|
@ -203,18 +203,25 @@ namespace ts {
|
|||
return compilerHost;
|
||||
}
|
||||
|
||||
interface CompilerHostLikeForCache {
|
||||
fileExists(fileName: string): boolean;
|
||||
readFile(fileName: string, encoding?: string): string | undefined;
|
||||
directoryExists?(directory: string): boolean;
|
||||
createDirectory?(directory: string): void;
|
||||
writeFile?: WriteFileCallback;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export function changeCompilerHostToUseCache(
|
||||
host: CompilerHost,
|
||||
export function changeCompilerHostLikeToUseCache(
|
||||
host: CompilerHostLikeForCache,
|
||||
toPath: (fileName: string) => Path,
|
||||
useCacheForSourceFile: boolean
|
||||
getSourceFile?: CompilerHost["getSourceFile"]
|
||||
) {
|
||||
const originalReadFile = host.readFile;
|
||||
const originalFileExists = host.fileExists;
|
||||
const originalDirectoryExists = host.directoryExists;
|
||||
const originalCreateDirectory = host.createDirectory;
|
||||
const originalWriteFile = host.writeFile;
|
||||
const originalGetSourceFile = host.getSourceFile;
|
||||
const readFileCache = createMap<string | false>();
|
||||
const fileExistsCache = createMap<boolean>();
|
||||
const directoryExistsCache = createMap<boolean>();
|
||||
|
@ -242,19 +249,17 @@ namespace ts {
|
|||
return setReadFileCache(key, fileName);
|
||||
};
|
||||
|
||||
if (useCacheForSourceFile) {
|
||||
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
||||
const key = toPath(fileName);
|
||||
const value = sourceFileCache.get(key);
|
||||
if (value) return value;
|
||||
const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
||||
const key = toPath(fileName);
|
||||
const value = sourceFileCache.get(key);
|
||||
if (value) return value;
|
||||
|
||||
const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
|
||||
sourceFileCache.set(key, sourceFile);
|
||||
}
|
||||
return sourceFile;
|
||||
};
|
||||
}
|
||||
const sourceFile = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
|
||||
sourceFileCache.set(key, sourceFile);
|
||||
}
|
||||
return sourceFile;
|
||||
} : undefined;
|
||||
|
||||
// fileExists for any kind of extension
|
||||
host.fileExists = fileName => {
|
||||
|
@ -265,23 +270,25 @@ namespace ts {
|
|||
fileExistsCache.set(key, !!newValue);
|
||||
return newValue;
|
||||
};
|
||||
host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
|
||||
const key = toPath(fileName);
|
||||
fileExistsCache.delete(key);
|
||||
if (originalWriteFile) {
|
||||
host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
|
||||
const key = toPath(fileName);
|
||||
fileExistsCache.delete(key);
|
||||
|
||||
const value = readFileCache.get(key);
|
||||
if (value && value !== data) {
|
||||
readFileCache.delete(key);
|
||||
sourceFileCache.delete(key);
|
||||
}
|
||||
else if (useCacheForSourceFile) {
|
||||
const sourceFile = sourceFileCache.get(key);
|
||||
if (sourceFile && sourceFile.text !== data) {
|
||||
const value = readFileCache.get(key);
|
||||
if (value && value !== data) {
|
||||
readFileCache.delete(key);
|
||||
sourceFileCache.delete(key);
|
||||
}
|
||||
}
|
||||
originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
};
|
||||
else if (getSourceFileWithCache) {
|
||||
const sourceFile = sourceFileCache.get(key);
|
||||
if (sourceFile && sourceFile.text !== data) {
|
||||
sourceFileCache.delete(key);
|
||||
}
|
||||
}
|
||||
originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
};
|
||||
}
|
||||
|
||||
// directoryExists
|
||||
if (originalDirectoryExists && originalCreateDirectory) {
|
||||
|
@ -306,7 +313,7 @@ namespace ts {
|
|||
originalDirectoryExists,
|
||||
originalCreateDirectory,
|
||||
originalWriteFile,
|
||||
originalGetSourceFile,
|
||||
getSourceFileWithCache,
|
||||
readFileWithCache
|
||||
};
|
||||
}
|
||||
|
@ -735,7 +742,7 @@ namespace ts {
|
|||
performance.mark("beforeProgram");
|
||||
|
||||
const host = createProgramOptions.host || createCompilerHost(options);
|
||||
const configParsingHost = parseConfigHostFromCompilerHost(host);
|
||||
const configParsingHost = parseConfigHostFromCompilerHostLike(host);
|
||||
|
||||
let skipDefaultLib = options.noLib;
|
||||
const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options));
|
||||
|
@ -3104,18 +3111,28 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
interface CompilerHostLike {
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getCurrentDirectory(): string;
|
||||
fileExists(fileName: string): boolean;
|
||||
readFile(fileName: string): string | undefined;
|
||||
readDirectory?(rootDir: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string>, depth?: number): string[];
|
||||
trace?(s: string): void;
|
||||
onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function parseConfigHostFromCompilerHost(host: CompilerHost): ParseConfigFileHost {
|
||||
export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost {
|
||||
return {
|
||||
fileExists: f => host.fileExists(f),
|
||||
fileExists: f => directoryStructureHost.fileExists(f),
|
||||
readDirectory(root, extensions, excludes, includes, depth) {
|
||||
Debug.assertDefined(host.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'");
|
||||
return host.readDirectory!(root, extensions, excludes, includes, depth);
|
||||
Debug.assertDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'");
|
||||
return directoryStructureHost.readDirectory!(root, extensions, excludes, includes, depth);
|
||||
},
|
||||
readFile: f => host.readFile(f),
|
||||
readFile: f => directoryStructureHost.readFile(f),
|
||||
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
|
||||
getCurrentDirectory: () => host.getCurrentDirectory(),
|
||||
onUnRecoverableConfigFileDiagnostic: () => undefined,
|
||||
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || (() => undefined),
|
||||
trace: host.trace ? (s) => host.trace!(s) : undefined
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,16 @@ declare function setTimeout(handler: (...args: any[]) => void, timeout: number):
|
|||
declare function clearTimeout(handle: any): void;
|
||||
|
||||
namespace ts {
|
||||
/**
|
||||
* djb2 hashing algorithm
|
||||
* http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
/* @internal */
|
||||
export function generateDjb2Hash(data: string): string {
|
||||
const chars = data.split("").map(str => str.charCodeAt(0));
|
||||
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a high stack trace limit to provide more information in case of an error.
|
||||
* Called for command-line and server use cases.
|
||||
|
@ -1115,15 +1125,6 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* djb2 hashing algorithm
|
||||
* http://www.cse.yorku.ca/~oz/hash.html
|
||||
*/
|
||||
function generateDjb2Hash(data: string): string {
|
||||
const chars = data.split("").map(str => str.charCodeAt(0));
|
||||
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
|
||||
}
|
||||
|
||||
function createMD5HashUsingNativeCrypto(data: string): string {
|
||||
const hash = _crypto!.createHash("md5");
|
||||
hash.update(data);
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace ts {
|
|||
newestDeclarationFileContentChangedTime?: Date;
|
||||
newestOutputFileTime?: Date;
|
||||
newestOutputFileName?: string;
|
||||
oldestOutputFileName?: string;
|
||||
oldestOutputFileName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -321,7 +321,7 @@ namespace ts {
|
|||
return fileExtensionIs(fileName, Extension.Dts);
|
||||
}
|
||||
|
||||
export interface SolutionBuilderHostBase extends CompilerHost {
|
||||
export interface SolutionBuilderHostBase<T extends BuilderProgram> extends ProgramHost<T> {
|
||||
getModifiedTime(fileName: string): Date | undefined;
|
||||
setModifiedTime(fileName: string, date: Date): void;
|
||||
deleteFile(fileName: string): void;
|
||||
|
@ -331,15 +331,17 @@ namespace ts {
|
|||
|
||||
// TODO: To do better with watch mode and normal build mode api that creates program and emits files
|
||||
// This currently helps enable --diagnostics and --extendedDiagnostics
|
||||
beforeCreateProgram?(options: CompilerOptions): void;
|
||||
afterProgramEmitAndDiagnostics?(program: Program): void;
|
||||
afterProgramEmitAndDiagnostics?(program: T): void;
|
||||
|
||||
// For testing
|
||||
now?(): Date;
|
||||
}
|
||||
|
||||
export interface SolutionBuilderHost extends SolutionBuilderHostBase {
|
||||
export interface SolutionBuilderHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T> {
|
||||
reportErrorSummary?: ReportEmitErrorSummary;
|
||||
}
|
||||
|
||||
export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost {
|
||||
export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost {
|
||||
}
|
||||
|
||||
export interface SolutionBuilder {
|
||||
|
@ -372,8 +374,8 @@ namespace ts {
|
|||
};
|
||||
}
|
||||
|
||||
function createSolutionBuilderHostBase(system = sys, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
|
||||
const host = createCompilerHostWorker({}, /*setParentNodes*/ undefined, system) as SolutionBuilderHostBase;
|
||||
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
|
||||
const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
|
||||
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined;
|
||||
host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
|
||||
host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop;
|
||||
|
@ -382,20 +384,16 @@ namespace ts {
|
|||
return host;
|
||||
}
|
||||
|
||||
export function createSolutionBuilderHost(system = sys, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
|
||||
const host = createSolutionBuilderHostBase(system, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost;
|
||||
export function createSolutionBuilderHost<T extends BuilderProgram = BuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram || createAbstractBuilder as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
|
||||
host.reportErrorSummary = reportErrorSummary;
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createSolutionBuilderWithWatchHost(system?: System, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
|
||||
const host = createSolutionBuilderHostBase(system, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost;
|
||||
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = SemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
|
||||
const watchHost = createWatchHost(system, reportWatchStatus);
|
||||
host.onWatchStatusChange = watchHost.onWatchStatusChange;
|
||||
host.watchFile = watchHost.watchFile;
|
||||
host.watchDirectory = watchHost.watchDirectory;
|
||||
host.setTimeout = watchHost.setTimeout;
|
||||
host.clearTimeout = watchHost.clearTimeout;
|
||||
copyProperties(host, watchHost);
|
||||
return host;
|
||||
}
|
||||
|
||||
|
@ -413,13 +411,13 @@ namespace ts {
|
|||
* TODO: use SolutionBuilderWithWatchHost => watchedSolution
|
||||
* use SolutionBuilderHost => Solution
|
||||
*/
|
||||
export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
|
||||
export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch;
|
||||
export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch {
|
||||
const hostWithWatch = host as SolutionBuilderWithWatchHost;
|
||||
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilder;
|
||||
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch;
|
||||
export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions): SolutionBuilderWithWatch {
|
||||
const hostWithWatch = host as SolutionBuilderWithWatchHost<T>;
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
|
||||
const parseConfigFileHost = parseConfigHostFromCompilerHost(host);
|
||||
const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host);
|
||||
|
||||
// State of the solution
|
||||
let options = defaultOptions;
|
||||
|
@ -434,8 +432,14 @@ namespace ts {
|
|||
let globalDependencyGraph: DependencyGraph | undefined;
|
||||
const writeFileName = (s: string) => host.trace && host.trace(s);
|
||||
let readFileWithCache = (f: string) => host.readFile(f);
|
||||
let projectCompilerOptions = baseCompilerOptions;
|
||||
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
|
||||
const originalGetSourceFile = compilerHost.getSourceFile;
|
||||
const computeHash = host.createHash || generateDjb2Hash;
|
||||
updateGetSourceFile();
|
||||
|
||||
// Watch state
|
||||
const builderPrograms = createFileMap<T>(toPath);
|
||||
const diagnostics = createFileMap<ReadonlyArray<Diagnostic>>(toPath);
|
||||
const projectPendingBuild = createFileMap<ConfigFileProgramReloadLevel>(toPath);
|
||||
const projectErrorsReported = createFileMap<true>(toPath);
|
||||
|
@ -443,6 +447,7 @@ namespace ts {
|
|||
let nextProjectToBuild = 0;
|
||||
let timerToBuildInvalidatedProject: any;
|
||||
let reportFileChangeDetected = false;
|
||||
const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(host, options);
|
||||
|
||||
// Watches for the solution
|
||||
const allWatchedWildcardDirectories = createFileMap<Map<WildcardDirectoryWatcher>>(toPath);
|
||||
|
@ -492,6 +497,27 @@ namespace ts {
|
|||
clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf));
|
||||
clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
|
||||
clearMap(allWatchedConfigFiles, closeFileWatcher);
|
||||
builderPrograms.clear();
|
||||
updateGetSourceFile();
|
||||
}
|
||||
|
||||
function updateGetSourceFile() {
|
||||
if (options.watch) {
|
||||
if (compilerHost.getSourceFile === originalGetSourceFile) {
|
||||
compilerHost.getSourceFile = (...args) => {
|
||||
const result = originalGetSourceFile.call(compilerHost, ...args);
|
||||
if (result && options.watch) {
|
||||
result.version = computeHash.call(host, result.text);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (compilerHost.getSourceFile !== originalGetSourceFile) {
|
||||
compilerHost.getSourceFile = originalGetSourceFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine {
|
||||
|
@ -542,9 +568,16 @@ namespace ts {
|
|||
|
||||
function watchConfigFile(resolved: ResolvedConfigFileName) {
|
||||
if (options.watch && !allWatchedConfigFiles.hasKey(resolved)) {
|
||||
allWatchedConfigFiles.setValue(resolved, hostWithWatch.watchFile(resolved, () => {
|
||||
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
|
||||
}));
|
||||
allWatchedConfigFiles.setValue(resolved, watchFile(
|
||||
hostWithWatch,
|
||||
resolved,
|
||||
() => {
|
||||
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full);
|
||||
},
|
||||
PollingInterval.High,
|
||||
WatchType.ConfigFile,
|
||||
resolved
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -554,20 +587,27 @@ namespace ts {
|
|||
getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolved),
|
||||
createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories),
|
||||
(dir, flags) => {
|
||||
return hostWithWatch.watchDirectory(dir, fileOrDirectory => {
|
||||
const fileOrDirectoryPath = toPath(fileOrDirectory);
|
||||
if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) {
|
||||
// writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
|
||||
return;
|
||||
}
|
||||
return watchDirectory(
|
||||
hostWithWatch,
|
||||
dir,
|
||||
fileOrDirectory => {
|
||||
const fileOrDirectoryPath = toPath(fileOrDirectory);
|
||||
if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) {
|
||||
writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOutputFile(fileOrDirectory, parsed)) {
|
||||
// writeLog(`${fileOrDirectory} is output file`);
|
||||
return;
|
||||
}
|
||||
if (isOutputFile(fileOrDirectory, parsed)) {
|
||||
writeLog(`${fileOrDirectory} is output file`);
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial);
|
||||
}, !!(flags & WatchDirectoryFlags.Recursive));
|
||||
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial);
|
||||
},
|
||||
flags,
|
||||
WatchType.WildcardDirectory,
|
||||
resolved
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -578,9 +618,15 @@ namespace ts {
|
|||
getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolved),
|
||||
arrayToMap(parsed.fileNames, toPath),
|
||||
{
|
||||
createNewValue: (_key, input) => hostWithWatch.watchFile(input, () => {
|
||||
invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None);
|
||||
}),
|
||||
createNewValue: (path, input) => watchFilePath(
|
||||
hostWithWatch,
|
||||
input,
|
||||
() => invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None),
|
||||
PollingInterval.Low,
|
||||
path as Path,
|
||||
WatchType.SourceFile,
|
||||
resolved
|
||||
),
|
||||
onDeleteValue: closeFileWatcher,
|
||||
}
|
||||
);
|
||||
|
@ -898,7 +944,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function reportErrorSummary() {
|
||||
if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) {
|
||||
if (options.watch || (host as SolutionBuilderHost<T>).reportErrorSummary) {
|
||||
// Report errors from the other projects
|
||||
getGlobalDependencyGraph().buildQueue.forEach(project => {
|
||||
if (!projectErrorsReported.hasKey(project)) {
|
||||
|
@ -911,7 +957,7 @@ namespace ts {
|
|||
reportWatchStatus(getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors);
|
||||
}
|
||||
else {
|
||||
(host as SolutionBuilderHost).reportErrorSummary!(totalErrors);
|
||||
(host as SolutionBuilderHost<T>).reportErrorSummary!(totalErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -944,16 +990,40 @@ namespace ts {
|
|||
return;
|
||||
}
|
||||
|
||||
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) {
|
||||
// Fake that files have been built by updating output file stamps
|
||||
updateOutputTimestamps(proj);
|
||||
return;
|
||||
}
|
||||
|
||||
const buildResult = buildSingleProject(resolved);
|
||||
const dependencyGraph = getGlobalDependencyGraph();
|
||||
const referencingProjects = dependencyGraph.referencingProjectsMap.getValue(resolved);
|
||||
if (buildResult & BuildResultFlags.AnyErrors) return;
|
||||
|
||||
const { referencingProjectsMap, buildQueue } = getGlobalDependencyGraph();
|
||||
const referencingProjects = referencingProjectsMap.getValue(resolved);
|
||||
if (!referencingProjects) return;
|
||||
|
||||
// Always use build order to queue projects
|
||||
for (const project of dependencyGraph.buildQueue) {
|
||||
for (let index = buildQueue.indexOf(resolved) + 1; index < buildQueue.length; index++) {
|
||||
const project = buildQueue[index];
|
||||
const prepend = referencingProjects.getValue(project);
|
||||
// If the project is referenced with prepend, always build downstream projectm,
|
||||
// otherwise queue it only if declaration output changed
|
||||
if (prepend || (prepend !== undefined && !(buildResult & BuildResultFlags.DeclarationOutputUnchanged))) {
|
||||
if (prepend !== undefined) {
|
||||
// If the project is referenced with prepend, always build downstream projects,
|
||||
// If declaration output is changed, build the project
|
||||
// otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps
|
||||
const status = projectStatus.getValue(project);
|
||||
if (prepend || !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) {
|
||||
if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes)) {
|
||||
projectStatus.setValue(project, {
|
||||
type: UpToDateStatusType.OutOfDateWithUpstream,
|
||||
outOfDateOutputFileName: status.oldestOutputFileName,
|
||||
newerProjectName: resolved
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (status && status.type === UpToDateStatusType.UpToDate) {
|
||||
status.type = UpToDateStatusType.UpToDateWithUpstreamTypes;
|
||||
}
|
||||
addProjToQueue(project);
|
||||
}
|
||||
}
|
||||
|
@ -1030,22 +1100,23 @@ namespace ts {
|
|||
return BuildResultFlags.None;
|
||||
}
|
||||
|
||||
const programOptions: CreateProgramOptions = {
|
||||
projectReferences: configFile.projectReferences,
|
||||
host,
|
||||
rootNames: configFile.fileNames,
|
||||
options: configFile.options,
|
||||
configFileParsingDiagnostics: configFile.errors
|
||||
};
|
||||
if (host.beforeCreateProgram) {
|
||||
host.beforeCreateProgram(options);
|
||||
}
|
||||
const program = createProgram(programOptions);
|
||||
// TODO: handle resolve module name to cache result in project reference redirect
|
||||
projectCompilerOptions = configFile.options;
|
||||
const program = host.createProgram(
|
||||
configFile.fileNames,
|
||||
configFile.options,
|
||||
compilerHost,
|
||||
builderPrograms.getValue(proj),
|
||||
configFile.errors,
|
||||
configFile.projectReferences
|
||||
);
|
||||
projectCompilerOptions = baseCompilerOptions;
|
||||
|
||||
// Don't emit anything in the presence of syntactic errors or options diagnostics
|
||||
const syntaxDiagnostics = [
|
||||
...program.getOptionsDiagnostics(),
|
||||
...program.getConfigFileParsingDiagnostics(),
|
||||
...program.getOptionsDiagnostics(),
|
||||
...program.getGlobalDiagnostics(),
|
||||
...program.getSyntacticDiagnostics()];
|
||||
if (syntaxDiagnostics.length) {
|
||||
return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic");
|
||||
|
@ -1057,6 +1128,8 @@ namespace ts {
|
|||
return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic");
|
||||
}
|
||||
|
||||
// Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly
|
||||
program.backupState();
|
||||
let newestDeclarationFileContentChangedTime = minimumDate;
|
||||
let anyDtsChanged = false;
|
||||
let declDiagnostics: Diagnostic[] | undefined;
|
||||
|
@ -1065,11 +1138,13 @@ namespace ts {
|
|||
emitFilesAndReportErrors(program, reportDeclarationDiagnostics, writeFileName, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }));
|
||||
// Don't emit .d.ts if there are decl file errors
|
||||
if (declDiagnostics) {
|
||||
program.restoreState();
|
||||
return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file");
|
||||
}
|
||||
|
||||
// Actual Emit
|
||||
const emitterDiagnostics = createDiagnosticCollection();
|
||||
const emittedOutputs = createFileMap<true>(toPath as ToPath);
|
||||
outputFiles.forEach(({ name, text, writeByteOrderMark }) => {
|
||||
let priorChangeTime: Date | undefined;
|
||||
if (!anyDtsChanged && isDeclarationFile(name)) {
|
||||
|
@ -1083,7 +1158,8 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
writeFile(host, emitterDiagnostics, name, text, writeByteOrderMark);
|
||||
emittedOutputs.setValue(name, true);
|
||||
writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark);
|
||||
if (priorChangeTime !== undefined) {
|
||||
newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime);
|
||||
unchangedOutputs.setValue(name, priorChangeTime);
|
||||
|
@ -1095,51 +1171,72 @@ namespace ts {
|
|||
return buildErrors(emitDiagnostics, BuildResultFlags.EmitErrors, "Emit");
|
||||
}
|
||||
|
||||
// Update time stamps for rest of the outputs
|
||||
newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs);
|
||||
|
||||
const status: UpToDateStatus = {
|
||||
type: UpToDateStatusType.UpToDate,
|
||||
newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime
|
||||
newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime,
|
||||
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile)
|
||||
};
|
||||
diagnostics.removeKey(proj);
|
||||
projectStatus.setValue(proj, status);
|
||||
if (host.afterProgramEmitAndDiagnostics) {
|
||||
host.afterProgramEmitAndDiagnostics(program);
|
||||
}
|
||||
afterProgramCreate(proj, program);
|
||||
return resultFlags;
|
||||
|
||||
function buildErrors(diagnostics: ReadonlyArray<Diagnostic>, errorFlags: BuildResultFlags, errorType: string) {
|
||||
resultFlags |= errorFlags;
|
||||
reportAndStoreErrors(proj, diagnostics);
|
||||
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` });
|
||||
if (host.afterProgramEmitAndDiagnostics) {
|
||||
host.afterProgramEmitAndDiagnostics(program);
|
||||
}
|
||||
afterProgramCreate(proj, program);
|
||||
return resultFlags;
|
||||
}
|
||||
}
|
||||
|
||||
function afterProgramCreate(proj: ResolvedConfigFileName, program: T) {
|
||||
if (host.afterProgramEmitAndDiagnostics) {
|
||||
host.afterProgramEmitAndDiagnostics(program);
|
||||
}
|
||||
if (options.watch) {
|
||||
program.releaseProgram();
|
||||
builderPrograms.setValue(proj, program);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOutputTimestamps(proj: ParsedCommandLine) {
|
||||
if (options.dry) {
|
||||
return reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!);
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
reportStatus(Diagnostics.Updating_output_timestamps_of_project_0, proj.options.configFilePath!);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const outputs = getAllProjectOutputs(proj);
|
||||
let priorNewestUpdateTime = minimumDate;
|
||||
for (const file of outputs) {
|
||||
if (isDeclarationFile(file)) {
|
||||
priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime);
|
||||
}
|
||||
|
||||
host.setModifiedTime(file, now);
|
||||
}
|
||||
|
||||
const priorNewestUpdateTime = updateOutputTimestampsWorker(proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0);
|
||||
projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus);
|
||||
}
|
||||
|
||||
function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap<true>) {
|
||||
const outputs = getAllProjectOutputs(proj);
|
||||
if (!skipOutputs || outputs.length !== skipOutputs.getSize()) {
|
||||
if (options.verbose) {
|
||||
reportStatus(verboseMessage, proj.options.configFilePath!);
|
||||
}
|
||||
const now = host.now ? host.now() : new Date();
|
||||
for (const file of outputs) {
|
||||
if (skipOutputs && skipOutputs.hasKey(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isDeclarationFile(file)) {
|
||||
priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime);
|
||||
}
|
||||
|
||||
host.setModifiedTime(file, now);
|
||||
if (proj.options.listEmittedFiles) {
|
||||
writeFileName(`TSFILE: ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return priorNewestUpdateTime;
|
||||
}
|
||||
|
||||
function getFilesToClean(): string[] {
|
||||
// Get the same graph for cleaning we'd use for building
|
||||
const graph = getGlobalDependencyGraph();
|
||||
|
@ -1187,12 +1284,15 @@ namespace ts {
|
|||
if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
|
||||
// TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api
|
||||
// Override readFile for json files and output .d.ts to cache the text
|
||||
const { originalReadFile, originalFileExists, originalDirectoryExists,
|
||||
originalCreateDirectory, originalWriteFile, originalGetSourceFile,
|
||||
readFileWithCache: newReadFileWithCache
|
||||
} = changeCompilerHostToUseCache(host, toPath, /*useCacheForSourceFile*/ true);
|
||||
const savedReadFileWithCache = readFileWithCache;
|
||||
const savedGetSourceFile = compilerHost.getSourceFile;
|
||||
|
||||
const { originalReadFile, originalFileExists, originalDirectoryExists,
|
||||
originalCreateDirectory, originalWriteFile, getSourceFileWithCache,
|
||||
readFileWithCache: newReadFileWithCache
|
||||
} = changeCompilerHostLikeToUseCache(host, toPath, (...args) => savedGetSourceFile.call(compilerHost, ...args));
|
||||
readFileWithCache = newReadFileWithCache;
|
||||
compilerHost.getSourceFile = getSourceFileWithCache!;
|
||||
|
||||
const graph = getGlobalDependencyGraph();
|
||||
reportBuildQueue(graph);
|
||||
|
@ -1249,8 +1349,8 @@ namespace ts {
|
|||
host.directoryExists = originalDirectoryExists;
|
||||
host.createDirectory = originalCreateDirectory;
|
||||
host.writeFile = originalWriteFile;
|
||||
compilerHost.getSourceFile = savedGetSourceFile;
|
||||
readFileWithCache = savedReadFileWithCache;
|
||||
host.getSourceFile = originalGetSourceFile;
|
||||
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
|
||||
}
|
||||
|
||||
|
@ -1278,7 +1378,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function relName(path: string): string {
|
||||
return convertToRelativePath(path, host.getCurrentDirectory(), f => host.getCanonicalFileName(f));
|
||||
return convertToRelativePath(path, host.getCurrentDirectory(), f => compilerHost.getCanonicalFileName(f));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1311,6 +1411,20 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function getFirstProjectOutput(project: ParsedCommandLine): string {
|
||||
if (project.options.outFile || project.options.out) {
|
||||
return first(getOutFileOutputs(project));
|
||||
}
|
||||
|
||||
for (const inputFile of project.fileNames) {
|
||||
const outputs = getOutputFileNames(inputFile, project);
|
||||
if (outputs.length) {
|
||||
return first(outputs);
|
||||
}
|
||||
}
|
||||
return Debug.fail(`project ${project.options.configFilePath} expected to have at least one output`);
|
||||
}
|
||||
|
||||
export function formatUpToDateStatus<T>(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) {
|
||||
switch (status.type) {
|
||||
case UpToDateStatusType.OutOfDateWithSelf:
|
||||
|
|
|
@ -2827,7 +2827,7 @@ namespace ts {
|
|||
fileName: string,
|
||||
data: string,
|
||||
writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void) | undefined,
|
||||
onError?: (message: string) => void,
|
||||
sourceFiles?: ReadonlyArray<SourceFile>,
|
||||
) => void;
|
||||
|
||||
|
@ -5002,7 +5002,6 @@ namespace ts {
|
|||
getDefaultLibLocation?(): string;
|
||||
writeFile: WriteFileCallback;
|
||||
getCurrentDirectory(): string;
|
||||
getDirectories(path: string): string[];
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getNewLine(): string;
|
||||
|
|
|
@ -88,21 +88,6 @@ namespace ts {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Program structure needed to emit the files and report diagnostics
|
||||
*/
|
||||
export interface ProgramToEmitFilesAndReportErrors {
|
||||
getCurrentDirectory(): string;
|
||||
getCompilerOptions(): CompilerOptions;
|
||||
getSourceFiles(): ReadonlyArray<SourceFile>;
|
||||
getSyntacticDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getOptionsDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getGlobalDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getSemanticDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
|
||||
}
|
||||
|
||||
export type ReportEmitErrorSummary = (errorCount: number) => void;
|
||||
|
||||
export function getErrorCountForSummary(diagnostics: ReadonlyArray<Diagnostic>) {
|
||||
|
@ -121,6 +106,21 @@ namespace ts {
|
|||
return `${newLine}${flattenDiagnosticMessageText(d.messageText, newLine)}${newLine}${newLine}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Program structure needed to emit the files and report diagnostics
|
||||
*/
|
||||
export interface ProgramToEmitFilesAndReportErrors {
|
||||
getCurrentDirectory(): string;
|
||||
getCompilerOptions(): CompilerOptions;
|
||||
getSourceFiles(): ReadonlyArray<SourceFile>;
|
||||
getSyntacticDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getOptionsDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getGlobalDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getSemanticDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options
|
||||
*/
|
||||
|
@ -187,30 +187,110 @@ namespace ts {
|
|||
const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system);
|
||||
return {
|
||||
onWatchStatusChange,
|
||||
watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher,
|
||||
watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher,
|
||||
setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop,
|
||||
clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop
|
||||
watchFile: maybeBind(system, system.watchFile) || (() => noopFileWatcher),
|
||||
watchDirectory: maybeBind(system, system.watchDirectory) || (() => noopFileWatcher),
|
||||
setTimeout: maybeBind(system, system.setTimeout) || noop,
|
||||
clearTimeout: maybeBind(system, system.clearTimeout) || noop
|
||||
};
|
||||
}
|
||||
|
||||
export const enum WatchType {
|
||||
ConfigFile = "Config file",
|
||||
SourceFile = "Source file",
|
||||
MissingFile = "Missing file",
|
||||
WildcardDirectory = "Wild card directory",
|
||||
FailedLookupLocations = "Failed Lookup Locations",
|
||||
TypeRoots = "Type roots"
|
||||
}
|
||||
|
||||
interface WatchFactory<X, Y = undefined> extends ts.WatchFactory<X, Y> {
|
||||
writeLog: (s: string) => void;
|
||||
}
|
||||
|
||||
export function createWatchFactory<Y = undefined>(host: { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) {
|
||||
const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None;
|
||||
const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop;
|
||||
const result = getWatchFactory<WatchType, Y>(watchLogLevel, writeLog) as WatchFactory<WatchType, Y>;
|
||||
result.writeLog = writeLog;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost {
|
||||
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
|
||||
const hostGetNewLine = memoize(() => host.getNewLine());
|
||||
return {
|
||||
getSourceFile: (fileName, languageVersion, onError) => {
|
||||
let text: string | undefined;
|
||||
try {
|
||||
performance.mark("beforeIORead");
|
||||
text = host.readFile(fileName, getCompilerOptions().charset);
|
||||
performance.mark("afterIORead");
|
||||
performance.measure("I/O Read", "beforeIORead", "afterIORead");
|
||||
}
|
||||
catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
text = "";
|
||||
}
|
||||
|
||||
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
|
||||
},
|
||||
getDefaultLibLocation: maybeBind(host, host.getDefaultLibLocation),
|
||||
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
|
||||
writeFile,
|
||||
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
|
||||
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
|
||||
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
|
||||
getNewLine: () => getNewLineCharacter(getCompilerOptions(), hostGetNewLine),
|
||||
fileExists: f => host.fileExists(f),
|
||||
readFile: f => host.readFile(f),
|
||||
trace: maybeBind(host, host.trace),
|
||||
directoryExists: maybeBind(directoryStructureHost, directoryStructureHost.directoryExists),
|
||||
getDirectories: maybeBind(directoryStructureHost, directoryStructureHost.getDirectories),
|
||||
realpath: maybeBind(host, host.realpath),
|
||||
getEnvironmentVariable: maybeBind(host, host.getEnvironmentVariable) || (() => ""),
|
||||
createHash: maybeBind(host, host.createHash),
|
||||
readDirectory: maybeBind(host, host.readDirectory),
|
||||
};
|
||||
|
||||
function ensureDirectoriesExist(directoryPath: string) {
|
||||
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists!(directoryPath)) {
|
||||
const parentDirectory = getDirectoryPath(directoryPath);
|
||||
ensureDirectoriesExist(parentDirectory);
|
||||
if (host.createDirectory) host.createDirectory(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
|
||||
try {
|
||||
performance.mark("beforeIOWrite");
|
||||
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
|
||||
|
||||
host.writeFile!(fileName, text, writeByteOrderMark);
|
||||
|
||||
performance.mark("afterIOWrite");
|
||||
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
|
||||
}
|
||||
catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the watch compiler host that can be extended with config file or root file names and options host
|
||||
*/
|
||||
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
|
||||
if (!createProgram) {
|
||||
createProgram = createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>;
|
||||
}
|
||||
|
||||
export function createProgramHost<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>): ProgramHost<T> {
|
||||
const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
|
||||
let host: DirectoryStructureHost = system;
|
||||
host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!)
|
||||
const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames;
|
||||
const writeFileName = (s: string) => system.write(s + system.newLine);
|
||||
const { onWatchStatusChange, watchFile, watchDirectory, setTimeout, clearTimeout } = createWatchHost(system, reportWatchStatus);
|
||||
return {
|
||||
useCaseSensitiveFileNames,
|
||||
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
|
||||
getNewLine: () => system.newLine,
|
||||
getCurrentDirectory: () => system.getCurrentDirectory(),
|
||||
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
|
||||
getDefaultLibLocation,
|
||||
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
|
||||
fileExists: path => system.fileExists(path),
|
||||
|
@ -218,27 +298,25 @@ namespace ts {
|
|||
directoryExists: path => system.directoryExists(path),
|
||||
getDirectories: path => system.getDirectories(path),
|
||||
readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth),
|
||||
realpath: system.realpath && (path => system.realpath!(path)),
|
||||
getEnvironmentVariable: system.getEnvironmentVariable && (name => system.getEnvironmentVariable(name)),
|
||||
watchFile,
|
||||
watchDirectory,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
trace: s => system.write(s),
|
||||
onWatchStatusChange,
|
||||
realpath: maybeBind(system, system.realpath),
|
||||
getEnvironmentVariable: maybeBind(system, system.getEnvironmentVariable),
|
||||
trace: s => system.write(s + system.newLine),
|
||||
createDirectory: path => system.createDirectory(path),
|
||||
writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark),
|
||||
onCachedDirectoryStructureHostCreate: cacheHost => host = cacheHost || system,
|
||||
createHash: system.createHash && (s => system.createHash!(s)),
|
||||
createProgram,
|
||||
afterProgramCreate: emitFilesAndReportErrorUsingBuilder
|
||||
createHash: maybeBind(system, system.createHash),
|
||||
createProgram
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultLibLocation() {
|
||||
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
|
||||
}
|
||||
|
||||
function emitFilesAndReportErrorUsingBuilder(builderProgram: BuilderProgram) {
|
||||
/**
|
||||
* Creates the watch compiler host that can be extended with config file or root file names and options host
|
||||
*/
|
||||
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
|
||||
const writeFileName = (s: string) => system.write(s + system.newLine);
|
||||
const result = createProgramHost(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>) as WatchCompilerHost<T>;
|
||||
copyProperties(result, createWatchHost(system, reportWatchStatus));
|
||||
result.afterProgramCreate = builderProgram => {
|
||||
const compilerOptions = builderProgram.getCompilerOptions();
|
||||
const newLine = getNewLineCharacter(compilerOptions, () => system.newLine);
|
||||
|
||||
|
@ -246,13 +324,14 @@ namespace ts {
|
|||
builderProgram,
|
||||
reportDiagnostic,
|
||||
writeFileName,
|
||||
errorCount => onWatchStatusChange!(
|
||||
errorCount => result.onWatchStatusChange!(
|
||||
createCompilerDiagnostic(getWatchErrorSummaryDiagnosticMessage(errorCount), errorCount),
|
||||
newLine,
|
||||
compilerOptions
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,6 +370,7 @@ namespace ts {
|
|||
export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
|
||||
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
|
||||
export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference> | undefined) => T;
|
||||
|
||||
/** Host that has watch functionality used in --watch mode */
|
||||
export interface WatchHost {
|
||||
/** If provided, called with Diagnostic message that informs about change in watch status */
|
||||
|
@ -305,19 +385,11 @@ namespace ts {
|
|||
/** If provided, will be used to reset existing delayed compilation */
|
||||
clearTimeout?(timeoutId: any): void;
|
||||
}
|
||||
export interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
|
||||
// TODO: GH#18217 Optional methods are frequently asserted
|
||||
|
||||
export interface ProgramHost<T extends BuilderProgram> {
|
||||
/**
|
||||
* Used to create the program when need for program creation or recreation detected
|
||||
*/
|
||||
createProgram: CreateProgram<T>;
|
||||
/** If provided, callback to invoke after every new program creation */
|
||||
afterProgramCreate?(program: T): void;
|
||||
|
||||
// Only for testing
|
||||
/*@internal*/
|
||||
maxNumberOfFilesToIterateForInvalidation?: number;
|
||||
|
||||
// Sub set of compiler host methods to read and generate new program
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
|
@ -357,16 +429,25 @@ namespace ts {
|
|||
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
|
||||
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
|
||||
}
|
||||
|
||||
/** Internal interface used to wire emit through same host */
|
||||
|
||||
/*@internal*/
|
||||
export interface WatchCompilerHost<T extends BuilderProgram> {
|
||||
export interface ProgramHost<T extends BuilderProgram> {
|
||||
// TODO: GH#18217 Optional methods are frequently asserted
|
||||
createDirectory?(path: string): void;
|
||||
writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
|
||||
onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void;
|
||||
}
|
||||
|
||||
export interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
|
||||
/** If provided, callback to invoke after every new program creation */
|
||||
afterProgramCreate?(program: T): void;
|
||||
|
||||
// Only for testing
|
||||
/*@internal*/
|
||||
maxNumberOfFilesToIterateForInvalidation?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Host to create watch with root files and options
|
||||
*/
|
||||
|
@ -479,8 +560,6 @@ namespace ts {
|
|||
|
||||
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCurrentDirectory = () => currentDirectory;
|
||||
const readFile: (path: string, encoding?: string) => string | undefined = (path, encoding) => host.readFile(path, encoding);
|
||||
const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, createProgram } = host;
|
||||
let { rootFiles: rootFileNames, options: compilerOptions, projectReferences } = host;
|
||||
let configFileSpecs: ConfigFileSpecs;
|
||||
|
@ -493,15 +572,7 @@ namespace ts {
|
|||
host.onCachedDirectoryStructureHostCreate(cachedDirectoryStructureHost);
|
||||
}
|
||||
const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host;
|
||||
const parseConfigFileHost: ParseConfigFileHost = {
|
||||
useCaseSensitiveFileNames,
|
||||
readDirectory: (path, extensions, exclude, include, depth) => directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth),
|
||||
fileExists: path => host.fileExists(path),
|
||||
readFile,
|
||||
getCurrentDirectory,
|
||||
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic,
|
||||
trace: host.trace ? s => host.trace!(s) : undefined
|
||||
};
|
||||
const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost);
|
||||
|
||||
// From tsc we want to get already parsed result and hence check for rootFileNames
|
||||
let newLine = updateNewLine();
|
||||
|
@ -517,55 +588,37 @@ namespace ts {
|
|||
newLine = updateNewLine();
|
||||
}
|
||||
|
||||
const trace = host.trace && ((s: string) => { host.trace!(s + newLine); });
|
||||
const watchLogLevel = trace ? compilerOptions.extendedDiagnostics ? WatchLogLevel.Verbose :
|
||||
compilerOptions.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None;
|
||||
const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? trace! : noop; // TODO: GH#18217
|
||||
const { watchFile, watchFilePath, watchDirectory } = getWatchFactory<string>(watchLogLevel, writeLog);
|
||||
|
||||
const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory<string>(host, compilerOptions);
|
||||
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
|
||||
writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
|
||||
if (configFileName) {
|
||||
watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, "Config file");
|
||||
watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile);
|
||||
}
|
||||
|
||||
const compilerHost: CompilerHost & ResolutionCacheHost = {
|
||||
// Members for CompilerHost
|
||||
getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile),
|
||||
getSourceFileByPath: getVersionedSourceFileByPath,
|
||||
getDefaultLibLocation: host.getDefaultLibLocation && (() => host.getDefaultLibLocation!()),
|
||||
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
|
||||
writeFile,
|
||||
getCurrentDirectory,
|
||||
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
|
||||
getCanonicalFileName,
|
||||
getNewLine: () => newLine,
|
||||
fileExists,
|
||||
readFile,
|
||||
trace,
|
||||
directoryExists: directoryStructureHost.directoryExists && (path => directoryStructureHost.directoryExists!(path)),
|
||||
getDirectories: (directoryStructureHost.getDirectories && ((path: string) => directoryStructureHost.getDirectories!(path)))!, // TODO: GH#18217
|
||||
realpath: host.realpath && (s => host.realpath!(s)),
|
||||
getEnvironmentVariable: host.getEnvironmentVariable ? (name => host.getEnvironmentVariable!(name)) : (() => ""),
|
||||
onReleaseOldSourceFile,
|
||||
createHash: host.createHash && (data => host.createHash!(data)),
|
||||
// Members for ResolutionCacheHost
|
||||
toPath,
|
||||
getCompilationSettings: () => compilerOptions,
|
||||
watchDirectoryOfFailedLookupLocation: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, "Failed Lookup Locations"),
|
||||
watchTypeRootsDirectory: (dir, cb, flags) => watchDirectory(host, dir, cb, flags, "Type roots"),
|
||||
getCachedDirectoryStructureHost: () => cachedDirectoryStructureHost,
|
||||
onInvalidatedResolution: scheduleProgramUpdate,
|
||||
onChangedAutomaticTypeDirectiveNames: () => {
|
||||
hasChangedAutomaticTypeDirectiveNames = true;
|
||||
scheduleProgramUpdate();
|
||||
},
|
||||
maxNumberOfFilesToIterateForInvalidation: host.maxNumberOfFilesToIterateForInvalidation,
|
||||
getCurrentProgram,
|
||||
writeLog,
|
||||
readDirectory: (path, extensions, exclude, include, depth?) => directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth),
|
||||
const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
|
||||
// Members for CompilerHost
|
||||
const getNewSourceFile = compilerHost.getSourceFile;
|
||||
compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args);
|
||||
compilerHost.getSourceFileByPath = getVersionedSourceFileByPath;
|
||||
compilerHost.getNewLine = () => newLine;
|
||||
compilerHost.fileExists = fileExists;
|
||||
compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile;
|
||||
// Members for ResolutionCacheHost
|
||||
compilerHost.toPath = toPath;
|
||||
compilerHost.getCompilationSettings = () => compilerOptions;
|
||||
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.FailedLookupLocations);
|
||||
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.TypeRoots);
|
||||
compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost;
|
||||
compilerHost.onInvalidatedResolution = scheduleProgramUpdate;
|
||||
compilerHost.onChangedAutomaticTypeDirectiveNames = () => {
|
||||
hasChangedAutomaticTypeDirectiveNames = true;
|
||||
scheduleProgramUpdate();
|
||||
};
|
||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation;
|
||||
compilerHost.getCurrentProgram = getCurrentProgram;
|
||||
compilerHost.writeLog = writeLog;
|
||||
|
||||
// Cache for the module resolution
|
||||
const resolutionCache = createResolutionCache(compilerHost, configFileName ?
|
||||
getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) :
|
||||
|
@ -630,11 +683,9 @@ namespace ts {
|
|||
|
||||
function createNewProgram(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
// Compile the program
|
||||
if (watchLogLevel !== WatchLogLevel.None) {
|
||||
writeLog("CreatingProgramWith::");
|
||||
writeLog(` roots: ${JSON.stringify(rootFileNames)}`);
|
||||
writeLog(` options: ${JSON.stringify(compilerOptions)}`);
|
||||
}
|
||||
writeLog("CreatingProgramWith::");
|
||||
writeLog(` roots: ${JSON.stringify(rootFileNames)}`);
|
||||
writeLog(` options: ${JSON.stringify(compilerOptions)}`);
|
||||
|
||||
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
|
||||
hasChangedCompilerOptions = false;
|
||||
|
@ -708,7 +759,7 @@ namespace ts {
|
|||
|
||||
// Create new source file if requested or the versions dont match
|
||||
if (!hostSourceFile || shouldCreateNewSourceFile || !isFilePresentOnHost(hostSourceFile) || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||
const sourceFile = getNewSourceFile();
|
||||
const sourceFile = getNewSourceFile(fileName, languageVersion, onError);
|
||||
if (hostSourceFile) {
|
||||
if (shouldCreateNewSourceFile) {
|
||||
hostSourceFile.version++;
|
||||
|
@ -719,7 +770,7 @@ namespace ts {
|
|||
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
|
||||
sourceFile.version = hostSourceFile.version.toString();
|
||||
if (!(hostSourceFile as FilePresentOnHost).fileWatcher) {
|
||||
(hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, "Source file");
|
||||
(hostSourceFile as FilePresentOnHost).fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -733,7 +784,7 @@ namespace ts {
|
|||
else {
|
||||
if (sourceFile) {
|
||||
sourceFile.version = initialVersion.toString();
|
||||
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, "Source file");
|
||||
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
|
||||
sourceFilesCache.set(path, { sourceFile, version: initialVersion, fileWatcher });
|
||||
}
|
||||
else {
|
||||
|
@ -743,23 +794,6 @@ namespace ts {
|
|||
return sourceFile;
|
||||
}
|
||||
return hostSourceFile.sourceFile;
|
||||
|
||||
function getNewSourceFile() {
|
||||
let text: string | undefined;
|
||||
try {
|
||||
performance.mark("beforeIORead");
|
||||
text = host.readFile(fileName, compilerOptions.charset);
|
||||
performance.mark("afterIORead");
|
||||
performance.measure("I/O Read", "beforeIORead", "afterIORead");
|
||||
}
|
||||
catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function nextSourceFileVersion(path: Path) {
|
||||
|
@ -907,7 +941,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function watchMissingFilePath(missingFilePath: Path) {
|
||||
return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, "Missing file");
|
||||
return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, WatchType.MissingFile);
|
||||
}
|
||||
|
||||
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
|
||||
|
@ -971,33 +1005,8 @@ namespace ts {
|
|||
}
|
||||
},
|
||||
flags,
|
||||
"Wild card directories"
|
||||
WatchType.WildcardDirectory
|
||||
);
|
||||
}
|
||||
|
||||
function ensureDirectoriesExist(directoryPath: string) {
|
||||
if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists!(directoryPath)) {
|
||||
const parentDirectory = getDirectoryPath(directoryPath);
|
||||
ensureDirectoriesExist(parentDirectory);
|
||||
host.createDirectory!(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
|
||||
try {
|
||||
performance.mark("beforeIOWrite");
|
||||
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
|
||||
|
||||
host.writeFile!(fileName, text, writeByteOrderMark);
|
||||
|
||||
performance.mark("afterIOWrite");
|
||||
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
|
||||
}
|
||||
catch (e) {
|
||||
if (onError) {
|
||||
onError(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -343,10 +343,10 @@ namespace ts {
|
|||
export interface WatchDirectoryHost {
|
||||
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
|
||||
}
|
||||
export type WatchFile<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
|
||||
export type WatchFile<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
|
||||
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
|
||||
export type WatchFilePath<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
|
||||
export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1?: X, detailInfo2?: Y) => FileWatcher;
|
||||
export type WatchFilePath<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
|
||||
export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
|
||||
|
||||
export interface WatchFactory<X, Y> {
|
||||
watchFile: WatchFile<X, Y>;
|
||||
|
@ -444,7 +444,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getWatchInfo<T, X, Y>(file: string, flags: T, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) {
|
||||
return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo1}`;
|
||||
return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`;
|
||||
}
|
||||
|
||||
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
|
||||
|
|
|
@ -375,7 +375,12 @@ namespace fakes {
|
|||
}
|
||||
}
|
||||
|
||||
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost {
|
||||
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
|
||||
createProgram = ts.createAbstractBuilder;
|
||||
now() {
|
||||
return new Date(this.sys.vfs.time());
|
||||
}
|
||||
|
||||
diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
reportDiagnostic(diagnostic: ts.Diagnostic) {
|
||||
|
|
|
@ -332,19 +332,6 @@ namespace ts.server {
|
|||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum WatchType {
|
||||
ConfigFilePath = "Config file for the program",
|
||||
MissingFilePath = "Missing file from program",
|
||||
WildcardDirectories = "Wild card directory",
|
||||
ClosedScriptInfo = "Closed Script info",
|
||||
ConfigFileForInferredRoot = "Config file for the inferred project root",
|
||||
FailedLookupLocation = "Directory of Failed lookup locations in module resolution",
|
||||
TypeRoots = "Type root directory",
|
||||
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
|
||||
MissingSourceMapFile = "Missing source map file"
|
||||
}
|
||||
|
||||
const enum ConfigFileWatcherStatus {
|
||||
ReloadingFiles = "Reloading configured projects for files",
|
||||
ReloadingInferredRootFiles = "Reloading configured projects for only inferred root files",
|
||||
|
@ -1035,7 +1022,7 @@ namespace ts.server {
|
|||
}
|
||||
},
|
||||
flags,
|
||||
WatchType.WildcardDirectories,
|
||||
WatchType.WildcardDirectory,
|
||||
project
|
||||
);
|
||||
}
|
||||
|
@ -1339,7 +1326,7 @@ namespace ts.server {
|
|||
watches.push(WatchType.ConfigFileForInferredRoot);
|
||||
}
|
||||
if (this.configuredProjects.has(canonicalConfigFilePath)) {
|
||||
watches.push(WatchType.ConfigFilePath);
|
||||
watches.push(WatchType.ConfigFile);
|
||||
}
|
||||
this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`);
|
||||
}
|
||||
|
@ -1706,7 +1693,7 @@ namespace ts.server {
|
|||
configFileName,
|
||||
(_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind),
|
||||
PollingInterval.High,
|
||||
WatchType.ConfigFilePath,
|
||||
WatchType.ConfigFile,
|
||||
project
|
||||
);
|
||||
this.configuredProjects.set(project.canonicalConfigFilePath, project);
|
||||
|
|
|
@ -428,7 +428,7 @@ namespace ts.server {
|
|||
directory,
|
||||
cb,
|
||||
flags,
|
||||
WatchType.FailedLookupLocation,
|
||||
WatchType.FailedLookupLocations,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
@ -989,7 +989,7 @@ namespace ts.server {
|
|||
}
|
||||
},
|
||||
PollingInterval.Medium,
|
||||
WatchType.MissingFilePath,
|
||||
WatchType.MissingFile,
|
||||
this
|
||||
);
|
||||
return fileWatcher;
|
||||
|
|
|
@ -217,3 +217,14 @@ namespace ts.server {
|
|||
return indentStr + JSON.stringify(json);
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
// Additional tsserver specific watch information
|
||||
export const enum WatchType {
|
||||
ClosedScriptInfo = "Closed Script info",
|
||||
ConfigFileForInferredRoot = "Config file for the inferred project root",
|
||||
NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them",
|
||||
MissingSourceMapFile = "Missing source map file",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace ts {
|
|||
|
||||
// We shouldn't have any errors about invalid tsconfig files in these tests
|
||||
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
|
||||
const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHost(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
|
||||
const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHostLike(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName);
|
||||
file.options.configFilePath = entryPointConfigFileName;
|
||||
const prog = createProgram({
|
||||
rootNames: file.fileNames,
|
||||
|
|
|
@ -234,39 +234,46 @@ namespace ts {
|
|||
// Update a timestamp in the middle project
|
||||
tick();
|
||||
touch(fs, "/src/logic/index.ts");
|
||||
const originalWriteFile = fs.writeFileSync;
|
||||
const writtenFiles = createMap<true>();
|
||||
fs.writeFileSync = (path, data, encoding) => {
|
||||
writtenFiles.set(path, true);
|
||||
originalWriteFile.call(fs, path, data, encoding);
|
||||
};
|
||||
// Because we haven't reset the build context, the builder should assume there's nothing to do right now
|
||||
const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic"));
|
||||
assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date");
|
||||
verifyInvalidation(/*expectedToWriteTests*/ false);
|
||||
|
||||
// Rebuild this project
|
||||
tick();
|
||||
builder.invalidateProject("/src/logic");
|
||||
builder.buildInvalidatedProject();
|
||||
// The file should be updated
|
||||
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
|
||||
|
||||
// Does not build tests or core because there is no change in declaration file
|
||||
tick();
|
||||
builder.buildInvalidatedProject();
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
|
||||
|
||||
// Rebuild this project
|
||||
tick();
|
||||
fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")}
|
||||
export class cNew {}`);
|
||||
builder.invalidateProject("/src/logic");
|
||||
builder.buildInvalidatedProject();
|
||||
// The file should be updated
|
||||
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
|
||||
verifyInvalidation(/*expectedToWriteTests*/ true);
|
||||
|
||||
// Build downstream projects should update 'tests', but not 'core'
|
||||
tick();
|
||||
builder.buildInvalidatedProject();
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
|
||||
function verifyInvalidation(expectedToWriteTests: boolean) {
|
||||
// Rebuild this project
|
||||
tick();
|
||||
builder.invalidateProject("/src/logic");
|
||||
builder.buildInvalidatedProject();
|
||||
// The file should be updated
|
||||
assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt");
|
||||
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
|
||||
assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
|
||||
writtenFiles.clear();
|
||||
|
||||
// Build downstream projects should update 'tests', but not 'core'
|
||||
tick();
|
||||
builder.buildInvalidatedProject();
|
||||
if (expectedToWriteTests) {
|
||||
assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt");
|
||||
}
|
||||
else {
|
||||
assert.equal(writtenFiles.size, 0, "Should not write any new files");
|
||||
}
|
||||
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp");
|
||||
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -2,18 +2,37 @@ namespace ts.tscWatch {
|
|||
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
|
||||
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
|
||||
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
|
||||
type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map<true>; };
|
||||
|
||||
function createTsBuildWatchSystem(fileOrFolderList: ReadonlyArray<TestFSWithWatch.FileOrFolderOrSymLink>, params?: TestFSWithWatch.TestServerHostCreationParameters) {
|
||||
const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
|
||||
const originalWriteFile = host.writeFile;
|
||||
host.writtenFiles = createMap<true>();
|
||||
host.writeFile = (fileName, content) => {
|
||||
originalWriteFile.call(host, fileName, content);
|
||||
const path = host.toFullPath(fileName);
|
||||
host.writtenFiles.set(path, true);
|
||||
};
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
|
||||
const host = createSolutionBuilderWithWatchHost(system);
|
||||
return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true });
|
||||
}
|
||||
|
||||
function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
|
||||
function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
|
||||
const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions);
|
||||
solutionBuilder.buildAllProjects();
|
||||
solutionBuilder.startWatching();
|
||||
return solutionBuilder;
|
||||
}
|
||||
|
||||
type OutputFileStamp = [string, Date | undefined, boolean];
|
||||
function transformOutputToOutputFileStamp(f: string, host: TsBuildWatchSystem): OutputFileStamp {
|
||||
return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp;
|
||||
}
|
||||
|
||||
describe("unittests:: tsbuild-watch program updates", () => {
|
||||
const project = "sample1";
|
||||
const enum SubProject {
|
||||
|
@ -61,12 +80,11 @@ namespace ts.tscWatch {
|
|||
return [`${file}.js`, `${file}.d.ts`];
|
||||
}
|
||||
|
||||
type OutputFileStamp = [string, Date | undefined];
|
||||
function getOutputStamps(host: WatchedSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
|
||||
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp);
|
||||
function getOutputStamps(host: TsBuildWatchSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
|
||||
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => transformOutputToOutputFileStamp(f, host));
|
||||
}
|
||||
|
||||
function getOutputFileStamps(host: WatchedSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] {
|
||||
function getOutputFileStamps(host: TsBuildWatchSystem, additionalFiles?: ReadonlyArray<[SubProject, string]>): OutputFileStamp[] {
|
||||
const result = [
|
||||
...getOutputStamps(host, SubProject.core, "anotherModule"),
|
||||
...getOutputStamps(host, SubProject.core, "index"),
|
||||
|
@ -76,18 +94,21 @@ namespace ts.tscWatch {
|
|||
if (additionalFiles) {
|
||||
additionalFiles.forEach(([subProject, baseFileNameWithoutExtension]) => result.push(...getOutputStamps(host, subProject, baseFileNameWithoutExtension)));
|
||||
}
|
||||
host.writtenFiles.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) {
|
||||
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: ReadonlyArray<string>, modifiedTimeStampFiles: ReadonlyArray<string>) {
|
||||
for (let i = 0; i < oldTimeStamps.length; i++) {
|
||||
const actual = actualStamps[i];
|
||||
const old = oldTimeStamps[i];
|
||||
if (contains(changedFiles, actual[0])) {
|
||||
assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} expected to written`);
|
||||
const expectedIsChanged = contains(changedFiles, actual[0]);
|
||||
assert.equal(actual[2], contains(changedFiles, actual[0]), `Expected ${actual[0]} to be written.`);
|
||||
if (expectedIsChanged || contains(modifiedTimeStampFiles, actual[0])) {
|
||||
assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} file expected to have newer modified time because it is expected to ${expectedIsChanged ? "be changed" : "have modified time stamp"}`);
|
||||
}
|
||||
else {
|
||||
assert.equal(actual[1], old[1], `${actual[0]} expected to not change`);
|
||||
assert.equal(actual[1], old[1], `${actual[0]} expected to not change or have timestamp modified.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +122,7 @@ namespace ts.tscWatch {
|
|||
const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
|
||||
|
||||
function createSolutionInWatchMode(allFiles: ReadonlyArray<File>, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) {
|
||||
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
|
||||
const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
|
||||
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], defaultOptions);
|
||||
verifyWatches(host);
|
||||
checkOutputErrorsInitial(host, emptyArray, disableConsoleClears);
|
||||
|
@ -112,7 +133,7 @@ namespace ts.tscWatch {
|
|||
return host;
|
||||
}
|
||||
|
||||
function verifyWatches(host: WatchedSystem) {
|
||||
function verifyWatches(host: TsBuildWatchSystem) {
|
||||
checkWatchedFiles(host, testProjectExpectedWatchedFiles);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
|
||||
|
@ -134,30 +155,50 @@ namespace ts.tscWatch {
|
|||
const host = createSolutionInWatchMode(allFiles);
|
||||
return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches };
|
||||
|
||||
function verifyChangeWithFile(fileName: string, content: string) {
|
||||
function verifyChangeWithFile(fileName: string, content: string, local?: boolean) {
|
||||
const outputFileStamps = getOutputFileStamps(host, additionalFiles);
|
||||
host.writeFile(fileName, content);
|
||||
verifyChangeAfterTimeout(outputFileStamps);
|
||||
verifyChangeAfterTimeout(outputFileStamps, local);
|
||||
}
|
||||
|
||||
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
|
||||
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[], local?: boolean) {
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds core
|
||||
const changedCore = getOutputFileStamps(host, additionalFiles);
|
||||
verifyChangedFiles(changedCore, outputFileStamps, [
|
||||
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
|
||||
...getOutputFileNames(SubProject.core, "index"),
|
||||
...(additionalFiles ? getOutputFileNames(SubProject.core, newFileWithoutExtension) : emptyArray)
|
||||
]);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
|
||||
verifyChangedFiles(
|
||||
changedCore,
|
||||
outputFileStamps,
|
||||
additionalFiles ?
|
||||
getOutputFileNames(SubProject.core, newFileWithoutExtension) :
|
||||
getOutputFileNames(SubProject.core, "index"), // Written files are new file or core index file thats changed
|
||||
[
|
||||
...getOutputFileNames(SubProject.core, "anotherModule"),
|
||||
...(additionalFiles ? getOutputFileNames(SubProject.core, "index") : emptyArray)
|
||||
]
|
||||
);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds logic or updates timestamps
|
||||
const changedLogic = getOutputFileStamps(host, additionalFiles);
|
||||
verifyChangedFiles(changedLogic, changedCore, [
|
||||
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
|
||||
]);
|
||||
verifyChangedFiles(
|
||||
changedLogic,
|
||||
changedCore,
|
||||
additionalFiles || local ?
|
||||
emptyArray :
|
||||
getOutputFileNames(SubProject.logic, "index"),
|
||||
additionalFiles || local ?
|
||||
getOutputFileNames(SubProject.logic, "index") :
|
||||
emptyArray
|
||||
);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
|
||||
const changedTests = getOutputFileStamps(host, additionalFiles);
|
||||
verifyChangedFiles(changedTests, changedLogic, [
|
||||
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
|
||||
]);
|
||||
verifyChangedFiles(
|
||||
changedTests,
|
||||
changedLogic,
|
||||
additionalFiles || local ?
|
||||
emptyArray :
|
||||
getOutputFileNames(SubProject.tests, "index"),
|
||||
additionalFiles || local ?
|
||||
getOutputFileNames(SubProject.tests, "index") :
|
||||
emptyArray
|
||||
);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
verifyWatches();
|
||||
|
@ -193,19 +234,9 @@ export class someClass2 { }`);
|
|||
});
|
||||
|
||||
it("non local change does not start build of referencing projects", () => {
|
||||
const host = createSolutionInWatchMode(allFiles);
|
||||
const outputFileStamps = getOutputFileStamps(host);
|
||||
host.writeFile(core[1].path, `${core[1].content}
|
||||
function foo() { }`);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds core
|
||||
const changedCore = getOutputFileStamps(host);
|
||||
verifyChangedFiles(changedCore, outputFileStamps, [
|
||||
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
|
||||
...getOutputFileNames(SubProject.core, "index"),
|
||||
]);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
verifyWatches(host);
|
||||
const { verifyChangeWithFile } = createSolutionInWatchModeToVerifyChanges();
|
||||
verifyChangeWithFile(core[1].path, `${core[1].content}
|
||||
function foo() { }`, /*local*/ true);
|
||||
});
|
||||
|
||||
it("builds when new file is added, and its subsequent updates", () => {
|
||||
|
@ -242,7 +273,7 @@ export class someClass2 { }`);
|
|||
|
||||
it("watches config files that are not present", () => {
|
||||
const allFiles = [libFile, ...core, logic[1], ...tests];
|
||||
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
|
||||
const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
|
||||
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]);
|
||||
checkWatchedFiles(host, [core[0], core[1], core[2]!, logic[0], ...tests].map(f => f.path.toLowerCase())); // tslint:disable-line no-unnecessary-type-assertion (TODO: type assertion should be necessary)
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
|
@ -268,14 +299,10 @@ export class someClass2 { }`);
|
|||
host.writeFile(logic[0].path, logic[0].content);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
|
||||
const changedLogic = getOutputFileStamps(host);
|
||||
verifyChangedFiles(changedLogic, initial, [
|
||||
...getOutputFileNames(SubProject.logic, "index")
|
||||
]);
|
||||
verifyChangedFiles(changedLogic, initial, getOutputFileNames(SubProject.logic, "index"), emptyArray);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
|
||||
const changedTests = getOutputFileStamps(host);
|
||||
verifyChangedFiles(changedTests, changedLogic, [
|
||||
...getOutputFileNames(SubProject.tests, "index")
|
||||
]);
|
||||
verifyChangedFiles(changedTests, changedLogic, getOutputFileNames(SubProject.tests, "index"), emptyArray);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
verifyWatches(host);
|
||||
|
@ -305,7 +332,7 @@ export class someClass2 { }`);
|
|||
};
|
||||
|
||||
const projectFiles = [coreTsConfig, coreIndex, logicTsConfig, logicIndex];
|
||||
const host = createWatchedSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation });
|
||||
const host = createTsBuildWatchSystem([libFile, ...projectFiles], { currentDirectory: projectsLocation });
|
||||
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.logic}`]);
|
||||
verifyWatches();
|
||||
checkOutputErrorsInitial(host, emptyArray);
|
||||
|
@ -318,6 +345,7 @@ export class someClass2 { }`);
|
|||
verifyChangeInCore(`${coreIndex.content}
|
||||
function myFunc() { return 10; }`);
|
||||
|
||||
// TODO:: local change does not build logic.js because builder doesnt find any changes in input files to generate output
|
||||
// Make local change to function bar
|
||||
verifyChangeInCore(`${coreIndex.content}
|
||||
function myFunc() { return 100; }`);
|
||||
|
@ -328,14 +356,20 @@ function myFunc() { return 100; }`);
|
|||
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds core
|
||||
const changedCore = getOutputFileStamps();
|
||||
verifyChangedFiles(changedCore, outputFileStamps, [
|
||||
...getOutputFileNames(SubProject.core, "index")
|
||||
]);
|
||||
verifyChangedFiles(
|
||||
changedCore,
|
||||
outputFileStamps,
|
||||
getOutputFileNames(SubProject.core, "index"),
|
||||
emptyArray
|
||||
);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
|
||||
const changedLogic = getOutputFileStamps();
|
||||
verifyChangedFiles(changedLogic, changedCore, [
|
||||
...getOutputFileNames(SubProject.logic, "index")
|
||||
]);
|
||||
verifyChangedFiles(
|
||||
changedLogic,
|
||||
changedCore,
|
||||
getOutputFileNames(SubProject.logic, "index"),
|
||||
emptyArray
|
||||
);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
verifyWatches();
|
||||
|
@ -346,6 +380,7 @@ function myFunc() { return 100; }`);
|
|||
...getOutputStamps(host, SubProject.core, "index"),
|
||||
...getOutputStamps(host, SubProject.logic, "index"),
|
||||
];
|
||||
host.writtenFiles.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -389,7 +424,7 @@ createSomeObject().message;`
|
|||
};
|
||||
|
||||
const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
|
||||
const host = createWatchedSystem(files, { currentDirectory: `${projectsLocation}/${project}` });
|
||||
const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${project}` });
|
||||
createSolutionBuilderWithWatch(host, ["App"]);
|
||||
checkOutputErrorsInitial(host, emptyArray);
|
||||
|
||||
|
@ -418,7 +453,7 @@ let y: string = 10;`);
|
|||
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
|
||||
const changedLogic = getOutputFileStamps(host);
|
||||
verifyChangedFiles(changedLogic, outputFileStamps, emptyArray);
|
||||
verifyChangedFiles(changedLogic, outputFileStamps, emptyArray, emptyArray);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, [
|
||||
`sample1/logic/index.ts(8,5): error TS2322: Type '10' is not assignable to type 'string'.\n`
|
||||
|
@ -429,7 +464,7 @@ let x: string = 10;`);
|
|||
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds core
|
||||
const changedCore = getOutputFileStamps(host);
|
||||
verifyChangedFiles(changedCore, changedLogic, emptyArray);
|
||||
verifyChangedFiles(changedCore, changedLogic, emptyArray, emptyArray);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, [
|
||||
`sample1/core/index.ts(5,5): error TS2322: Type '10' is not assignable to type 'string'.\n`,
|
||||
|
@ -444,11 +479,118 @@ let x: string = 10;`);
|
|||
it("when preserveWatchOutput is passed on command line", () => {
|
||||
verifyIncrementalErrors({ preserveWatchOutput: true, watch: true }, /*disabledConsoleClear*/ true);
|
||||
});
|
||||
|
||||
describe("when declaration emit errors are present", () => {
|
||||
const solution = "solution";
|
||||
const subProject = "app";
|
||||
const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`;
|
||||
const fileWithError: File = {
|
||||
path: `${subProjectLocation}/fileWithError.ts`,
|
||||
content: `export var myClassWithError = class {
|
||||
tags() { }
|
||||
private p = 12
|
||||
};`
|
||||
};
|
||||
const fileWithFixedError: File = {
|
||||
path: fileWithError.path,
|
||||
content: fileWithError.content.replace("private p = 12", "")
|
||||
};
|
||||
const fileWithoutError: File = {
|
||||
path: `${subProjectLocation}/fileWithoutError.ts`,
|
||||
content: `export class myClass { }`
|
||||
};
|
||||
const tsconfig: File = {
|
||||
path: `${subProjectLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({ compilerOptions: { composite: true } })
|
||||
};
|
||||
const expectedDtsEmitErrors = [
|
||||
`${subProject}/fileWithError.ts(1,12): error TS4094: Property 'p' of exported class expression may not be private or protected.\n`
|
||||
];
|
||||
const outputs = [
|
||||
changeExtension(fileWithError.path, Extension.Js),
|
||||
changeExtension(fileWithError.path, Extension.Dts),
|
||||
changeExtension(fileWithoutError.path, Extension.Js),
|
||||
changeExtension(fileWithoutError.path, Extension.Dts)
|
||||
];
|
||||
|
||||
function verifyDtsErrors(host: TsBuildWatchSystem, isIncremental: boolean, expectedErrors: ReadonlyArray<string>) {
|
||||
(isIncremental ? checkOutputErrorsIncremental : checkOutputErrorsInitial)(host, expectedErrors);
|
||||
outputs.forEach(f => assert.equal(host.fileExists(f), !expectedErrors.length, `Expected file ${f} to ${!expectedErrors.length ? "exist" : "not exist"}`));
|
||||
}
|
||||
|
||||
function createSolutionWithWatch(withFixedError?: true) {
|
||||
const files = [libFile, withFixedError ? fileWithFixedError : fileWithError, fileWithoutError, tsconfig];
|
||||
const host = createTsBuildWatchSystem(files, { currentDirectory: `${projectsLocation}/${solution}` });
|
||||
createSolutionBuilderWithWatch(host, [subProject]);
|
||||
verifyDtsErrors(host, /*isIncremental*/ false, withFixedError ? emptyArray : expectedDtsEmitErrors);
|
||||
return host;
|
||||
}
|
||||
|
||||
function incrementalBuild(host: TsBuildWatchSystem) {
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Build the app
|
||||
host.checkTimeoutQueueLength(0);
|
||||
}
|
||||
|
||||
function fixError(host: TsBuildWatchSystem) {
|
||||
// Fix error
|
||||
host.writeFile(fileWithError.path, fileWithFixedError.content);
|
||||
host.writtenFiles.clear();
|
||||
incrementalBuild(host);
|
||||
verifyDtsErrors(host, /*isIncremental*/ true, emptyArray);
|
||||
}
|
||||
|
||||
it("when fixing error files all files are emitted", () => {
|
||||
const host = createSolutionWithWatch();
|
||||
fixError(host);
|
||||
});
|
||||
|
||||
it("when file with no error changes, declaration errors are reported", () => {
|
||||
const host = createSolutionWithWatch();
|
||||
host.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2"));
|
||||
incrementalBuild(host);
|
||||
verifyDtsErrors(host, /*isIncremental*/ true, expectedDtsEmitErrors);
|
||||
});
|
||||
|
||||
describe("when reporting errors on introducing error", () => {
|
||||
function createSolutionWithIncrementalError() {
|
||||
const host = createSolutionWithWatch(/*withFixedError*/ true);
|
||||
host.writeFile(fileWithError.path, fileWithError.content);
|
||||
host.writtenFiles.clear();
|
||||
|
||||
incrementalBuild(host);
|
||||
checkOutputErrorsIncremental(host, expectedDtsEmitErrors);
|
||||
assert.equal(host.writtenFiles.size, 0, `Expected not to write any files: ${arrayFrom(host.writtenFiles.keys())}`);
|
||||
return host;
|
||||
}
|
||||
|
||||
function verifyWrittenFile(host: TsBuildWatchSystem, f: string) {
|
||||
assert.isTrue(host.writtenFiles.has(host.toFullPath(f)), `Expected to write ${f}: ${arrayFrom(host.writtenFiles.keys())}`);
|
||||
}
|
||||
|
||||
it("when fixing errors only changed file is emitted", () => {
|
||||
const host = createSolutionWithIncrementalError();
|
||||
fixError(host);
|
||||
assert.equal(host.writtenFiles.size, 2, `Expected to write only changed files: ${arrayFrom(host.writtenFiles.keys())}`);
|
||||
verifyWrittenFile(host, outputs[0]);
|
||||
verifyWrittenFile(host, outputs[1]);
|
||||
});
|
||||
|
||||
it("when file with no error changes, declaration errors are reported", () => {
|
||||
const host = createSolutionWithIncrementalError();
|
||||
host.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2"));
|
||||
host.writtenFiles.clear();
|
||||
|
||||
incrementalBuild(host);
|
||||
checkOutputErrorsIncremental(host, expectedDtsEmitErrors);
|
||||
assert.equal(host.writtenFiles.size, 0, `Expected not to write any files: ${arrayFrom(host.writtenFiles.keys())}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsc-watch and tsserver works with project references", () => {
|
||||
describe("invoking when references are already built", () => {
|
||||
function verifyWatchesOfProject(host: WatchedSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
|
||||
function verifyWatchesOfProject(host: TsBuildWatchSystem, expectedWatchedFiles: ReadonlyArray<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>, expectedWatchedDirectories?: ReadonlyArray<string>) {
|
||||
checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
|
||||
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories || emptyArray, 1, /*recursive*/ false);
|
||||
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true);
|
||||
|
@ -457,9 +599,9 @@ let x: string = 10;`);
|
|||
function createSolutionOfProject(allFiles: ReadonlyArray<File>,
|
||||
currentDirectory: string,
|
||||
solutionBuilderconfig: string,
|
||||
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
|
||||
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
|
||||
// Build the composite project
|
||||
const host = createWatchedSystem(allFiles, { currentDirectory });
|
||||
const host = createTsBuildWatchSystem(allFiles, { currentDirectory });
|
||||
const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {});
|
||||
solutionBuilder.buildAllProjects();
|
||||
const outputFileStamps = getOutputFileStamps(host);
|
||||
|
@ -474,7 +616,7 @@ let x: string = 10;`);
|
|||
currentDirectory: string,
|
||||
solutionBuilderconfig: string,
|
||||
watchConfig: string,
|
||||
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
|
||||
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
|
||||
// Build the composite project
|
||||
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
|
||||
|
||||
|
@ -489,7 +631,7 @@ let x: string = 10;`);
|
|||
currentDirectory: string,
|
||||
solutionBuilderconfig: string,
|
||||
openFileName: string,
|
||||
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
|
||||
getOutputFileStamps: (host: TsBuildWatchSystem) => ReadonlyArray<OutputFileStamp>) {
|
||||
// Build the composite project
|
||||
const { host, solutionBuilder } = createSolutionOfProject(allFiles, currentDirectory, solutionBuilderconfig, getOutputFileStamps);
|
||||
|
||||
|
@ -525,12 +667,12 @@ let x: string = 10;`);
|
|||
return createSolutionAndServiceOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[1].path, getOutputFileStamps);
|
||||
}
|
||||
|
||||
function verifyWatches(host: WatchedSystem, withTsserver?: boolean) {
|
||||
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
|
||||
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles.filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles, expectedWatchedDirectoriesRecursive);
|
||||
}
|
||||
|
||||
function verifyScenario(
|
||||
edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void,
|
||||
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
|
||||
expectedFilesAfterEdit: ReadonlyArray<string>
|
||||
) {
|
||||
it("with tsc-watch", () => {
|
||||
|
@ -633,7 +775,7 @@ export function gfoo() {
|
|||
}
|
||||
|
||||
function verifyWatchState(
|
||||
host: WatchedSystem,
|
||||
host: TsBuildWatchSystem,
|
||||
watch: Watch,
|
||||
expectedProgramFiles: ReadonlyArray<string>,
|
||||
expectedWatchedFiles: ReadonlyArray<string>,
|
||||
|
@ -720,20 +862,20 @@ export function gfoo() {
|
|||
return createSolutionAndServiceOfProject(allFiles, getProjectPath(project), configToBuild, cTs.path, getOutputFileStamps);
|
||||
}
|
||||
|
||||
function getOutputFileStamps(host: WatchedSystem) {
|
||||
return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp);
|
||||
function getOutputFileStamps(host: TsBuildWatchSystem) {
|
||||
return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
|
||||
}
|
||||
|
||||
function verifyProgram(host: WatchedSystem, watch: Watch) {
|
||||
function verifyProgram(host: TsBuildWatchSystem, watch: Watch) {
|
||||
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies, expectedWatchedDirectories);
|
||||
}
|
||||
|
||||
function verifyProject(host: WatchedSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray<string>) {
|
||||
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: ReadonlyArray<string>) {
|
||||
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
|
||||
}
|
||||
|
||||
function verifyServerState(
|
||||
host: WatchedSystem,
|
||||
host: TsBuildWatchSystem,
|
||||
service: projectSystem.TestProjectService,
|
||||
expectedProgramFiles: ReadonlyArray<string>,
|
||||
expectedWatchedFiles: ReadonlyArray<string>,
|
||||
|
@ -753,13 +895,13 @@ export function gfoo() {
|
|||
}
|
||||
|
||||
function verifyScenario(
|
||||
edit: (host: WatchedSystem, solutionBuilder: SolutionBuilder) => void,
|
||||
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void,
|
||||
expectedEditErrors: ReadonlyArray<string>,
|
||||
expectedProgramFiles: ReadonlyArray<string>,
|
||||
expectedWatchedFiles: ReadonlyArray<string>,
|
||||
expectedWatchedDirectoriesRecursive: ReadonlyArray<string>,
|
||||
dependencies: ReadonlyArray<[string, ReadonlyArray<string>]>,
|
||||
revert?: (host: WatchedSystem) => void,
|
||||
revert?: (host: TsBuildWatchSystem) => void,
|
||||
orphanInfosAfterEdit?: ReadonlyArray<string>,
|
||||
orphanInfosAfterRevert?: ReadonlyArray<string>) {
|
||||
it("with tsc-watch", () => {
|
||||
|
@ -978,8 +1120,8 @@ export function gfoo() {
|
|||
[refs.path, [refs.path]],
|
||||
[cTsFile.path, [cTsFile.path, refs.path, bDts]]
|
||||
];
|
||||
function getOutputFileStamps(host: WatchedSystem) {
|
||||
return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp);
|
||||
function getOutputFileStamps(host: TsBuildWatchSystem) {
|
||||
return expectedFiles.map(file => transformOutputToOutputFileStamp(file, host));
|
||||
}
|
||||
const { host, watch } = createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), "tsconfig.c.json", "tsconfig.c.json", getOutputFileStamps);
|
||||
verifyWatchState(host, watch, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, defaultDependencies);
|
||||
|
|
|
@ -204,12 +204,11 @@ namespace ts {
|
|||
reportWatchModeWithoutSysSupport();
|
||||
}
|
||||
|
||||
// TODO: change this to host if watch => watchHost otherwiue without watch
|
||||
const buildHost = buildOptions.watch ?
|
||||
createSolutionBuilderWithWatchHost(sys, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) :
|
||||
createSolutionBuilderHost(sys, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions));
|
||||
buildHost.beforeCreateProgram = enableStatistics;
|
||||
buildHost.afterProgramEmitAndDiagnostics = reportStatistics;
|
||||
createSolutionBuilderWithWatchHost(sys, createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()) :
|
||||
createSolutionBuilderHost(sys, createAbstractBuilder, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createReportErrorSummary(buildOptions));
|
||||
updateCreateProgram(buildHost);
|
||||
buildHost.afterProgramEmitAndDiagnostics = (program: BuilderProgram) => reportStatistics(program.getProgram());
|
||||
|
||||
const builder = createSolutionBuilder(buildHost, projects, buildOptions);
|
||||
if (buildOptions.clean) {
|
||||
|
@ -234,7 +233,7 @@ namespace ts {
|
|||
const host = createCompilerHost(options);
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
|
||||
changeCompilerHostToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName), /*useCacheForSourceFile*/ false);
|
||||
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
enableStatistics(options);
|
||||
|
||||
const programOptions: CreateProgramOptions = {
|
||||
|
@ -255,15 +254,19 @@ namespace ts {
|
|||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>) {
|
||||
const compileUsingBuilder = watchCompilerHost.createProgram;
|
||||
watchCompilerHost.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
|
||||
function updateCreateProgram<T extends BuilderProgram>(host: { createProgram: CreateProgram<T>; }) {
|
||||
const compileUsingBuilder = host.createProgram;
|
||||
host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
|
||||
Debug.assert(rootNames !== undefined || (options === undefined && !!oldProgram));
|
||||
if (options !== undefined) {
|
||||
enableStatistics(options);
|
||||
}
|
||||
return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences);
|
||||
};
|
||||
}
|
||||
|
||||
function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>) {
|
||||
updateCreateProgram(watchCompilerHost);
|
||||
const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217
|
||||
watchCompilerHost.afterProgramCreate = builderProgram => {
|
||||
emitFilesUsingBuilder(builderProgram);
|
||||
|
|
|
@ -1778,7 +1778,7 @@ declare namespace ts {
|
|||
type ResolvedConfigFileName = string & {
|
||||
_isResolvedConfigFileName: never;
|
||||
};
|
||||
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles?: ReadonlyArray<SourceFile>) => void;
|
||||
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray<SourceFile>) => void;
|
||||
class OperationCanceledException {
|
||||
}
|
||||
interface CancellationToken {
|
||||
|
@ -2689,7 +2689,6 @@ declare namespace ts {
|
|||
getDefaultLibLocation?(): string;
|
||||
writeFile: WriteFileCallback;
|
||||
getCurrentDirectory(): string;
|
||||
getDirectories(path: string): string[];
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getNewLine(): string;
|
||||
|
@ -4279,6 +4278,10 @@ declare namespace ts {
|
|||
* Get the syntax diagnostics, for all source files if source file is not supplied
|
||||
*/
|
||||
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
|
||||
/**
|
||||
* Get the declaration diagnostics, for all source files if source file is not supplied
|
||||
*/
|
||||
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
||||
/**
|
||||
* Get all the dependencies of the file
|
||||
*/
|
||||
|
@ -4365,13 +4368,11 @@ declare namespace ts {
|
|||
/** If provided, will be used to reset existing delayed compilation */
|
||||
clearTimeout?(timeoutId: any): void;
|
||||
}
|
||||
interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
|
||||
interface ProgramHost<T extends BuilderProgram> {
|
||||
/**
|
||||
* Used to create the program when need for program creation or recreation detected
|
||||
*/
|
||||
createProgram: CreateProgram<T>;
|
||||
/** If provided, callback to invoke after every new program creation */
|
||||
afterProgramCreate?(program: T): void;
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getNewLine(): string;
|
||||
getCurrentDirectory(): string;
|
||||
|
@ -4405,6 +4406,10 @@ declare namespace ts {
|
|||
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
|
||||
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
|
||||
}
|
||||
interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
|
||||
/** If provided, callback to invoke after every new program creation */
|
||||
afterProgramCreate?(program: T): void;
|
||||
}
|
||||
/**
|
||||
* Host to create watch with root files and options
|
||||
*/
|
||||
|
|
15
tests/baselines/reference/api/typescript.d.ts
vendored
15
tests/baselines/reference/api/typescript.d.ts
vendored
|
@ -1778,7 +1778,7 @@ declare namespace ts {
|
|||
type ResolvedConfigFileName = string & {
|
||||
_isResolvedConfigFileName: never;
|
||||
};
|
||||
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles?: ReadonlyArray<SourceFile>) => void;
|
||||
type WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ReadonlyArray<SourceFile>) => void;
|
||||
class OperationCanceledException {
|
||||
}
|
||||
interface CancellationToken {
|
||||
|
@ -2689,7 +2689,6 @@ declare namespace ts {
|
|||
getDefaultLibLocation?(): string;
|
||||
writeFile: WriteFileCallback;
|
||||
getCurrentDirectory(): string;
|
||||
getDirectories(path: string): string[];
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getNewLine(): string;
|
||||
|
@ -4279,6 +4278,10 @@ declare namespace ts {
|
|||
* Get the syntax diagnostics, for all source files if source file is not supplied
|
||||
*/
|
||||
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
|
||||
/**
|
||||
* Get the declaration diagnostics, for all source files if source file is not supplied
|
||||
*/
|
||||
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
||||
/**
|
||||
* Get all the dependencies of the file
|
||||
*/
|
||||
|
@ -4365,13 +4368,11 @@ declare namespace ts {
|
|||
/** If provided, will be used to reset existing delayed compilation */
|
||||
clearTimeout?(timeoutId: any): void;
|
||||
}
|
||||
interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
|
||||
interface ProgramHost<T extends BuilderProgram> {
|
||||
/**
|
||||
* Used to create the program when need for program creation or recreation detected
|
||||
*/
|
||||
createProgram: CreateProgram<T>;
|
||||
/** If provided, callback to invoke after every new program creation */
|
||||
afterProgramCreate?(program: T): void;
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getNewLine(): string;
|
||||
getCurrentDirectory(): string;
|
||||
|
@ -4405,6 +4406,10 @@ declare namespace ts {
|
|||
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
|
||||
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
|
||||
}
|
||||
interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
|
||||
/** If provided, callback to invoke after every new program creation */
|
||||
afterProgramCreate?(program: T): void;
|
||||
}
|
||||
/**
|
||||
* Host to create watch with root files and options
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue