From a06f0c3d9f97a99fb84cd7ee15433c2fe37655b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Oct 2017 17:15:02 -0700 Subject: [PATCH 001/101] Use builder state to emit instead --- src/compiler/builder.ts | 513 +++++++++--------- src/compiler/tsc.ts | 4 +- src/compiler/watch.ts | 40 +- src/harness/unittests/builder.ts | 9 +- src/server/project.ts | 27 +- .../reference/api/tsserverlibrary.d.ts | 67 ++- tests/baselines/reference/api/typescript.d.ts | 64 +++ 7 files changed, 408 insertions(+), 316 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 34ca1bdf0e9..9a60557ae36 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -11,10 +11,8 @@ namespace ts { writeByteOrderMark: boolean; text: string; } -} -/* @internal */ -namespace ts { + /* @internal */ export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { const outputFiles: OutputFile[] = []; @@ -26,213 +24,270 @@ namespace ts { } } - export interface Builder { - /** Called to inform builder about new program */ - updateProgram(newProgram: Program): void; - - /** Gets the files affected by the file path */ - getFilesAffectedBy(program: Program, path: Path): ReadonlyArray; - - /** Emit the changed files and clear the cache of the changed files */ - emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray; - - /** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */ - getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray; - - /** Called to reset the status of the builder */ - clear(): void; + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { + if (map1 === undefined) { + return map2 === undefined; + } + if (map2 === undefined) { + return map1 === undefined; + } + // Has same size and every key is present in both maps + return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); } - interface EmitHandler { + /** + * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) + * Note that it is only safe to pass BuilderState as old state when creating new state, when + * - If iterator's next method to get next affected file is never called + * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + */ + export interface BuilderState { /** - * Called when sourceFile is added to the program + * The map of file infos, where there is entry for each file in the program + * The entry is signature of the file (from last emit) or empty string */ - onAddSourceFile(program: Program, sourceFile: SourceFile): void; + fileInfos: ReadonlyMap>; + /** - * Called when sourceFile is removed from the program + * Returns true if module gerneration is not ModuleKind.None */ - onRemoveSourceFile(path: Path): void; + isModuleEmit: boolean; + /** - * For all source files, either "onUpdateSourceFile" or "onUpdateSourceFileWithSameVersion" will be called. - * If the builder is sure that the source file needs an update, "onUpdateSourceFile" will be called; - * otherwise "onUpdateSourceFileWithSameVersion" will be called. + * Map of file referenced or undefined if it wasnt module emit + * The entry is present only if file references other files + * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) */ - onUpdateSourceFile(program: Program, sourceFile: SourceFile): void; + referencedMap: ReadonlyMap | undefined; + /** - * For all source files, either "onUpdateSourceFile" or "onUpdateSourceFileWithSameVersion" will be called. - * If the builder is sure that the source file needs an update, "onUpdateSourceFile" will be called; - * otherwise "onUpdateSourceFileWithSameVersion" will be called. - * This function should return whether the source file should be marked as changed (meaning that something associated with file has changed, e.g. module resolution) + * Set of source file's paths that have been changed, either in resolution or versions */ - onUpdateSourceFileWithSameVersion(program: Program, sourceFile: SourceFile): boolean; + changedFilesSet: ReadonlyMap; + /** - * Gets the files affected by the script info which has updated shape from the known one + * Set of cached semantic diagnostics per file */ - getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray; + semanticDiagnosticsPerFile: ReadonlyMap>; + + /** + * Returns true if this state is safe to use as oldState + */ + canCreateNewStateFrom(): boolean; + + /** + * Gets the files affected by the file path + * This api is only for internal use + */ + /* @internal */ + getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; + + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + + /** + * 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 + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; } - interface FileInfo { + /** + * Information about the source file: Its version and optional signature from last emit + */ + export interface FileInfo { version: string; signature: string; } + export interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + export type ReferencedSet = ReadonlyMap; + export interface BuilderOptions { getCanonicalFileName: GetCanonicalFileName; computeHash: (data: string) => string; } - export function createBuilder(options: BuilderOptions): Builder { - let isModuleEmit: boolean | undefined; + export function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState { const fileInfos = createMap(); + const isModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; + const referencedMap = isModuleEmit ? createMap() : undefined; + const semanticDiagnosticsPerFile = createMap>(); /** The map has key by source file's path that has been changed */ const changedFilesSet = createMap(); const hasShapeChanged = createMap(); let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - let emitHandler: EmitHandler; + + // Iterator datas + let affectedFiles: ReadonlyArray | undefined; + let affectedFilesIndex = 0; + const seenAffectedFiles = createMap(); + const getEmitDependentFilesAffectedBy = isModuleEmit ? + getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit; + + const useOldState = oldState && oldState.isModuleEmit === isModuleEmit; + if (useOldState) { + Debug.assert(oldState.canCreateNewStateFrom(), "Cannot use this state as old state"); + Debug.assert(!forEachEntry(oldState.changedFilesSet, (_value, path) => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + + copyEntries(oldState.changedFilesSet, changedFilesSet); + copyEntries(oldState.semanticDiagnosticsPerFile, semanticDiagnosticsPerFile); + } + + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + let oldInfo: Readonly; + let oldReferences: ReferencedSet; + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + + // Register changed file + // if not using old state so every file is changed + if (!useOldState || + // File wasnt present earlier + !(oldInfo = oldState.fileInfos.get(sourceFile.path)) || + // versions dont match + oldInfo.version !== version || + // Referenced files changed + !hasSameKeys(newReferences, (oldReferences = oldState.referencedMap && oldState.referencedMap.get(sourceFile.path))) || + // Referenced file was deleted + newReferences && forEachEntry(newReferences, (_value, path) => oldState.fileInfos.has(path) && !newProgram.getSourceFileByPath(path as Path))) { + changedFilesSet.set(sourceFile.path, true); + // All changed files need to re-evaluate its semantic diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + + newReferences && referencedMap.set(sourceFile.path, newReferences); + fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + } + + // For removed files, remove the semantic diagnostics removed files as changed + useOldState && oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + + // Set the old state and program to undefined to ensure we arent keeping them alive hence forward + oldState = undefined; + newProgram = undefined; + return { - updateProgram, + fileInfos, + isModuleEmit, + referencedMap, + changedFilesSet, + semanticDiagnosticsPerFile, + canCreateNewStateFrom, getFilesAffectedBy, - emitChangedFiles, - getSemanticDiagnostics, - clear + emitNextAffectedFile, + getSemanticDiagnostics }; - function createProgramGraph(program: Program) { - const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None; - if (isModuleEmit !== currentIsModuleEmit) { - isModuleEmit = currentIsModuleEmit; - emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); - fileInfos.clear(); - semanticDiagnosticsPerFile.clear(); - } - hasShapeChanged.clear(); - allFilesExcludingDefaultLibraryFile = undefined; - mutateMap( - fileInfos, - arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), - { - // Add new file info - createNewValue: (_path, sourceFile) => addNewFileInfo(program, sourceFile), - // Remove existing file info - onDeleteValue: removeExistingFileInfo, - // We will update in place instead of deleting existing value and adding new one - onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile) - } - ); + /** + * Can use this state as old State if we have iterated through all affected files present + */ + function canCreateNewStateFrom() { + return !affectedFiles || affectedFiles.length <= affectedFilesIndex; } - function registerChangedFile(path: Path) { - changedFilesSet.set(path, true); - // All changed files need to re-evaluate its semantic diagnostics - semanticDiagnosticsPerFile.delete(path); - } - - function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { - registerChangedFile(sourceFile.path); - emitHandler.onAddSourceFile(program, sourceFile); - return { version: sourceFile.version, signature: undefined }; - } - - function removeExistingFileInfo(_existingFileInfo: FileInfo, path: Path) { - // Since we dont need to track removed file as changed file - // We can just remove its diagnostics - changedFilesSet.delete(path); - semanticDiagnosticsPerFile.delete(path); - emitHandler.onRemoveSourceFile(path); - } - - function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) { - if (existingInfo.version !== sourceFile.version) { - registerChangedFile(sourceFile.path); - existingInfo.version = sourceFile.version; - emitHandler.onUpdateSourceFile(program, sourceFile); - } - else if (emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) { - registerChangedFile(sourceFile.path); - } - } - - function ensureProgramGraph(program: Program) { - if (!emitHandler) { - createProgramGraph(program); - } - } - - function updateProgram(newProgram: Program) { - if (emitHandler) { - createProgramGraph(newProgram); - } - } - - function getFilesAffectedBy(program: Program, path: Path): ReadonlyArray { - ensureProgramGraph(program); - - const sourceFile = program.getSourceFileByPath(path); + /** + * Gets the files affected by the path from the program + */ + function getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray { + const sourceFile = programOfThisState.getSourceFileByPath(path); if (!sourceFile) { return emptyArray; } - if (!updateShapeSignature(program, sourceFile)) { + if (!updateShapeSignature(programOfThisState, sourceFile)) { return [sourceFile]; } - return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile); + + return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile); } - function emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray { - ensureProgramGraph(program); - const compilerOptions = program.getCompilerOptions(); + /** + * Emits the next affected file, and returns the EmitResult along with source files emitted + * Returns undefined when iteration is complete + */ + function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined { + if (affectedFiles) { + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + affectedFilesIndex++; + if (!seenAffectedFiles.has(affectedFile.path)) { + seenAffectedFiles.set(affectedFile.path, true); - if (!changedFilesSet.size) { - return emptyArray; + // Emit the affected file + const result = programOfThisState.emit(affectedFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; + result.affectedFile = affectedFile; + return result; + } + } + + affectedFiles = undefined; } + // Get next changed file + const nextKey = changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + const compilerOptions = programOfThisState.getCompilerOptions(); // With --out or --outFile all outputs go into single file, do it only once if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); changedFilesSet.clear(); - return [program.emit(/*targetSourceFile*/ undefined, writeFileCallback)]; + return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers); } - const seenFiles = createMap(); - let result: EmitResult[] | undefined; - changedFilesSet.forEach((_true, path) => { - // Get the affected Files by this program - const affectedFiles = getFilesAffectedBy(program, path as Path); - affectedFiles.forEach(affectedFile => { - // Affected files shouldnt have cached diagnostics - semanticDiagnosticsPerFile.delete(affectedFile.path); + // Get next batch of affected files + changedFilesSet.delete(nextKey.value); + affectedFilesIndex = 0; + affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path); - if (!seenFiles.has(affectedFile.path)) { - seenFiles.set(affectedFile.path, true); + // Clear the semantic diagnostic of affected files + affectedFiles.forEach(affectedFile => semanticDiagnosticsPerFile.delete(affectedFile.path)); - // Emit the affected file - (result || (result = [])).push(program.emit(affectedFile, writeFileCallback)); - } - }); - }); - changedFilesSet.clear(); - return result || emptyArray; + return emitNextAffectedFile(programOfThisState, writeFileCallback, cancellationToken, customTransformers); } - function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray { - ensureProgramGraph(program); - Debug.assert(changedFilesSet.size === 0); - - const compilerOptions = program.getCompilerOptions(); + /** + * 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 + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + const compilerOptions = programOfThisState.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); // We dont need to cache the diagnostics just return them from program - return program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken); + return programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken); + } + + if (sourceFile) { + return getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken); } let diagnostics: Diagnostic[]; - for (const sourceFile of program.getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(program, sourceFile, cancellationToken)); + for (const sourceFile of programOfThisState.getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken)); } return diagnostics || emptyArray; } + /** + * Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set + */ function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { const path = sourceFile.path; const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); @@ -247,15 +302,6 @@ namespace ts { return diagnostics; } - function clear() { - isModuleEmit = undefined; - emitHandler = undefined; - fileInfos.clear(); - semanticDiagnosticsPerFile.clear(); - changedFilesSet.clear(); - hasShapeChanged.clear(); - } - /** * For script files that contains only ambient external modules, although they are not actually external module files, * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, @@ -271,9 +317,10 @@ namespace ts { return true; } - /** - * @return {boolean} indicates if the shape signature has changed since last update. - */ + /** + * Returns if the shape of the signature has changed since last emit + * Note that it also updates the current signature as the latest signature for the file + */ function updateShapeSignature(program: Program, sourceFile: SourceFile) { Debug.assert(!!sourceFile); @@ -360,6 +407,15 @@ namespace ts { } } + /** + * Gets the files referenced by the the file path + */ + function getReferencedByPaths(referencedFilePath: Path) { + return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); + } + /** * Gets all files of the program excluding the default library file */ @@ -386,126 +442,53 @@ namespace ts { } } - function getNonModuleEmitHandler(): EmitHandler { - return { - onAddSourceFile: noop, - onRemoveSourceFile: noop, - onUpdateSourceFile: noop, - onUpdateSourceFileWithSameVersion: returnFalse, - getFilesAffectedByUpdatedShape - }; - - function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { - const options = program.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (options && (options.out || options.outFile)) { - return [sourceFile]; - } - return getAllFilesExcludingDefaultLibraryFile(program, sourceFile); + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; } + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } - function getModuleEmitHandler(): EmitHandler { - const references = createMap>(); - return { - onAddSourceFile: setReferences, - onRemoveSourceFile, - onUpdateSourceFile: updateReferences, - onUpdateSourceFileWithSameVersion: updateReferencesTrackingChangedReferences, - getFilesAffectedByUpdatedShape - }; - - function setReferences(program: Program, sourceFile: SourceFile) { - const newReferences = getReferencedFiles(program, sourceFile); - if (newReferences) { - references.set(sourceFile.path, newReferences); - } + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } - function updateReferences(program: Program, sourceFile: SourceFile) { - const newReferences = getReferencedFiles(program, sourceFile); - if (newReferences) { - references.set(sourceFile.path, newReferences); - } - else { - references.delete(sourceFile.path); - } + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; } - function updateReferencesTrackingChangedReferences(program: Program, sourceFile: SourceFile) { - const newReferences = getReferencedFiles(program, sourceFile); - if (!newReferences) { - // Changed if we had references - return references.delete(sourceFile.path); - } + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); - const oldReferences = references.get(sourceFile.path); - references.set(sourceFile.path, newReferences); - if (!oldReferences || oldReferences.size !== newReferences.size) { - return true; - } - - // If there are any new references that werent present previously there is change - return forEachEntry(newReferences, (_true, referencedPath) => !oldReferences.delete(referencedPath)) || - // Otherwise its changed if there are more references previously than now - !!oldReferences.size; - } - - function onRemoveSourceFile(removedFilePath: Path) { - // Remove existing references - references.forEach((referencesInFile, filePath) => { - if (referencesInFile.has(removedFilePath)) { - // add files referencing the removedFilePath, as changed files too - const referencedByInfo = fileInfos.get(filePath); - if (referencedByInfo) { - registerChangedFile(filePath as Path); - } - } - }); - // Delete the entry for the removed file path - references.delete(removedFilePath); - } - - function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(references.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); - } - - function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { - if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) { - return getAllFilesExcludingDefaultLibraryFile(program, sourceFile); - } - - const compilerOptions = program.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFile]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - const path = sourceFile.path; - seenFileNamesMap.set(path, sourceFile); - const queue = getReferencedByPaths(path); - while (queue.length > 0) { - const currentPath = queue.pop(); - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = program.getSourceFileByPath(currentPath); - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { - queue.push(...getReferencedByPaths(currentPath)); - } + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile)) { + queue.push(...getReferencedByPaths(currentPath)); } } - - // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); } + + // Return array of values that needs emit + return flatMapIter(seenFileNamesMap.values(), value => value); } } } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 01fb45e4f7d..ab1e5cc566e 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -155,8 +155,8 @@ namespace ts { const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); watchingHost.beforeCompile = enableStatistics; const afterCompile = watchingHost.afterCompile; - watchingHost.afterCompile = (host, program, builder) => { - afterCompile(host, program, builder); + watchingHost.afterCompile = (host, program) => { + afterCompile(host, program); reportStatistics(program); }; return watchingHost; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 8e49bb71a15..96605f7b6b9 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -2,7 +2,6 @@ /// /// -/* @internal */ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; @@ -19,7 +18,7 @@ namespace ts { // Callbacks to do custom action before creating program and after creating program beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void; + afterCompile(host: DirectoryStructureHost, program: Program): void; } const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { @@ -133,6 +132,11 @@ namespace ts { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply); reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system); parseConfigFile = parseConfigFile || ts.parseConfigFile; + let builderState: Readonly | undefined; + const options: BuilderOptions = { + getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), + computeHash: data => system.createHash ? system.createHash(data) : data + }; return { system, parseConfigFile, @@ -142,7 +146,9 @@ namespace ts { afterCompile: compileWatchedProgram, }; - function compileWatchedProgram(host: DirectoryStructureHost, program: Program, builder: Builder) { + function compileWatchedProgram(host: DirectoryStructureHost, program: Program) { + builderState = createBuilderState(program, options, builderState); + // First get and report any syntactic errors. const diagnostics = program.getSyntacticDiagnostics().slice(); let reportSemanticDiagnostics = false; @@ -163,22 +169,15 @@ namespace ts { let sourceMaps: SourceMapData[]; let emitSkipped: boolean; - const result = builder.emitChangedFiles(program, writeFile); - if (result.length === 0) { - emitSkipped = true; - } - else { - for (const emitOutput of result) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - addRange(diagnostics, emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - } + let affectedEmitResult: AffectedFileEmitResult; + while (affectedEmitResult = builderState.emitNextAffectedFile(program, writeFile)) { + emitSkipped = emitSkipped || affectedEmitResult.emitSkipped; + addRange(diagnostics, affectedEmitResult.diagnostics); + sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps); } if (reportSemanticDiagnostics) { - addRange(diagnostics, builder.getSemanticDiagnostics(program)); + addRange(diagnostics, builderState.getSemanticDiagnostics(program)); } return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic); @@ -299,8 +298,6 @@ namespace ts { getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : getCurrentDirectory() ); - // There is no extra check needed since we can just rely on the program to decide emit - const builder = createBuilder({ getCanonicalFileName, computeHash }); synchronizeProgram(); @@ -334,7 +331,6 @@ namespace ts { compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); resolutionCache.finishCachingPerDirectoryResolution(); - builder.updateProgram(program); // Update watches updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); @@ -356,7 +352,7 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(directoryStructureHost, program, builder); + afterCompile(directoryStructureHost, program); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } @@ -640,9 +636,5 @@ namespace ts { flags ); } - - function computeHash(data: string) { - return system.createHash ? system.createHash(data) : data; - } } } diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index bfdeb1a4067..60297d2b7fc 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -44,15 +44,16 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - const builder = createBuilder({ + let builderState: BuilderState; + const builderOptions: BuilderOptions = { getCanonicalFileName: identity, computeHash: identity - }); + }; return fileNames => { const program = getProgram(); - builder.updateProgram(program); + builderState = createBuilderState(program, builderOptions, builderState); const outputFileNames: string[] = []; - builder.emitChangedFiles(program, fileName => outputFileNames.push(fileName)); + while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { } assert.deepEqual(outputFileNames, fileNames); }; } diff --git a/src/server/project.ts b/src/server/project.ts index ce750c78e23..056fedf19af 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builder: Builder; + private builderState: BuilderState; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -442,15 +442,6 @@ namespace ts.server { return this.languageService; } - private ensureBuilder() { - if (!this.builder) { - this.builder = createBuilder({ - getCanonicalFileName: this.projectService.toCanonicalFileName, - computeHash: data => this.projectService.host.createHash(data) - }); - } - } - private shouldEmitFile(scriptInfo: ScriptInfo) { return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent(); } @@ -460,8 +451,11 @@ namespace ts.server { return []; } this.updateGraph(); - this.ensureBuilder(); - return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), + this.builderState = createBuilderState(this.program, { + getCanonicalFileName: this.projectService.toCanonicalFileName, + computeHash: data => this.projectService.host.createHash(data) + }, this.builderState); + return mapDefined(this.builderState.getFilesAffectedBy(this.program, scriptInfo.path), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } @@ -497,6 +491,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; + this.builderState = undefined; this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -537,7 +532,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.externalFiles = undefined; this.program = undefined; - this.builder = undefined; + this.builderState = undefined; this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; @@ -787,15 +782,9 @@ namespace ts.server { if (this.setTypings(cachedTypings)) { hasChanges = this.updateGraphWorker() || hasChanges; } - if (this.builder) { - this.builder.updateProgram(this.program); - } } else { this.lastCachedUnresolvedImportsList = undefined; - if (this.builder) { - this.builder.clear(); - } } if (hasChanges) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 231d7bae3f2..3ec16254780 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,6 +3771,70 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + /** + * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) + * Note that it is only safe to pass BuilderState as old state when creating new state, when + * - If iterator's next method to get next affected file is never called + * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + */ + interface BuilderState { + /** + * The map of file infos, where there is entry for each file in the program + * The entry is signature of the file (from last emit) or empty string + */ + fileInfos: ReadonlyMap>; + /** + * Returns true if module gerneration is not ModuleKind.None + */ + isModuleEmit: boolean; + /** + * Map of file referenced or undefined if it wasnt module emit + * The entry is present only if file references other files + * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + */ + referencedMap: ReadonlyMap | undefined; + /** + * Set of source file's paths that have been changed, either in resolution or versions + */ + changedFilesSet: ReadonlyMap; + /** + * Set of cached semantic diagnostics per file + */ + semanticDiagnosticsPerFile: ReadonlyMap>; + /** + * Returns true if this state is safe to use as oldState + */ + canCreateNewStateFrom(): boolean; + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + /** + * 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 + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature: string; + } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } + function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -7210,7 +7274,7 @@ declare namespace ts.server { languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; - private builder; + private builderState; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -7271,7 +7335,6 @@ declare namespace ts.server { getGlobalProjectErrors(): ReadonlyArray; getAllProjectErrors(): ReadonlyArray; getLanguageService(ensureSynchronized?: boolean): LanguageService; - private ensureBuilder(); private shouldEmitFile(scriptInfo); getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6b2db71b140..5dec286a8b7 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,6 +3718,70 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + /** + * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) + * Note that it is only safe to pass BuilderState as old state when creating new state, when + * - If iterator's next method to get next affected file is never called + * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + */ + interface BuilderState { + /** + * The map of file infos, where there is entry for each file in the program + * The entry is signature of the file (from last emit) or empty string + */ + fileInfos: ReadonlyMap>; + /** + * Returns true if module gerneration is not ModuleKind.None + */ + isModuleEmit: boolean; + /** + * Map of file referenced or undefined if it wasnt module emit + * The entry is present only if file references other files + * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + */ + referencedMap: ReadonlyMap | undefined; + /** + * Set of source file's paths that have been changed, either in resolution or versions + */ + changedFilesSet: ReadonlyMap; + /** + * Set of cached semantic diagnostics per file + */ + semanticDiagnosticsPerFile: ReadonlyMap>; + /** + * Returns true if this state is safe to use as oldState + */ + canCreateNewStateFrom(): boolean; + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + /** + * 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 + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature: string; + } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } + function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; From 576fe1e995973b96c98d7152af5d27e34dec2d0e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 26 Oct 2017 10:00:23 -0700 Subject: [PATCH 002/101] Expose the watch and builder API in the typescript.d.ts --- src/compiler/tsc.ts | 62 +++- src/compiler/watch.ts | 326 +++++++++++------- .../unittests/reuseProgramStructure.ts | 32 +- src/harness/unittests/tscWatchMode.ts | 123 +++---- src/harness/virtualFileSystemWithWatch.ts | 2 +- src/services/tsconfig.json | 6 +- tests/baselines/reference/api/typescript.d.ts | 66 ++++ 7 files changed, 382 insertions(+), 235 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index ab1e5cc566e..5342319e75d 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -21,10 +21,10 @@ namespace ts { return diagnostic.messageText; } - let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply); + let reportDiagnostic = createDiagnosticReporter(); function udpateReportDiagnostic(options: CompilerOptions) { if (options.pretty) { - reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext); + reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true); } } @@ -55,7 +55,7 @@ namespace ts { // If there are any errors due to command line parsing and/or // setting up localization, report them and quit. if (commandLine.errors.length > 0) { - reportDiagnostics(commandLine.errors, reportDiagnostic); + commandLine.errors.forEach(reportDiagnostic); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -110,12 +110,11 @@ namespace ts { const commandLineOptions = commandLine.options; if (configFileName) { - const reportWatchDiagnostic = createWatchDiagnosticReporter(); - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic); + const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic); udpateReportDiagnostic(configParseResult.options); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic)); + createWatchOfConfigFile(configParseResult, commandLineOptions); } else { performCompilation(configParseResult.fileNames, configParseResult.options); @@ -125,7 +124,7 @@ namespace ts { udpateReportDiagnostic(commandLineOptions); if (isWatchSet(commandLineOptions)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost()); + createWatchOfFilesAndCompilerOptions(commandLine.fileNames, commandLineOptions); } else { performCompilation(commandLine.fileNames, commandLineOptions); @@ -151,15 +150,37 @@ namespace ts { return sys.exit(exitStatus); } - function createWatchingSystemHost(reportWatchDiagnostic?: DiagnosticReporter) { - const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); - watchingHost.beforeCompile = enableStatistics; - const afterCompile = watchingHost.afterCompile; - watchingHost.afterCompile = (host, program) => { - afterCompile(host, program); + function createProgramCompilerWithBuilderState() { + const compilerWithBuilderState = ts.createProgramCompilerWithBuilderState(sys, reportDiagnostic); + return (host: DirectoryStructureHost, program: Program) => { + compilerWithBuilderState(host, program); reportStatistics(program); }; - return watchingHost; + } + + function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) { + createWatch({ + system: sys, + beforeProgramCreate: enableStatistics, + afterProgramCreate: createProgramCompilerWithBuilderState(), + onConfigFileDiagnostic: reportDiagnostic, + rootFiles: configParseResult.fileNames, + options: configParseResult.options, + configFileName: configParseResult.options.configFilePath, + optionsToExtend, + configFileSpecs: configParseResult.configFileSpecs, + configFileWildCardDirectories: configParseResult.wildcardDirectories + }); + } + + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { + createWatch({ + system: sys, + beforeProgramCreate: enableStatistics, + afterProgramCreate: createProgramCompilerWithBuilderState(), + rootFiles, + options + }); } function compileProgram(program: Program): ExitStatus { @@ -182,7 +203,18 @@ namespace ts { const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); addRange(diagnostics, emitDiagnostics); - return handleEmitOutputAndReportErrors(sys, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic); + sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); + writeFileAndEmittedFileList(sys, program, emittedFiles); + if (emitSkipped && diagnostics.length > 0) { + // If the emitter didn't emit anything, then pass that value along. + return ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + else if (diagnostics.length > 0) { + // The emitter emitted something, inform the caller if that happened in the presence + // of diagnostics or not. + return ExitStatus.DiagnosticsPresent_OutputsGenerated; + } + return ExitStatus.Success; } function enableStatistics(compilerOptions: CompilerOptions) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 96605f7b6b9..0ff0d2f6122 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -4,149 +4,97 @@ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; - export interface WatchingSystemHost { - // FS system to use - system: System; - // parse config file - parseConfigFile: ParseConfigFile; - - // Reporting errors - reportDiagnostic: DiagnosticReporter; - reportWatchDiagnostic: DiagnosticReporter; - - // Callbacks to do custom action before creating program and after creating program - beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: DirectoryStructureHost, program: Program): void; - } - - const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { + const sysFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { getCurrentDirectory: () => sys.getCurrentDirectory(), getNewLine: () => sys.newLine, getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) } : undefined; - export function createDiagnosticReporter(system = sys, worker = reportDiagnosticSimply, formatDiagnosticsHost?: FormatDiagnosticsHost): DiagnosticReporter { - return diagnostic => worker(diagnostic, getFormatDiagnosticsHost(), system); - - function getFormatDiagnosticsHost() { - return formatDiagnosticsHost || (formatDiagnosticsHost = system === sys ? defaultFormatDiagnosticsHost : { - getCurrentDirectory: () => system.getCurrentDirectory(), - getNewLine: () => system.newLine, - getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - }); + /** + * Create a function that reports error by writing to the system and handles the formating of the diagnostic + */ + /*@internal*/ + export function createDiagnosticReporter(system = sys, pretty?: boolean): DiagnosticReporter { + const host: FormatDiagnosticsHost = system === sys ? sysFormatDiagnosticsHost : { + getCurrentDirectory: () => system.getCurrentDirectory(), + getNewLine: () => system.newLine, + getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), + }; + if (!pretty) { + return diagnostic => system.write(ts.formatDiagnostic(diagnostic, host)); } - } - export function createWatchDiagnosticReporter(system = sys): DiagnosticReporter { + const diagnostics: Diagnostic[] = new Array(1); return diagnostic => { - let output = new Date().toLocaleTimeString() + " - "; - output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine + system.newLine}`; - system.write(output); + diagnostics[0] = diagnostic; + system.write(formatDiagnosticsWithColorAndContext(diagnostics, host) + host.getNewLine()); + diagnostics[0] = undefined; }; } - export function reportDiagnostics(diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter): void { - for (const diagnostic of diagnostics) { - reportDiagnostic(diagnostic); - } - } - - export function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void { - system.write(ts.formatDiagnostic(diagnostic, host)); - } - - export function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void { - system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); - } - - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { + /** + * Reads the config file, reports errors if any and exits if the config file cannot be found + */ + /*@internal*/ + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter): ParsedCommandLine { let configFileText: string; try { configFileText = system.readFile(configFileName); } catch (e) { const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error); + reportDiagnostic(error); system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } if (!configFileText) { const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], reportDiagnostic); + reportDiagnostic(error); system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } const result = parseJsonText(configFileName, configFileText); - reportDiagnostics(result.parseDiagnostics, reportDiagnostic); + result.parseDiagnostics.forEach(reportDiagnostic); const cwd = system.getCurrentDirectory(); const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, reportDiagnostic); + configParseResult.errors.forEach(reportDiagnostic); return configParseResult; } - function reportEmittedFiles(files: string[], system: DirectoryStructureHost): void { - if (!files || files.length === 0) { - return; - } + /** + * Writes emitted files, source files depending on options + */ + /*@internal*/ + export function writeFileAndEmittedFileList(system: System, program: Program, emittedFiles: string[]) { const currentDir = system.getCurrentDirectory(); - for (const file of files) { + forEach(emittedFiles, file => { const filepath = getNormalizedAbsolutePath(file, currentDir); system.write(`TSFILE: ${filepath}${system.newLine}`); - } - } - - export function handleEmitOutputAndReportErrors(system: DirectoryStructureHost, program: Program, - emittedFiles: string[], emitSkipped: boolean, - diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter - ): ExitStatus { - reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), reportDiagnostic); - reportEmittedFiles(emittedFiles, system); + }); if (program.getCompilerOptions().listFiles) { forEach(program.getSourceFiles(), file => { system.write(file.fileName + system.newLine); }); } - - if (emitSkipped && diagnostics.length > 0) { - // If the emitter didn't emit anything, then pass that value along. - return ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - else if (diagnostics.length > 0) { - // The emitter emitted something, inform the caller if that happened in the presence - // of diagnostics or not. - return ExitStatus.DiagnosticsPresent_OutputsGenerated; - } - return ExitStatus.Success; } - export function createWatchingSystemHost(pretty?: DiagnosticStyle, system = sys, - parseConfigFile?: ParseConfigFile, reportDiagnostic?: DiagnosticReporter, - reportWatchDiagnostic?: DiagnosticReporter - ): WatchingSystemHost { - reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply); - reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system); - parseConfigFile = parseConfigFile || ts.parseConfigFile; + /** + * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + */ + export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) { + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); let builderState: Readonly | undefined; const options: BuilderOptions = { getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), computeHash: data => system.createHash ? system.createHash(data) : data }; - return { - system, - parseConfigFile, - reportDiagnostic, - reportWatchDiagnostic, - beforeCompile: noop, - afterCompile: compileWatchedProgram, - }; - function compileWatchedProgram(host: DirectoryStructureHost, program: Program) { + return (host: DirectoryStructureHost, program: Program) => { builderState = createBuilderState(program, options, builderState); // First get and report any syntactic errors. @@ -179,8 +127,9 @@ namespace ts { if (reportSemanticDiagnostics) { addRange(diagnostics, builderState.getSemanticDiagnostics(program)); } - return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped, - diagnostics, reportDiagnostic); + + sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); + writeFileAndEmittedFileList(system, program, emittedFiles); function ensureDirectoriesExist(directoryPath: string) { if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { @@ -210,24 +159,120 @@ namespace ts { } } } + }; + } + + export interface WatchHost { + /** FS system to use */ + system: System; + + /** Custom action before creating the program */ + beforeProgramCreate(compilerOptions: CompilerOptions): void; + /** Custom action after new program creation is successful */ + afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + } + + /** + * Host to create watch with root files and options + */ + export interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + /** root files to use to generate program */ + rootFiles: string[]; + + /** Compiler options */ + options: CompilerOptions; + } + + /** + * Host to create watch with config file + */ + export interface WatchOfConfigFileHost extends WatchHost { + /** Name of the config file to compile */ + configFileName: string; + + /** Options to extend */ + optionsToExtend?: CompilerOptions; + + // Reports errors in the config file + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + + /*@internal*/ + /** + * Host to create watch with config file that is already parsed (from tsc) + */ + export interface WatchOfConfigFileHost extends WatchHost { + rootFiles?: string[]; + options?: CompilerOptions; + optionsToExtend?: CompilerOptions; + configFileSpecs?: ConfigFileSpecs; + configFileWildCardDirectories?: MapLike; + } + + export interface Watch { + /** Synchronize the program with the changes */ + synchronizeProgram(): void; + /** Get current program */ + /*@internal*/ + getProgram(): Program; + } + + /** + * Creates the watch what generates program using the config file + */ + export interface WatchOfConfigFile extends Watch { + } + + /** + * Creates the watch that generates program using the root files and compiler options + */ + export interface WatchOfFilesAndCompilerOptions extends Watch { + /** Updates the root files in the program, only if this is not config file compilation */ + updateRootFileNames(fileNames: string[]): void; + } + + /** + * Create the watched program for config file + */ + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + return createWatch({ + system, + beforeProgramCreate: noop, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), + onConfigFileDiagnostic: reportDiagnostic || createDiagnosticReporter(system), + configFileName, + optionsToExtend + }); + } + + /** + * Create the watched program for root files and compiler options + */ + export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { + return createWatch({ + system, + beforeProgramCreate: noop, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), + rootFiles, + options + }); + } + + /** + * Creates the watch from the host for root files and compiler options + */ + export function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + export function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; + export function createWatch(host: WatchOfFilesAndCompilerOptionsHost | WatchOfConfigFileHost): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + interface HostFileInfo { + version: number; + sourceFile: SourceFile; + fileWatcher: FileWatcher; } - } - export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions = {}, watchingHost?: WatchingSystemHost) { - return createWatchMode(configParseResult.fileNames, configParseResult.options, watchingHost, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); - } - - export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost) { - return createWatchMode(rootFileNames, compilerOptions, watchingHost); - } - - interface HostFileInfo { - version: number; - sourceFile: SourceFile; - fileWatcher: FileWatcher; - } - - function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { let program: Program; let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc let missingFilesMap: Map; // Map of file watchers for the missing files @@ -239,16 +284,21 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed + const { system, configFileName, onConfigFileDiagnostic, afterProgramCreate, beforeProgramCreate, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchOfConfigFileHost; + + // From tsc we want to get already parsed result and hence check for rootFileNames + const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; + if (configFileName && !rootFileNames) { + parseConfigFile(); + } + const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(system.newLine); } : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; - watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); - const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - - const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName) { watchFile(system, configFileName, scheduleProgramReload, writeLog); } @@ -304,7 +354,9 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); - return () => program; + return configFileName ? + { getProgram: () => program, synchronizeProgram } : + { getProgram: () => program, synchronizeProgram, updateRootFileNames }; function synchronizeProgram() { writeLog(`Synchronizing program`); @@ -321,7 +373,7 @@ namespace ts { return; } - beforeCompile(compilerOptions); + beforeProgramCreate(compilerOptions); // Compile the program const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program; @@ -352,10 +404,16 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(directoryStructureHost, program); + afterProgramCreate(directoryStructureHost, program); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } + function updateRootFileNames(files: string[]) { + Debug.assert(!configFileName, "Cannot update root file names with config file watch mode"); + rootFileNames = files; + scheduleProgramUpdate(); + } + function toPath(fileName: string) { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } @@ -468,6 +526,10 @@ namespace ts { } } + function reportWatchDiagnostic(diagnostic: Diagnostic) { + system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); + } + // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch // operations (such as saving all modified files in an editor) a chance to complete before we kick // off a new compilation. @@ -505,7 +567,7 @@ namespace ts { function reloadFileNamesFromConfigFile() { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { - reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } rootFileNames = result.fileNames; @@ -519,19 +581,22 @@ namespace ts { const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; cachedHost.clearCache(); - const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); - rootFileNames = configParseResult.fileNames; - compilerOptions = configParseResult.options; + parseConfigFile(); hasChangedCompilerOptions = true; - configFileSpecs = configParseResult.configFileSpecs; - configFileWildCardDirectories = configParseResult.wildcardDirectories; - synchronizeProgram(); // Update the wild card directory watch watchConfigFileWildCardDirectories(); } + function parseConfigFile() { + const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, directoryStructureHost as CachedDirectoryStructureHost, onConfigFileDiagnostic); + rootFileNames = configParseResult.fileNames; + compilerOptions = configParseResult.options; + configFileSpecs = configParseResult.configFileSpecs; + configFileWildCardDirectories = configParseResult.wildcardDirectories; + } + function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { updateCachedSystemWithFile(fileName, path, eventKind); const hostSourceFile = sourceFilesCache.get(path); @@ -590,11 +655,16 @@ namespace ts { } function watchConfigFileWildCardDirectories() { - updateWatchingWildcardDirectories( - watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), - createMapFromTemplate(configFileWildCardDirectories), - watchWildcardDirectory - ); + if (configFileWildCardDirectories) { + updateWatchingWildcardDirectories( + watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), + createMapFromTemplate(configFileWildCardDirectories), + watchWildcardDirectory + ); + } + else if (watchedWildcardDirectories) { + clearMap(watchedWildcardDirectories, closeFileWatcherOf); + } } function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index fdccc8a7795..cc330300d33 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -871,7 +871,6 @@ namespace ts { }); }); - import TestSystem = ts.TestFSWithWatch.TestServerHost; type FileOrFolder = ts.TestFSWithWatch.FileOrFolder; import createTestSystem = ts.TestFSWithWatch.createWatchedSystem; import libFile = ts.TestFSWithWatch.libFile; @@ -897,30 +896,21 @@ namespace ts { return JSON.parse(JSON.stringify(filesOrOptions)); } - function createWatchingSystemHost(host: TestSystem) { - return ts.createWatchingSystemHost(/*pretty*/ undefined, host); - } - - function verifyProgramWithoutConfigFile(watchingSystemHost: WatchingSystemHost, rootFiles: string[], options: CompilerOptions) { - const program = createWatchModeWithoutConfigFile(rootFiles, options, watchingSystemHost)(); + function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { + const program = createWatchOfFilesAndCompilerOptions(rootFiles, options, system).getProgram(); verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); } - function getConfigParseResult(watchingSystemHost: WatchingSystemHost, configFileName: string) { - return parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); - } - - function verifyProgramWithConfigFile(watchingSystemHost: WatchingSystemHost, configFile: string) { - const result = getConfigParseResult(watchingSystemHost, configFile); - const program = createWatchModeWithConfigFile(result, {}, watchingSystemHost)(); - const { fileNames, options } = getConfigParseResult(watchingSystemHost, configFile); + function verifyProgramWithConfigFile(system: System, configFileName: string) { + const program = createWatchOfConfigFile(configFileName, {}, system).getProgram(); + const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } function verifyProgram(files: FileOrFolder[], rootFiles: string[], options: CompilerOptions, configFile: string) { - const watchingSystemHost = createWatchingSystemHost(createTestSystem(files)); - verifyProgramWithoutConfigFile(watchingSystemHost, rootFiles, options); - verifyProgramWithConfigFile(watchingSystemHost, configFile); + const system = createTestSystem(files); + verifyProgramWithoutConfigFile(system, rootFiles, options); + verifyProgramWithConfigFile(system, configFile); } it("has empty options", () => { @@ -1031,11 +1021,9 @@ namespace ts { }; const configFile: FileOrFolder = { path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] }) + content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) }; - - const watchingSystemHost = createWatchingSystemHost(createTestSystem([app, module1, module2, module3, libFile, configFile])); - verifyProgramWithConfigFile(watchingSystemHost, configFile.path); + verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); }); }); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 4e2d63cec90..a3647aead4a 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -22,23 +22,14 @@ namespace ts.tscWatch { checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); } - function createWatchingSystemHost(system: WatchedSystem) { - return ts.createWatchingSystemHost(/*pretty*/ undefined, system); + function createWatchOfConfigFile(configFileName: string, host: WatchedSystem) { + const watch = ts.createWatchOfConfigFile(configFileName, {}, host); + return () => watch.getProgram(); } - function parseConfigFile(configFileName: string, watchingSystemHost: WatchingSystemHost) { - return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); - } - - function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) { - const watchingSystemHost = createWatchingSystemHost(host); - const configFileResult = parseConfigFile(configFilePath, watchingSystemHost); - return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); - } - - function createWatchModeWithoutConfigFile(fileNames: string[], host: WatchedSystem, options: CompilerOptions = {}) { - const watchingSystemHost = createWatchingSystemHost(host); - return ts.createWatchModeWithoutConfigFile(fileNames, options, watchingSystemHost); + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}) { + const watch = ts.createWatchOfFilesAndCompilerOptions(rootFiles, options, host); + return () => watch.getProgram(); } function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) { @@ -190,7 +181,7 @@ namespace ts.tscWatch { content: `export let x: number` }; const host = createWatchedSystem([appFile, moduleFile, libFile]); - const watch = createWatchModeWithoutConfigFile([appFile.path], host); + const watch = createWatchOfFilesAndCompilerOptions([appFile.path], host); checkProgramActualFiles(watch(), [appFile.path, libFile.path, moduleFile.path]); @@ -215,7 +206,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false }); const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - const watch = createWatchModeWithConfigFile(upperCaseConfigFilePath, host); + const watch = createWatchOfConfigFile(upperCaseConfigFilePath, host); checkProgramActualFiles(watch(), [combinePaths(getDirectoryPath(upperCaseConfigFilePath), getBaseFileName(f1.path))]); }); @@ -244,14 +235,10 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([configFile, libFile, file1, file2, file3]); - const watchingSystemHost = createWatchingSystemHost(host); - const configFileResult = parseConfigFile(configFile.path, watchingSystemHost); - assert.equal(configFileResult.errors.length, 0, `expect no errors in config file, got ${JSON.stringify(configFileResult.errors)}`); + const watch = ts.createWatchOfConfigFile(configFile.path, {}, host, notImplemented); - const watch = ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); - - checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]); - checkProgramRootFiles(watch(), [file1.path, file2.path]); + checkProgramActualFiles(watch.getProgram(), [file1.path, libFile.path, file2.path]); + checkProgramRootFiles(watch.getProgram(), [file1.path, file2.path]); checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]); const configDir = getDirectoryPath(configFile.path); checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true); @@ -267,7 +254,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([commonFile1, libFile, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); const configDir = getDirectoryPath(configFile.path); checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true); @@ -291,7 +278,7 @@ namespace ts.tscWatch { }` }; const host = createWatchedSystem([commonFile1, commonFile2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); const commonFile3 = "/a/b/commonFile3.ts"; checkProgramRootFiles(watch(), [commonFile1.path, commonFile3]); @@ -304,7 +291,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([commonFile1, commonFile2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); // delete commonFile2 @@ -326,7 +313,7 @@ namespace ts.tscWatch { let x = y` }; const host = createWatchedSystem([file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, libFile.path]); @@ -352,7 +339,7 @@ namespace ts.tscWatch { }; const files = [commonFile1, commonFile2, configFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); configFile.content = `{ @@ -379,7 +366,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([commonFile1, commonFile2, excludedFile1, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); }); @@ -407,7 +394,7 @@ namespace ts.tscWatch { }; const files = [file1, nodeModuleFile, classicModuleFile, configFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, nodeModuleFile.path]); @@ -435,7 +422,7 @@ namespace ts.tscWatch { }` }; const host = createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); }); @@ -453,7 +440,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, file2.path]); @@ -482,7 +469,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); host.reloadFS([file1, file3]); @@ -505,7 +492,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path, file3.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path, file3.path], host); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); host.reloadFS([file1, file3]); @@ -533,7 +520,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, file3, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [file2.path, file3.path]); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); @@ -555,10 +542,10 @@ namespace ts.tscWatch { content: "export let y = 1;" }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file2.path, file3.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file2.path, file3.path], host); checkProgramActualFiles(watch(), [file2.path, file3.path]); - const watch2 = createWatchModeWithoutConfigFile([file1.path], host); + const watch2 = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramActualFiles(watch2(), [file1.path, file2.path, file3.path]); // Previous program shouldnt be updated @@ -581,7 +568,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path]); host.reloadFS([file1, file2, configFile]); @@ -606,7 +593,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path]); @@ -636,7 +623,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path]); const modifiedConfigFile = { @@ -664,7 +651,7 @@ namespace ts.tscWatch { content: JSON.stringify({ compilerOptions: {} }) }; const host = createWatchedSystem([file1, file2, libFile, config]); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]); checkOutputErrors(host, emptyArray, /*isInitial*/ true); @@ -688,7 +675,7 @@ namespace ts.tscWatch { content: "{" }; const host = createWatchedSystem([file1, corruptedConfig]); - const watch = createWatchModeWithConfigFile(corruptedConfig.path, host); + const watch = createWatchOfConfigFile(corruptedConfig.path, host); checkProgramActualFiles(watch(), [file1.path]); }); @@ -738,7 +725,7 @@ namespace ts.tscWatch { }) }; const host = createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const watch = createWatchModeWithConfigFile(config1.path, host); + const watch = createWatchOfConfigFile(config1.path, host); checkProgramActualFiles(watch(), [libES5.path, app.path]); @@ -763,7 +750,7 @@ namespace ts.tscWatch { }) }; const host = createWatchedSystem([f, config]); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [f.path]); }); @@ -777,7 +764,7 @@ namespace ts.tscWatch { content: 'import * as T from "./moduleFile"; T.bar();' }; const host = createWatchedSystem([moduleFile, file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); const moduleFileOldPath = moduleFile.path; @@ -809,7 +796,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); const moduleFileOldPath = moduleFile.path; @@ -844,7 +831,7 @@ namespace ts.tscWatch { path: "/a/c" }; const host = createWatchedSystem([f1, config, node, cwd], { currentDirectory: cwd.path }); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [f1.path, node.path]); }); @@ -859,7 +846,7 @@ namespace ts.tscWatch { content: 'import * as T from "./moduleFile"; T.bar();' }; const host = createWatchedSystem([file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile") @@ -886,7 +873,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, [ getUnknownCompilerOption(watch(), configFile, "foo"), getUnknownCompilerOption(watch(), configFile, "allowJS") @@ -906,7 +893,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); }); @@ -923,7 +910,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); configFile.content = `{ @@ -959,7 +946,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [libFile.path]); }); @@ -985,7 +972,7 @@ namespace ts.tscWatch { content: `export const x: number` }; const host = createWatchedSystem([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [t1.path, t2.path]); }); @@ -996,7 +983,7 @@ namespace ts.tscWatch { content: "let x = 1" }; const host = createWatchedSystem([f, libFile]); - const watch = createWatchModeWithoutConfigFile([f.path], host, { allowNonTsExtensions: true }); + const watch = createWatchOfFilesAndCompilerOptions([f.path], host, { allowNonTsExtensions: true }); checkProgramActualFiles(watch(), [f.path, libFile.path]); }); @@ -1024,7 +1011,7 @@ namespace ts.tscWatch { const files = [file, libFile, configFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); const errors = () => [ getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"allowJs"'), '"allowJs"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration"), getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"declaration"'), '"declaration"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration") @@ -1080,7 +1067,7 @@ namespace ts.tscWatch { const files = [f1, f2, config, libFile]; host.reloadFS(files); - createWatchModeWithConfigFile(config.path, host); + createWatchOfConfigFile(config.path, host); const allEmittedLines = getEmittedLines(files); checkOutputContains(host, allEmittedLines); @@ -1142,7 +1129,7 @@ namespace ts.tscWatch { mapOfFilesWritten.set(p, count ? count + 1 : 1); return originalWriteFile(p, content); }; - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); if (useOutFile) { // Only out file assert.equal(mapOfFilesWritten.size, 1); @@ -1226,7 +1213,7 @@ namespace ts.tscWatch { host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); // Initial compile - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); if (firstCompilationEmitFiles) { checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); } @@ -1537,11 +1524,11 @@ namespace ts.tscWatch { // Initial compile if (configFile) { - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); } else { // First file as the root - createWatchModeWithoutConfigFile([files[0].path], host, { listEmittedFiles: true }); + createWatchOfFilesAndCompilerOptions([files[0].path], host, { listEmittedFiles: true }); } checkOutputContains(host, allEmittedFiles); @@ -1661,7 +1648,7 @@ namespace ts.tscWatch { const files = [root, imported, libFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path); const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); @@ -1762,7 +1749,7 @@ namespace ts.tscWatch { return originalFileExists.call(host, fileName); }; - const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); checkOutputErrors(host, [ @@ -1804,7 +1791,7 @@ namespace ts.tscWatch { return originalFileExists.call(host, fileName); }; - const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); checkOutputErrors(host, emptyArray, /*isInitial*/ true); @@ -1853,7 +1840,7 @@ declare module "fs" { const filesWithNodeType = files.concat(packageJson, nodeType); const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - const watch = createWatchModeWithoutConfigFile([root.path], host, { }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") @@ -1895,7 +1882,7 @@ declare module "fs" { const files = [root, file, libFile]; const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - const watch = createWatchModeWithoutConfigFile([root.path, file.path], host, {}); + const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") @@ -1937,7 +1924,7 @@ declare module "fs" { const outDirFolder = "/a/b/projects/myProject/dist/"; const programFiles = [file1, file2, module1, libFile]; const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" }); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), programFiles.map(f => f.path)); checkOutputErrors(host, emptyArray, /*isInitial*/ true); const expectedFiles: ExpectedFile[] = [ @@ -2014,7 +2001,7 @@ declare module "fs" { }; const files = [configFile, file1, file2, libFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); file1.content = "var zz30 = 100;"; diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index d4d203cabbb..f2a178610c9 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -460,7 +460,7 @@ interface Array {}` private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) { const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); - invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => cb(fileName, eventKind)); + invokeWatcherCallbacks(callbacks, ({ cb }) => cb(fileFullPath, eventKind)); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index d73014a93a2..ba944bafd9d 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -1,4 +1,4 @@ -{ +{ "extends": "../tsconfig-base", "compilerOptions": { "removeComments": false, @@ -37,6 +37,10 @@ "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", "../compiler/program.ts", + "../compiler/builder.ts", + "../compiler/resolutionCache.ts", + "../compiler/watch.ts", + "../compiler/watchUtilities.ts", "../compiler/commandLineParser.ts", "../compiler/diagnosticInformationMap.generated.ts", "types.ts", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5dec286a8b7..fb5ee37012d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3812,6 +3812,72 @@ declare namespace ts { */ function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; } +declare namespace ts { + type DiagnosticReporter = (diagnostic: Diagnostic) => void; + /** + * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + */ + function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; + interface WatchHost { + /** FS system to use */ + system: System; + /** Custom action before creating the program */ + beforeProgramCreate(compilerOptions: CompilerOptions): void; + /** Custom action after new program creation is successful */ + afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + } + /** + * Host to create watch with root files and options + */ + interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + /** root files to use to generate program */ + rootFiles: string[]; + /** Compiler options */ + options: CompilerOptions; + } + /** + * Host to create watch with config file + */ + interface WatchOfConfigFileHost extends WatchHost { + /** Name of the config file to compile */ + configFileName: string; + /** Options to extend */ + optionsToExtend?: CompilerOptions; + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + interface Watch { + /** Synchronize the program with the changes */ + synchronizeProgram(): void; + } + /** + * Creates the watch what generates program using the config file + */ + interface WatchOfConfigFile extends Watch { + } + /** + * Creates the watch that generates program using the root files and compiler options + */ + interface WatchOfFilesAndCompilerOptions extends Watch { + /** Updates the root files in the program, only if this is not config file compilation */ + updateRootFileNames(fileNames: string[]): void; + } + /** + * Create the watched program for config file + */ + function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile; + /** + * Create the watched program for root files and compiler options + */ + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for root files and compiler options + */ + function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; +} declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; /** From 7ebf9d9f9d76e4a655d0079a7663a574842fa373 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 Nov 2017 11:35:38 -0800 Subject: [PATCH 003/101] Lint errors fix --- src/compiler/builder.ts | 8 ++++++-- src/harness/unittests/builder.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 9a60557ae36..0881f68800c 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -166,12 +166,16 @@ namespace ts { semanticDiagnosticsPerFile.delete(sourceFile.path); } - newReferences && referencedMap.set(sourceFile.path, newReferences); + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); } // For removed files, remove the semantic diagnostics removed files as changed - useOldState && oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + if (useOldState) { + oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + } // Set the old state and program to undefined to ensure we arent keeping them alive hence forward oldState = undefined; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 60297d2b7fc..89b0852bd32 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -53,7 +53,9 @@ namespace ts { const program = getProgram(); builderState = createBuilderState(program, builderOptions, builderState); const outputFileNames: string[] = []; - while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { } + // tslint:disable-next-line no-empty + while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { + } assert.deepEqual(outputFileNames, fileNames); }; } From 3c5a6e1ae7848d97e7178a4d3e8043cf33d15fca Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 Nov 2017 13:08:20 -0800 Subject: [PATCH 004/101] Allow watch host to specify module name resolver --- src/compiler/watch.ts | 10 ++++++++-- tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0ff0d2f6122..ff352c0febc 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -170,6 +170,9 @@ namespace ts { beforeProgramCreate(compilerOptions: CompilerOptions): void; /** Custom action after new program creation is successful */ afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + + /** Optional module name resolver */ + moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -308,6 +311,9 @@ namespace ts { const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); let newLine = getNewLineCharacter(compilerOptions, system); + const resolveModuleNames: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModule[] = host.moduleNameResolver ? + (moduleNames, containingFile, reusedNames) => host.moduleNameResolver(moduleNames, containingFile, reusedNames) : + (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost @@ -328,7 +334,7 @@ namespace ts { getDirectories: path => directoryStructureHost.getDirectories(path), realpath, resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), - resolveModuleNames: (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false), + resolveModuleNames, onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, @@ -341,7 +347,7 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = true; scheduleProgramUpdate(); }, - writeLog + writeLog, }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fb5ee37012d..63e2d453b18 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3825,6 +3825,8 @@ declare namespace ts { beforeProgramCreate(compilerOptions: CompilerOptions): void; /** Custom action after new program creation is successful */ afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + /** Optional module name resolver */ + moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options From c9a17f325b96f812604b529ce2ba36d21e3441b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 9 Nov 2017 13:35:56 -0800 Subject: [PATCH 005/101] Add api to get the dependencies of the file --- src/compiler/builder.ts | 54 ++++++++++++++++++- .../reference/api/tsserverlibrary.d.ts | 4 ++ tests/baselines/reference/api/typescript.d.ts | 4 ++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 0881f68800c..efa0f0a0372 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -93,6 +93,11 @@ namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** @@ -190,7 +195,8 @@ namespace ts { canCreateNewStateFrom, getFilesAffectedBy, emitNextAffectedFile, - getSemanticDiagnostics + getSemanticDiagnostics, + getAllDependencies }; /** @@ -306,6 +312,52 @@ namespace ts { return diagnostics; } + /** + * Get all the dependencies of the sourceFile + */ + function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[] { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return programOfThisState.getSourceFiles().map(getFileName); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + return programOfThisState.getSourceFiles().map(getFileName); + } + + // Get the references, traversing deep from the referenceMap + Debug.assert(!!referencedMap); + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop(); + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { + queue.push(value as Path); + } + } + } + } + + return flatMapIter(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + if (file) { + return file.fileName; + } + return path; + }); + } + + function getFileName(sourceFile: SourceFile) { + return sourceFile.fileName; + } + /** * For script files that contains only ambient external modules, although they are not actually external module files, * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3ec16254780..5c306474533 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3815,6 +3815,10 @@ declare namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** * Information about the source file: Its version and optional signature from last emit diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 63e2d453b18..2212a859c0a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3762,6 +3762,10 @@ declare namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** * Information about the source file: Its version and optional signature from last emit From 6d36a3d778b188a29cef1271cb24e270d93d644b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 14 Nov 2017 11:35:20 -0800 Subject: [PATCH 006/101] Make the versions in the source file non zero when the source file is created --- src/compiler/watch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ff352c0febc..07fb9203b6a 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -468,9 +468,9 @@ namespace ts { else { let fileWatcher: FileWatcher; if (sourceFile) { - sourceFile.version = "0"; + sourceFile.version = "1"; fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); - sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + sourceFilesCache.set(path, { sourceFile, version: 1, fileWatcher }); } else { sourceFilesCache.set(path, "0"); @@ -612,7 +612,7 @@ namespace ts { resolutionCache.invalidateResolutionOfFile(path); if (!isString(hostSourceFile)) { hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, (hostSourceFile.version++).toString()); + sourceFilesCache.set(path, (++hostSourceFile.version).toString()); } } else { From 85ce1d0398e252192b1826b9c2818a091b783f3e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 14 Nov 2017 14:47:58 -0800 Subject: [PATCH 007/101] Make the builder state as internal and expose builder instead of builder state --- src/compiler/builder.ts | 627 ++++++++++++------ src/compiler/watch.ts | 15 +- src/harness/unittests/builder.ts | 9 +- src/server/project.ts | 19 +- .../reference/api/tsserverlibrary.d.ts | 85 ++- tests/baselines/reference/api/typescript.d.ts | 85 ++- 6 files changed, 521 insertions(+), 319 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index efa0f0a0372..8703d83aa1d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,29 +1,56 @@ /// +/*@internal*/ namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - - /* @internal */ export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { const outputFiles: OutputFile[] = []; const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - return { outputFiles, emitSkipped: emitResult.emitSkipped }; + return { outputFiles, emitSkipped: emitResult.emitSkipped }; function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { outputFiles.push({ name: fileName, writeByteOrderMark, text }); } } + /** + * Internal Builder to get files affected by another file + */ + export interface InternalBuilder extends BaseBuilder { + /** + * Gets the files affected by the file path + * This api is only for internal use + */ + /*@internal*/ + getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; + } + + /** + * Create the internal builder to get files affected by sourceFile + */ + export function createInternalBuilder(options: BuilderOptions): InternalBuilder { + return createBuilder(options, BuilderType.InternalBuilder); + } + + export enum BuilderType { + InternalBuilder, + SemanticDiagnosticsBuilder, + EmitAndSemanticDiagnosticsBuilder + } + + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature?: string; + } + + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { if (map1 === undefined) { return map2 === undefined; @@ -35,191 +62,273 @@ namespace ts { return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); } - /** - * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) - * Note that it is only safe to pass BuilderState as old state when creating new state, when - * - If iterator's next method to get next affected file is never called - * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete - */ - export interface BuilderState { + export function createBuilder(options: BuilderOptions, builderType: BuilderType.InternalBuilder): InternalBuilder; + export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder; + export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder; + export function createBuilder(options: BuilderOptions, builderType: BuilderType) { /** - * The map of file infos, where there is entry for each file in the program - * The entry is signature of the file (from last emit) or empty string + * Information of the file eg. its version, signature etc */ - fileInfos: ReadonlyMap>; - - /** - * Returns true if module gerneration is not ModuleKind.None - */ - isModuleEmit: boolean; - - /** - * Map of file referenced or undefined if it wasnt module emit - * The entry is present only if file references other files - * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) - */ - referencedMap: ReadonlyMap | undefined; - - /** - * Set of source file's paths that have been changed, either in resolution or versions - */ - changedFilesSet: ReadonlyMap; - - /** - * Set of cached semantic diagnostics per file - */ - semanticDiagnosticsPerFile: ReadonlyMap>; - - /** - * Returns true if this state is safe to use as oldState - */ - canCreateNewStateFrom(): boolean; - - /** - * Gets the files affected by the file path - * This api is only for internal use - */ - /* @internal */ - getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; - - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; - - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; - } - - /** - * Information about the source file: Its version and optional signature from last emit - */ - export interface FileInfo { - version: string; - signature: string; - } - - export interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } - - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - export type ReferencedSet = ReadonlyMap; - - export interface BuilderOptions { - getCanonicalFileName: GetCanonicalFileName; - computeHash: (data: string) => string; - } - - export function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState { const fileInfos = createMap(); - const isModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; - const referencedMap = isModuleEmit ? createMap() : undefined; + /** + * true if module emit is enabled + */ + let isModuleEmit: boolean; + + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + */ + let referencedMap: Map | undefined; + + /** + * Get the files affected by the source file. + * This is dependent on whether its a module emit or not and hence function expression + */ + let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) => ReadonlyArray; + + /** + * Cache of semantic diagnostics for files with their Path being the key + */ const semanticDiagnosticsPerFile = createMap>(); - /** The map has key by source file's path that has been changed */ + + /** + * The map has key by source file's path that has been changed + */ const changedFilesSet = createMap(); - const hasShapeChanged = createMap(); + + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + const hasCalledUpdateShapeSignature = createMap(); + + /** + * Cache of all files excluding default library file for the current program + */ let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - // Iterator datas + /** + * Set of affected files being iterated + */ let affectedFiles: ReadonlyArray | undefined; + /** + * Current index to retrieve affected file from + */ let affectedFilesIndex = 0; + /** + * Current changed file for iterating over affected files + */ + let currentChangedFilePath: Path | undefined; + /** + * Map of file signatures, with key being file path, calculated while getting current changed file's affected files + * These will be commited whenever the iteration through affected files of current changed file is complete + */ + const currentAffectedFilesSignatures = createMap(); + /** + * Already seen affected files + */ const seenAffectedFiles = createMap(); - const getEmitDependentFilesAffectedBy = isModuleEmit ? - getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit; - const useOldState = oldState && oldState.isModuleEmit === isModuleEmit; - if (useOldState) { - Debug.assert(oldState.canCreateNewStateFrom(), "Cannot use this state as old state"); - Debug.assert(!forEachEntry(oldState.changedFilesSet, (_value, path) => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - - copyEntries(oldState.changedFilesSet, changedFilesSet); - copyEntries(oldState.semanticDiagnosticsPerFile, semanticDiagnosticsPerFile); + switch (builderType) { + case BuilderType.InternalBuilder: + return getInternalBuilder(); + case BuilderType.SemanticDiagnosticsBuilder: + return getSemanticDiagnosticsBuilder(); + case BuilderType.EmitAndSemanticDiagnosticsBuilder: + return getEmitAndSemanticDiagnosticsBuilder(); + default: + notImplemented(); } - for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; - let oldInfo: Readonly; - let oldReferences: ReferencedSet; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); - - // Register changed file - // if not using old state so every file is changed - if (!useOldState || - // File wasnt present earlier - !(oldInfo = oldState.fileInfos.get(sourceFile.path)) || - // versions dont match - oldInfo.version !== version || - // Referenced files changed - !hasSameKeys(newReferences, (oldReferences = oldState.referencedMap && oldState.referencedMap.get(sourceFile.path))) || - // Referenced file was deleted - newReferences && forEachEntry(newReferences, (_value, path) => oldState.fileInfos.has(path) && !newProgram.getSourceFileByPath(path as Path))) { - changedFilesSet.set(sourceFile.path, true); - // All changed files need to re-evaluate its semantic diagnostics - semanticDiagnosticsPerFile.delete(sourceFile.path); - } - - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + function getInternalBuilder(): InternalBuilder { + return { + updateProgram, + getFilesAffectedBy, + getAllDependencies + }; } - // For removed files, remove the semantic diagnostics removed files as changed - if (useOldState) { - oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder { + return { + updateProgram, + getAllDependencies, + getSemanticDiagnosticsOfNextAffectedFile, + getSemanticDiagnostics + }; } - // Set the old state and program to undefined to ensure we arent keeping them alive hence forward - oldState = undefined; - newProgram = undefined; - - return { - fileInfos, - isModuleEmit, - referencedMap, - changedFilesSet, - semanticDiagnosticsPerFile, - canCreateNewStateFrom, - getFilesAffectedBy, - emitNextAffectedFile, - getSemanticDiagnostics, - getAllDependencies - }; + function getEmitAndSemanticDiagnosticsBuilder(): EmitAndSemanticDiagnosticsBuilder { + return { + updateProgram, + getAllDependencies, + emitNextAffectedFile, + getSemanticDiagnostics + }; + } /** - * Can use this state as old State if we have iterated through all affected files present + * Update current state to reflect new program + * Updates changed files, references, file infos etc */ - function canCreateNewStateFrom() { - return !affectedFiles || affectedFiles.length <= affectedFilesIndex; + function updateProgram(newProgram: Program) { + const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; + const oldReferencedMap = referencedMap; + if (isModuleEmit !== newProgramHasModuleEmit) { + // Changes in the module emit, clear out everything and initialize as if first time + + // Clear file information and semantic diagnostics + fileInfos.clear(); + semanticDiagnosticsPerFile.clear(); + + // Clear changed files and affected files information + changedFilesSet.clear(); + affectedFiles = undefined; + currentChangedFilePath = undefined; + currentAffectedFilesSignatures.clear(); + + // Update the reference map creation + referencedMap = newProgramHasModuleEmit ? createMap() : undefined; + + // Update the module emit + isModuleEmit = newProgramHasModuleEmit; + getEmitDependentFilesAffectedBy = isModuleEmit ? + getFilesAffectedByUpdatedShapeWhenModuleEmit : + getFilesAffectedByUpdatedShapeWhenNonModuleEmit; + } + else { + if (currentChangedFilePath) { + // Remove the diagnostics for all the affected files since we should resume the state such that + // the whole iteration on currentChangedFile never happened + affectedFiles.map(sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path)); + affectedFiles = undefined; + currentAffectedFilesSignatures.clear(); + } + else { + // Verify the sanity of old state + Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated"); + } + Debug.assert(!forEachEntry(changedFilesSet, (_value, path) => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + } + + // Clear datas that cant be retained beyond previous state + seenAffectedFiles.clear(); + hasCalledUpdateShapeSignature.clear(); + allFilesExcludingDefaultLibraryFile = undefined; + + // Create the reference map and update changed files + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + const oldInfo = fileInfos.get(sourceFile.path); + let oldReferences: ReferencedSet; + + // Register changed file if its new file or we arent reusing old state + if (!oldInfo) { + // New file: Set the file info + fileInfos.set(sourceFile.path, { version }); + changedFilesSet.set(sourceFile.path, true); + } + // versions dont match + else if (oldInfo.version !== version || + // Referenced files changed + !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || + // Referenced file was deleted in the new program + newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { + + // Changed file: Update the version, set as changed file + oldInfo.version = version; + changedFilesSet.set(sourceFile.path, true); + + // All changed files need to re-evaluate its semantic diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + + // Set the references + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + else if (referencedMap) { + referencedMap.delete(sourceFile.path); + } + } + + // For removed files, remove the semantic diagnostics and file info + if (fileInfos.size > newProgram.getSourceFiles().length) { + fileInfos.forEach((_value, path) => { + if (!newProgram.getSourceFileByPath(path as Path)) { + fileInfos.delete(path); + semanticDiagnosticsPerFile.delete(path); + if (referencedMap) { + referencedMap.delete(path); + } + } + }); + } } /** * Gets the files affected by the path from the program */ - function getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray { + function getFilesAffectedBy(programOfThisState: Program, path: Path, cacheToUpdateSignature?: Map): ReadonlyArray { const sourceFile = programOfThisState.getSourceFileByPath(path); if (!sourceFile) { return emptyArray; } - if (!updateShapeSignature(programOfThisState, sourceFile)) { + if (!updateShapeSignature(programOfThisState, sourceFile, cacheToUpdateSignature)) { return [sourceFile]; } - return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile); + return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, cacheToUpdateSignature); + } + + function getNextAffectedFile(programOfThisState: Program): SourceFile | Program | undefined { + while (true) { + if (affectedFiles) { + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + affectedFilesIndex++; + if (!seenAffectedFiles.has(affectedFile.path)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + seenAffectedFiles.set(affectedFile.path, true); + semanticDiagnosticsPerFile.delete(affectedFile.path); + return affectedFile; + } + } + + // Remove the changed file from the change set + changedFilesSet.delete(currentChangedFilePath); + currentChangedFilePath = undefined; + // Commit the changes in file signature + currentAffectedFilesSignatures.forEach((signature, path) => fileInfos.get(path).signature = signature); + currentAffectedFilesSignatures.clear(); + affectedFiles = undefined; + } + + // Get next changed file + const nextKey = changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + if (compilerOptions.outFile || compilerOptions.out) { + Debug.assert(semanticDiagnosticsPerFile.size === 0); + changedFilesSet.clear(); + return programOfThisState; + } + + // Get next batch of affected files + currentChangedFilePath = nextKey.value as Path; + affectedFilesIndex = 0; + affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, currentAffectedFilesSignatures); + } } /** @@ -227,47 +336,48 @@ namespace ts { * Returns undefined when iteration is complete */ function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined { - if (affectedFiles) { - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - affectedFilesIndex++; - if (!seenAffectedFiles.has(affectedFile.path)) { - seenAffectedFiles.set(affectedFile.path, true); - - // Emit the affected file - const result = programOfThisState.emit(affectedFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; - result.affectedFile = affectedFile; - return result; - } - } - - affectedFiles = undefined; - } - - // Get next changed file - const nextKey = changedFilesSet.keys().next(); - if (nextKey.done) { + const affectedFile = getNextAffectedFile(programOfThisState); + if (!affectedFile) { // Done return undefined; } - - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, do it only once - if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(semanticDiagnosticsPerFile.size === 0); - changedFilesSet.clear(); + else if (affectedFile === programOfThisState) { + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers); } - // Get next batch of affected files - changedFilesSet.delete(nextKey.value); - affectedFilesIndex = 0; - affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path); + // Emit the affected file + const targetSourceFile = affectedFile as SourceFile; + const result = programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; + result.affectedFile = targetSourceFile; + return result; + } - // Clear the semantic diagnostic of affected files - affectedFiles.forEach(affectedFile => semanticDiagnosticsPerFile.delete(affectedFile.path)); + /** + * Return the semantic diagnostics for the next affected file or undefined if iteration is complete + * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true + */ + function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray { + while (true) { + const affectedFile = getNextAffectedFile(programOfThisState); + if (!affectedFile) { + // Done + return undefined; + } + else if (affectedFile === programOfThisState) { + // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) + return programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken); + } - return emitNextAffectedFile(programOfThisState, writeFileCallback, cancellationToken, customTransformers); + // Get diagnostics for the affected file if its not ignored + const targetSourceFile = affectedFile as SourceFile; + if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) { + // Get next affected file + continue; + } + + return getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken); + } } /** @@ -276,6 +386,7 @@ namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + Debug.assert(!affectedFiles || affectedFiles[affectedFilesIndex - 1] !== sourceFile || !semanticDiagnosticsPerFile.has(sourceFile.path)); const compilerOptions = programOfThisState.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); @@ -373,19 +484,20 @@ namespace ts { return true; } - /** - * Returns if the shape of the signature has changed since last emit - * Note that it also updates the current signature as the latest signature for the file - */ - function updateShapeSignature(program: Program, sourceFile: SourceFile) { + /** + * Returns if the shape of the signature has changed since last emit + * Note that it also updates the current signature as the latest signature for the file + */ + function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map | undefined) { Debug.assert(!!sourceFile); // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasShapeChanged.has(sourceFile.path)) { + if (hasCalledUpdateShapeSignature.has(sourceFile.path)) { return false; } - hasShapeChanged.set(sourceFile.path, true); + Debug.assert(!cacheToUpdateSignature || !cacheToUpdateSignature.has(sourceFile.path)); + hasCalledUpdateShapeSignature.set(sourceFile.path, true); const info = fileInfos.get(sourceFile.path); Debug.assert(!!info); @@ -393,13 +505,13 @@ namespace ts { let latestSignature: string; if (sourceFile.isDeclarationFile) { latestSignature = sourceFile.version; - info.signature = latestSignature; + setLatestSigature(); } else { const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = options.computeHash(emitOutput.outputFiles[0].text); - info.signature = latestSignature; + setLatestSigature(); } else { latestSignature = prevSignature; @@ -407,6 +519,15 @@ namespace ts { } return !prevSignature || latestSignature !== prevSignature; + + function setLatestSigature() { + if (cacheToUpdateSignature) { + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + } + else { + info.signature = latestSignature; + } + } } /** @@ -514,7 +635,7 @@ namespace ts { /** * When program emits modular code, gets the files affected by the sourceFile whose shape has changed */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) { if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } @@ -537,7 +658,7 @@ namespace ts { if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile)) { + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature)) { queue.push(...getReferencedByPaths(currentPath)); } } @@ -548,3 +669,93 @@ namespace ts { } } } + +namespace ts { + export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + + export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } + + export interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + + export interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } + + /** + * Builder to manage the program state changes + */ + export interface BaseBuilder { + /** + * Updates the program in the builder to represent new state + */ + updateProgram(newProgram: Program): void; + + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + } + + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + export interface SemanticDiagnosticsBuilder extends BaseBuilder { + /** + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete + */ + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + + /** + * 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 + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + export interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + + /** + * 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 + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + + /** + * Create the builder to manage semantic diagnostics and cache them + */ + export function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder { + return createBuilder(options, BuilderType.SemanticDiagnosticsBuilder); + } + + /** + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well + */ + export function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder { + return createBuilder(options, BuilderType.EmitAndSemanticDiagnosticsBuilder); + } +} diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 07fb9203b6a..fcb4d98ace0 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -84,18 +84,17 @@ namespace ts { } /** - * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files */ export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - let builderState: Readonly | undefined; - const options: BuilderOptions = { + const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: data => system.createHash ? system.createHash(data) : data - }; + computeHash: system.createHash ? system.createHash.bind(system) : identity + }); return (host: DirectoryStructureHost, program: Program) => { - builderState = createBuilderState(program, options, builderState); + builder.updateProgram(program); // First get and report any syntactic errors. const diagnostics = program.getSyntacticDiagnostics().slice(); @@ -118,14 +117,14 @@ namespace ts { let emitSkipped: boolean; let affectedEmitResult: AffectedFileEmitResult; - while (affectedEmitResult = builderState.emitNextAffectedFile(program, writeFile)) { + while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) { emitSkipped = emitSkipped || affectedEmitResult.emitSkipped; addRange(diagnostics, affectedEmitResult.diagnostics); sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps); } if (reportSemanticDiagnostics) { - addRange(diagnostics, builderState.getSemanticDiagnostics(program)); + addRange(diagnostics, builder.getSemanticDiagnostics(program)); } sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 89b0852bd32..a9c16c59fbd 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -44,17 +44,16 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - let builderState: BuilderState; - const builderOptions: BuilderOptions = { + const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: identity, computeHash: identity - }; + }); return fileNames => { const program = getProgram(); - builderState = createBuilderState(program, builderOptions, builderState); + builder.updateProgram(program); const outputFileNames: string[] = []; // tslint:disable-next-line no-empty - while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { + while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { } assert.deepEqual(outputFileNames, fileNames); }; diff --git a/src/server/project.ts b/src/server/project.ts index 056fedf19af..3a5785163d2 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builderState: BuilderState; + private builder: InternalBuilder | undefined; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -451,11 +451,14 @@ namespace ts.server { return []; } this.updateGraph(); - this.builderState = createBuilderState(this.program, { - getCanonicalFileName: this.projectService.toCanonicalFileName, - computeHash: data => this.projectService.host.createHash(data) - }, this.builderState); - return mapDefined(this.builderState.getFilesAffectedBy(this.program, scriptInfo.path), + if (!this.builder) { + this.builder = createInternalBuilder({ + getCanonicalFileName: this.projectService.toCanonicalFileName, + computeHash: data => this.projectService.host.createHash(data) + }); + } + this.builder.updateProgram(this.program); + return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } @@ -491,7 +494,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; - this.builderState = undefined; + this.builder = undefined; this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -532,7 +535,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.externalFiles = undefined; this.program = undefined; - this.builderState = undefined; + this.builder = undefined; this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5c306474533..e258d179310 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,40 +3771,48 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } /** - * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) - * Note that it is only safe to pass BuilderState as old state when creating new state, when - * - If iterator's next method to get next affected file is never called - * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + * Builder to manage the program state changes */ - interface BuilderState { + interface BaseBuilder { /** - * The map of file infos, where there is entry for each file in the program - * The entry is signature of the file (from last emit) or empty string + * Updates the program in the builder to represent new state */ - fileInfos: ReadonlyMap>; + updateProgram(newProgram: Program): void; /** - * Returns true if module gerneration is not ModuleKind.None + * Get all the dependencies of the file */ - isModuleEmit: boolean; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + } + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + interface SemanticDiagnosticsBuilder extends BaseBuilder { /** - * Map of file referenced or undefined if it wasnt module emit - * The entry is present only if file references other files - * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete */ - referencedMap: ReadonlyMap | undefined; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; /** - * Set of source file's paths that have been changed, either in resolution or versions + * 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 + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ - changedFilesSet: ReadonlyMap; - /** - * Set of cached semantic diagnostics per file - */ - semanticDiagnosticsPerFile: ReadonlyMap>; - /** - * Returns true if this state is safe to use as oldState - */ - canCreateNewStateFrom(): boolean; + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ @@ -3812,33 +3820,20 @@ declare namespace ts { /** * 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 - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** - * Information about the source file: Its version and optional signature from last emit + * Create the builder to manage semantic diagnostics and cache them */ - interface FileInfo { - version: string; - signature: string; - } - interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } + function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; /** - * Referenced files with values for the keys as referenced file's path to be true + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well */ - type ReferencedSet = ReadonlyMap; - interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; - } - function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; + function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -7278,7 +7273,7 @@ declare namespace ts.server { languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; - private builderState; + private builder; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 2212a859c0a..bac53072302 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,40 +3718,48 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } /** - * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) - * Note that it is only safe to pass BuilderState as old state when creating new state, when - * - If iterator's next method to get next affected file is never called - * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + * Builder to manage the program state changes */ - interface BuilderState { + interface BaseBuilder { /** - * The map of file infos, where there is entry for each file in the program - * The entry is signature of the file (from last emit) or empty string + * Updates the program in the builder to represent new state */ - fileInfos: ReadonlyMap>; + updateProgram(newProgram: Program): void; /** - * Returns true if module gerneration is not ModuleKind.None + * Get all the dependencies of the file */ - isModuleEmit: boolean; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + } + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + interface SemanticDiagnosticsBuilder extends BaseBuilder { /** - * Map of file referenced or undefined if it wasnt module emit - * The entry is present only if file references other files - * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete */ - referencedMap: ReadonlyMap | undefined; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; /** - * Set of source file's paths that have been changed, either in resolution or versions + * 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 + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ - changedFilesSet: ReadonlyMap; - /** - * Set of cached semantic diagnostics per file - */ - semanticDiagnosticsPerFile: ReadonlyMap>; - /** - * Returns true if this state is safe to use as oldState - */ - canCreateNewStateFrom(): boolean; + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ @@ -3759,33 +3767,20 @@ declare namespace ts { /** * 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 - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** - * Information about the source file: Its version and optional signature from last emit + * Create the builder to manage semantic diagnostics and cache them */ - interface FileInfo { - version: string; - signature: string; - } - interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } + function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; /** - * Referenced files with values for the keys as referenced file's path to be true + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well */ - type ReferencedSet = ReadonlyMap; - interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; - } - function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; + function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -3819,7 +3814,7 @@ declare namespace ts { declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** - * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files */ function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; interface WatchHost { From e102fee363e256cd14e2881f6912e78b4e369333 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 14 Nov 2017 16:53:07 -0800 Subject: [PATCH 008/101] Use the results from affected file enumerator apis as Affected File result --- src/compiler/builder.ts | 35 ++++++++++++------- src/compiler/watch.ts | 8 ++--- .../reference/api/tsserverlibrary.d.ts | 9 ++--- tests/baselines/reference/api/typescript.d.ts | 9 ++--- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 8703d83aa1d..40aabb04baa 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -331,11 +331,18 @@ namespace ts { } } + /** + * Returns the result with affected file + */ + function toAffectedFileResult(result: T, affectedFile?: SourceFile): AffectedFileResult { + return { result, affectedFile }; + } + /** * Emits the next affected file, and returns the EmitResult along with source files emitted * Returns undefined when iteration is complete */ - function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined { + function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult { const affectedFile = getNextAffectedFile(programOfThisState); if (!affectedFile) { // Done @@ -343,21 +350,22 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers); + return toAffectedFileResult(programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers)); } // Emit the affected file const targetSourceFile = affectedFile as SourceFile; - const result = programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; - result.affectedFile = targetSourceFile; - return result; + return toAffectedFileResult( + programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), + targetSourceFile + ); } /** * Return the semantic diagnostics for the next affected file or undefined if iteration is complete * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true */ - function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray { + function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { while (true) { const affectedFile = getNextAffectedFile(programOfThisState); if (!affectedFile) { @@ -366,7 +374,7 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - return programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken); + return toAffectedFileResult(programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken)); } // Get diagnostics for the affected file if its not ignored @@ -376,7 +384,10 @@ namespace ts { continue; } - return getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken); + return toAffectedFileResult( + getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken), + targetSourceFile + ); } } @@ -682,9 +693,7 @@ namespace ts { text: string; } - export interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } + export type AffectedFileResult = { result: T; affectedFile?: SourceFile; } | undefined; export interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; @@ -714,7 +723,7 @@ namespace ts { * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program @@ -733,7 +742,7 @@ namespace ts { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index fcb4d98ace0..716846cd606 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -116,11 +116,11 @@ namespace ts { let sourceMaps: SourceMapData[]; let emitSkipped: boolean; - let affectedEmitResult: AffectedFileEmitResult; + let affectedEmitResult: AffectedFileResult; while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) { - emitSkipped = emitSkipped || affectedEmitResult.emitSkipped; - addRange(diagnostics, affectedEmitResult.diagnostics); - sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps); + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + addRange(diagnostics, affectedEmitResult.result.diagnostics); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); } if (reportSemanticDiagnostics) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e258d179310..e5ee4d8db6c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,9 +3771,10 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } - interface AffectedFileEmitResult extends EmitResult { + type AffectedFileResult = { + result: T; affectedFile?: SourceFile; - } + } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; computeHash: (data: string) => string; @@ -3799,7 +3800,7 @@ declare namespace ts { * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; /** * 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 @@ -3816,7 +3817,7 @@ declare namespace ts { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; /** * 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 diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index bac53072302..bd1eff0be6f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,9 +3718,10 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } - interface AffectedFileEmitResult extends EmitResult { + type AffectedFileResult = { + result: T; affectedFile?: SourceFile; - } + } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; computeHash: (data: string) => string; @@ -3746,7 +3747,7 @@ declare namespace ts { * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; /** * 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 @@ -3763,7 +3764,7 @@ declare namespace ts { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; /** * 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 From ffa64e8c4f1ca31263e0e16e299b0542f633fc65 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 16 Nov 2017 11:16:39 -0800 Subject: [PATCH 009/101] Set program as affected if emitting/diagnostics for whole program --- src/compiler/builder.ts | 16 +++++++++++----- .../baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 40aabb04baa..69f60bb1708 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -334,8 +334,8 @@ namespace ts { /** * Returns the result with affected file */ - function toAffectedFileResult(result: T, affectedFile?: SourceFile): AffectedFileResult { - return { result, affectedFile }; + function toAffectedFileResult(result: T, affected: SourceFile | Program): AffectedFileResult { + return { result, affected }; } /** @@ -350,7 +350,10 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - return toAffectedFileResult(programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers)); + return toAffectedFileResult( + programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), + programOfThisState + ); } // Emit the affected file @@ -374,7 +377,10 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - return toAffectedFileResult(programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken)); + return toAffectedFileResult( + programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), + programOfThisState + ); } // Get diagnostics for the affected file if its not ignored @@ -693,7 +699,7 @@ namespace ts { text: string; } - export type AffectedFileResult = { result: T; affectedFile?: SourceFile; } | undefined; + export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; export interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e5ee4d8db6c..de738e7cbc0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3773,7 +3773,7 @@ declare namespace ts { } type AffectedFileResult = { result: T; - affectedFile?: SourceFile; + affected: SourceFile | Program; } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index bd1eff0be6f..974bc043d3f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3720,7 +3720,7 @@ declare namespace ts { } type AffectedFileResult = { result: T; - affectedFile?: SourceFile; + affected: SourceFile | Program; } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; From 012f12bcbd001165f3f6216105b92eb0aa6f326b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 22 Nov 2017 18:24:53 -0800 Subject: [PATCH 010/101] To handle cancellation token, remove changed/affected files from the changeset only after getting the result --- src/compiler/builder.ts | 100 +++++++++++++++++++++---------- src/compiler/program.ts | 2 +- src/harness/unittests/builder.ts | 72 +++++++++++++++++++++- src/server/project.ts | 2 +- 4 files changed, 141 insertions(+), 35 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 69f60bb1708..ddbb015a2ee 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -22,7 +22,7 @@ namespace ts { * This api is only for internal use */ /*@internal*/ - getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; + getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken): ReadonlyArray; } /** @@ -86,7 +86,7 @@ namespace ts { * Get the files affected by the source file. * This is dependent on whether its a module emit or not and hence function expression */ - let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) => ReadonlyArray; + let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; /** * Cache of semantic diagnostics for files with their Path being the key @@ -272,38 +272,65 @@ namespace ts { /** * Gets the files affected by the path from the program */ - function getFilesAffectedBy(programOfThisState: Program, path: Path, cacheToUpdateSignature?: Map): ReadonlyArray { + function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be commited once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are commited only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); const sourceFile = programOfThisState.getSourceFileByPath(path); if (!sourceFile) { return emptyArray; } - if (!updateShapeSignature(programOfThisState, sourceFile, cacheToUpdateSignature)) { + if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { return [sourceFile]; } - return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, cacheToUpdateSignature); + const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(signatureCache); + } + return result; } - function getNextAffectedFile(programOfThisState: Program): SourceFile | Program | undefined { + /** + * Updates the signatures from the cache + * This should be called whenever it is safe to commit the state of the builder + */ + function updateSignaturesFromCache(signatureCache: Map) { + signatureCache.forEach((signature, path) => { + fileInfos.get(path).signature = signature; + hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ + function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined { while (true) { if (affectedFiles) { while (affectedFilesIndex < affectedFiles.length) { const affectedFile = affectedFiles[affectedFilesIndex]; - affectedFilesIndex++; if (!seenAffectedFiles.has(affectedFile.path)) { // Set the next affected file as seen and remove the cached semantic diagnostics - seenAffectedFiles.set(affectedFile.path, true); semanticDiagnosticsPerFile.delete(affectedFile.path); return affectedFile; } + seenAffectedFiles.set(affectedFile.path, true); + affectedFilesIndex++; } // Remove the changed file from the change set changedFilesSet.delete(currentChangedFilePath); currentChangedFilePath = undefined; // Commit the changes in file signature - currentAffectedFilesSignatures.forEach((signature, path) => fileInfos.get(path).signature = signature); + updateSignaturesFromCache(currentAffectedFilesSignatures); currentAffectedFilesSignatures.clear(); affectedFiles = undefined; } @@ -320,21 +347,37 @@ namespace ts { // so operations are performed directly on program, return program if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); - changedFilesSet.clear(); return programOfThisState; } // Get next batch of affected files + currentAffectedFilesSignatures.clear(); + affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); currentChangedFilePath = nextKey.value as Path; + semanticDiagnosticsPerFile.delete(currentChangedFilePath); affectedFilesIndex = 0; - affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, currentAffectedFilesSignatures); + } + } + + /** + * 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(programOfThisState: Program, affected: SourceFile | Program) { + if (affected === programOfThisState) { + changedFilesSet.clear(); + } + else { + seenAffectedFiles.set((affected).path, true); + affectedFilesIndex++; } } /** * Returns the result with affected file */ - function toAffectedFileResult(result: T, affected: SourceFile | Program): AffectedFileResult { + function toAffectedFileResult(programOfThisState: Program, result: T, affected: SourceFile | Program): AffectedFileResult { + doneWithAffectedFile(programOfThisState, affected); return { result, affected }; } @@ -343,7 +386,7 @@ namespace ts { * Returns undefined when iteration is complete */ function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult { - const affectedFile = getNextAffectedFile(programOfThisState); + const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); if (!affectedFile) { // Done return undefined; @@ -351,6 +394,7 @@ namespace ts { else if (affectedFile === programOfThisState) { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) return toAffectedFileResult( + programOfThisState, programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), programOfThisState ); @@ -359,6 +403,7 @@ namespace ts { // Emit the affected file const targetSourceFile = affectedFile as SourceFile; return toAffectedFileResult( + programOfThisState, programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), targetSourceFile ); @@ -370,7 +415,7 @@ namespace ts { */ function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { while (true) { - const affectedFile = getNextAffectedFile(programOfThisState); + const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); if (!affectedFile) { // Done return undefined; @@ -378,6 +423,7 @@ namespace ts { else if (affectedFile === programOfThisState) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) return toAffectedFileResult( + programOfThisState, programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), programOfThisState ); @@ -387,10 +433,12 @@ namespace ts { const targetSourceFile = affectedFile as SourceFile; if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) { // Get next affected file + doneWithAffectedFile(programOfThisState, targetSourceFile); continue; } return toAffectedFileResult( + programOfThisState, getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken), targetSourceFile ); @@ -505,16 +553,14 @@ namespace ts { * Returns if the shape of the signature has changed since last emit * Note that it also updates the current signature as the latest signature for the file */ - function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map | undefined) { + function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { Debug.assert(!!sourceFile); // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasCalledUpdateShapeSignature.has(sourceFile.path)) { + if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { return false; } - Debug.assert(!cacheToUpdateSignature || !cacheToUpdateSignature.has(sourceFile.path)); - hasCalledUpdateShapeSignature.set(sourceFile.path, true); const info = fileInfos.get(sourceFile.path); Debug.assert(!!info); @@ -522,29 +568,19 @@ namespace ts { let latestSignature: string; if (sourceFile.isDeclarationFile) { latestSignature = sourceFile.version; - setLatestSigature(); } else { - const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); + const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = options.computeHash(emitOutput.outputFiles[0].text); - setLatestSigature(); } else { latestSignature = prevSignature; } } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); return !prevSignature || latestSignature !== prevSignature; - - function setLatestSigature() { - if (cacheToUpdateSignature) { - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - } - else { - info.signature = latestSignature; - } - } } /** @@ -652,7 +688,7 @@ namespace ts { /** * When program emits modular code, gets the files affected by the sourceFile whose shape has changed */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) { + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } @@ -675,7 +711,7 @@ namespace ts { if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature)) { + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { queue.push(...getReferencedByPaths(currentPath)); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4088a6951a1..213b1fc9601 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1162,7 +1162,7 @@ namespace ts { // This is because in the -out scenario all files need to be emitted, and therefore all // files need to be type checked. And the way to specify that all files need to be type // checked is to not pass the file to getEmitResolver. - const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); + const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken); performance.mark("beforeEmit"); diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index a9c16c59fbd..64f43be1376 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -41,9 +41,39 @@ namespace ts { program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); assertChanges(["/b.js", "/a.js"]); }); + + it("keeps the file in affected files if cancellation token throws during the operation", () => { + const files: NamedSourceText[] = [ + { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, + { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, + { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, + { name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") }, + { name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") }, + ]; + + let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); + const assertChanges = makeAssertChangesWithCancellationToken(() => program); + // No cancellation + assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); + + // cancel when emitting a.ts + program = updateProgramFile(program, "/a.ts", "export function foo() { }"); + assertChanges(["/a.js"], 0); + // Change d.ts and verify previously pending a.ts is emitted as well + program = updateProgramFile(program, "/d.ts", "export function bar() { }"); + assertChanges(["/a.js", "/d.js"]); + + // Cancel when emitting b.js + program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); + program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); + assertChanges(["/d.js", "/b.js", "/a.js"], 1); + // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts + program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); + assertChanges(["/b.js", "/a.js", "/e.js"]); + }); }); - function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { + function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: identity, computeHash: identity @@ -59,6 +89,46 @@ namespace ts { }; } + function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: ReadonlyArray, cancelAfterEmitLength?: number) => void { + const builder = createEmitAndSemanticDiagnosticsBuilder({ + getCanonicalFileName: identity, + computeHash: identity + }); + let cancel = false; + const cancellationToken: CancellationToken = { + isCancellationRequested: () => cancel, + throwIfCancellationRequested: () => { + if (cancel) { + throw new OperationCanceledException(); + } + }, + }; + return (fileNames, cancelAfterEmitLength?: number) => { + cancel = false; + let operationWasCancelled = false; + const program = getProgram(); + builder.updateProgram(program); + const outputFileNames: string[] = []; + try { + // tslint:disable-next-line no-empty + do { + assert.isFalse(cancel); + if (outputFileNames.length === cancelAfterEmitLength) { + cancel = true; + } + } while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName), cancellationToken)); + } + catch (e) { + assert.isFalse(operationWasCancelled); + assert.isTrue(e instanceof OperationCanceledException, e.toString()); + operationWasCancelled = true; + } + assert.equal(cancel, operationWasCancelled); + assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength); + assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); + }; + } + function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts { return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { updateProgramText(files, fileName, fileContent); diff --git a/src/server/project.ts b/src/server/project.ts index 3a5785163d2..9b9dbb99436 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -458,7 +458,7 @@ namespace ts.server { }); } this.builder.updateProgram(this.program); - return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), + return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path, this.cancellationToken), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } From 0b79f4a0735c222e21e307ebbcfa6112ff12a89c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 22 Nov 2017 18:34:49 -0800 Subject: [PATCH 011/101] Handle emit only declaration file to always produce declaration file and skip the diagnostics check --- src/compiler/checker.ts | 6 ++-- src/compiler/declarationEmitter.ts | 2 +- src/compiler/program.ts | 52 ++++++++++++++++-------------- src/compiler/types.ts | 2 +- src/harness/unittests/builder.ts | 7 ++++ 5 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bc9906f2054..364af70cb55 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -564,10 +564,12 @@ namespace ts { return _jsxNamespace; } - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, ignoreDiagnostics?: boolean) { // Ensure we have all the type information in place for this file so that all the // emitter questions of this resolver will return the right information. - getDiagnostics(sourceFile, cancellationToken); + if (!ignoreDiagnostics) { + getDiagnostics(sourceFile, cancellationToken); + } return emitResolver; } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index dc76e0ddf50..90d9ff3de5a 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -1994,7 +1994,7 @@ namespace ts { export function writeDeclarationFile(declarationFilePath: string, sourceFileOrBundle: SourceFile | Bundle, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, emitOnlyDtsFiles: boolean) { const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFileOrBundle, emitOnlyDtsFiles); const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath) || host.getCompilerOptions().noEmit; - if (!emitSkipped) { + if (!emitSkipped || emitOnlyDtsFiles) { const sourceFiles = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle.sourceFiles : [sourceFileOrBundle]; const declarationOutput = emitDeclarationResult.referencesOutput + getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 213b1fc9601..9560859c7ce 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1125,32 +1125,34 @@ namespace ts { function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { let declarationDiagnostics: ReadonlyArray = []; - if (options.noEmit) { - return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; - } - - // If the noEmitOnError flag is set, then check if we have any errors so far. If so, - // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we - // get any preEmit diagnostics, not just the ones - if (options.noEmitOnError) { - const diagnostics = [ - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSemanticDiagnostics(sourceFile, cancellationToken) - ]; - - if (diagnostics.length === 0 && program.getCompilerOptions().declaration) { - declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + if (!emitOnlyDtsFiles) { + if (options.noEmit) { + return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; } - if (diagnostics.length > 0 || declarationDiagnostics.length > 0) { - return { - diagnostics: concatenate(diagnostics, declarationDiagnostics), - sourceMaps: undefined, - emittedFiles: undefined, - emitSkipped: true - }; + // If the noEmitOnError flag is set, then check if we have any errors so far. If so, + // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we + // get any preEmit diagnostics, not just the ones + if (options.noEmitOnError) { + const diagnostics = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; + + if (diagnostics.length === 0 && program.getCompilerOptions().declaration) { + declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + } + + if (diagnostics.length > 0 || declarationDiagnostics.length > 0) { + return { + diagnostics: concatenate(diagnostics, declarationDiagnostics), + sourceMaps: undefined, + emittedFiles: undefined, + emitSkipped: true + }; + } } } @@ -1162,7 +1164,7 @@ namespace ts { // This is because in the -out scenario all files need to be emitted, and therefore all // files need to be type checked. And the way to specify that all files need to be type // checked is to not pass the file to getEmitResolver. - const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken); + const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken, emitOnlyDtsFiles); performance.mark("beforeEmit"); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 53c36ac6caa..568a3e2afe4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2798,7 +2798,7 @@ namespace ts { // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; - /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken): EmitResolver; + /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken, ignoreDiagnostics?: boolean): EmitResolver; /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 64f43be1376..38ac8a4e3d7 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -70,6 +70,13 @@ namespace ts { // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); assertChanges(["/b.js", "/a.js", "/e.js"]); + + // Cancel in the middle of affected files list after b.js emit + program = updateProgramFile(program, "/b.ts", "export class b { foo2() { c + 1; } }"); + assertChanges(["/b.js", "/a.js"], 1); + // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts + program = updateProgramFile(program, "/e.ts", "export function bar5() { }"); + assertChanges(["/b.js", "/a.js", "/e.js"]); }); }); From 3dda2179e8cf7d0b4c6fa474b98c5e10991f5813 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 14:35:37 -0800 Subject: [PATCH 012/101] Rename getProgram to getExistingProgram --- src/compiler/watch.ts | 8 ++++---- src/harness/unittests/reuseProgramStructure.ts | 4 ++-- src/harness/unittests/tscWatchMode.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 5a82584c8c3..70aa8939bcc 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -214,9 +214,9 @@ namespace ts { export interface Watch { /** Synchronize the program with the changes */ synchronizeProgram(): void; - /** Get current program */ + /** Gets the existing program without synchronizing with changes on host */ /*@internal*/ - getProgram(): Program; + getExistingProgram(): Program; } /** @@ -360,8 +360,8 @@ namespace ts { watchConfigFileWildCardDirectories(); return configFileName ? - { getProgram: () => program, synchronizeProgram } : - { getProgram: () => program, synchronizeProgram, updateRootFileNames }; + { getExistingProgram: () => program, synchronizeProgram } : + { getExistingProgram: () => program, synchronizeProgram, updateRootFileNames }; function synchronizeProgram() { writeLog(`Synchronizing program`); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index cc330300d33..108e2d86694 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -897,12 +897,12 @@ namespace ts { } function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { - const program = createWatchOfFilesAndCompilerOptions(rootFiles, options, system).getProgram(); + const program = createWatchOfFilesAndCompilerOptions(rootFiles, options, system).getExistingProgram(); verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); } function verifyProgramWithConfigFile(system: System, configFileName: string) { - const program = createWatchOfConfigFile(configFileName, {}, system).getProgram(); + const program = createWatchOfConfigFile(configFileName, {}, system).getExistingProgram(); const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 6a83d950ad3..736f8c520b3 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -24,12 +24,12 @@ namespace ts.tscWatch { function createWatchOfConfigFile(configFileName: string, host: WatchedSystem) { const watch = ts.createWatchOfConfigFile(configFileName, {}, host); - return () => watch.getProgram(); + return () => watch.getExistingProgram(); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}) { const watch = ts.createWatchOfFilesAndCompilerOptions(rootFiles, options, host); - return () => watch.getProgram(); + return () => watch.getExistingProgram(); } function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) { @@ -237,8 +237,8 @@ namespace ts.tscWatch { const host = createWatchedSystem([configFile, libFile, file1, file2, file3]); const watch = ts.createWatchOfConfigFile(configFile.path, {}, host, notImplemented); - checkProgramActualFiles(watch.getProgram(), [file1.path, libFile.path, file2.path]); - checkProgramRootFiles(watch.getProgram(), [file1.path, file2.path]); + checkProgramActualFiles(watch.getExistingProgram(), [file1.path, libFile.path, file2.path]); + checkProgramRootFiles(watch.getExistingProgram(), [file1.path, file2.path]); checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]); const configDir = getDirectoryPath(configFile.path); checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true); From 61fc9b94de0b7e40030d41d2893e7a61817656e4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 14:40:30 -0800 Subject: [PATCH 013/101] Rename Watch.synchronizeProgram to getProgram and return the updated program as part of this api --- src/compiler/watch.ts | 16 +++++++++------- tests/baselines/reference/api/typescript.d.ts | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 70aa8939bcc..e00a6751a70 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -212,8 +212,8 @@ namespace ts { } export interface Watch { - /** Synchronize the program with the changes */ - synchronizeProgram(): void; + /** Synchronize with host and get updated program */ + getProgram(): Program; /** Gets the existing program without synchronizing with changes on host */ /*@internal*/ getExistingProgram(): Program; @@ -360,10 +360,10 @@ namespace ts { watchConfigFileWildCardDirectories(); return configFileName ? - { getExistingProgram: () => program, synchronizeProgram } : - { getExistingProgram: () => program, synchronizeProgram, updateRootFileNames }; + { getExistingProgram: () => program, getProgram: synchronizeProgram } : + { getExistingProgram: () => program, getProgram: synchronizeProgram, updateRootFileNames }; - function synchronizeProgram() { + function synchronizeProgram(): Program { writeLog(`Synchronizing program`); if (hasChangedCompilerOptions) { @@ -375,7 +375,7 @@ namespace ts { const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { - return; + return program; } beforeProgramCreate(compilerOptions); @@ -411,6 +411,7 @@ namespace ts { afterProgramCreate(directoryStructureHost, program); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + return program; } function updateRootFileNames(files: string[]) { @@ -569,7 +570,8 @@ namespace ts { case ConfigFileProgramReloadLevel.Full: return reloadConfigFile(); default: - return synchronizeProgram(); + synchronizeProgram(); + return; } } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3ea22f19902..df02d53209c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3850,8 +3850,8 @@ declare namespace ts { onConfigFileDiagnostic(diagnostic: Diagnostic): void; } interface Watch { - /** Synchronize the program with the changes */ - synchronizeProgram(): void; + /** Synchronize with host and get updated program */ + getProgram(): Program; } /** * Creates the watch what generates program using the config file From 471c83b7f59e7df61af49e44811049ebdd90fd00 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 15:08:56 -0800 Subject: [PATCH 014/101] Rename WatchHost.moduleNameResolver to WatchHost.resolveModuleNames to align with compiler host --- src/compiler/resolutionCache.ts | 8 ++++---- src/compiler/watch.ts | 15 ++++++++------- src/server/project.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e21e81a2e88..1c96dc21f08 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -9,7 +9,7 @@ namespace ts { startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[]; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined): ResolvedModuleFull[]; resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; @@ -72,7 +72,7 @@ namespace ts { type GetResolutionWithResolvedFileName = (resolution: T) => R; - export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache { + export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string, logChangesWhenResolvingModule: boolean): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; let allFilesHaveInvalidatedResolution = false; @@ -306,12 +306,12 @@ namespace ts { ); } - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[] { + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined): ResolvedModuleFull[] { return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, resolveModuleName, getResolvedModule, - reusedNames, logChanges + reusedNames, logChangesWhenResolvingModule ); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index e00a6751a70..c9b520c3777 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -171,7 +171,7 @@ namespace ts { afterProgramCreate(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ - moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -310,9 +310,6 @@ namespace ts { const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); let newLine = getNewLineCharacter(compilerOptions, system); - const resolveModuleNames: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModule[] = host.moduleNameResolver ? - (moduleNames, containingFile, reusedNames) => host.moduleNameResolver(moduleNames, containingFile, reusedNames) : - (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost @@ -332,8 +329,6 @@ namespace ts { getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", getDirectories: path => directoryStructureHost.getDirectories(path), realpath, - resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), - resolveModuleNames, onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, @@ -351,8 +346,14 @@ namespace ts { // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : - getCurrentDirectory() + getCurrentDirectory(), + /*logChangesWhenResolvingModule*/ false ); + // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names + compilerHost.resolveModuleNames = host.resolveModuleNames ? + host.resolveModuleNames.bind(host) : + resolutionCache.resolveModuleNames.bind(resolutionCache); + compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); synchronizeProgram(); diff --git a/src/server/project.ts b/src/server/project.ts index 9b9dbb99436..800e93f1949 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -237,7 +237,7 @@ namespace ts.server { } // Use the current directory as resolution root only if the project created using current directory string - this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory); + this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true); this.languageService = createLanguageService(this, this.documentRegistry); if (!languageServiceEnabled) { this.disableLanguageService(); @@ -353,7 +353,7 @@ namespace ts.server { } resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[] { - return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ true); + return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames); } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index df02d53209c..c82599c18f2 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3828,7 +3828,7 @@ declare namespace ts { /** Custom action after new program creation is successful */ afterProgramCreate(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ - moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options From 1a91256c2276972f5393e917b4cf26f7ac855578 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 15:24:22 -0800 Subject: [PATCH 015/101] Make before and after program create callbacks optional --- src/compiler/watch.ts | 14 +++++++------- tests/baselines/reference/api/typescript.d.ts | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index c9b520c3777..5e5a97e16a2 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -165,10 +165,10 @@ namespace ts { /** FS system to use */ system: System; - /** Custom action before creating the program */ - beforeProgramCreate(compilerOptions: CompilerOptions): void; - /** Custom action after new program creation is successful */ - afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + /** If provided, callback to invoke before each program creation */ + beforeProgramCreate?(compilerOptions: CompilerOptions): void; + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; @@ -239,7 +239,6 @@ namespace ts { export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { return createWatch({ system, - beforeProgramCreate: noop, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), onConfigFileDiagnostic: reportDiagnostic || createDiagnosticReporter(system), configFileName, @@ -253,7 +252,6 @@ namespace ts { export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { return createWatch({ system, - beforeProgramCreate: noop, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), rootFiles, options @@ -286,7 +284,9 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, afterProgramCreate, beforeProgramCreate, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; + const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; + const beforeProgramCreate: WatchHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; + const afterProgramCreate: WatchHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchOfConfigFileHost; // From tsc we want to get already parsed result and hence check for rootFileNames diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c82599c18f2..13b5b17f50f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3823,10 +3823,10 @@ declare namespace ts { interface WatchHost { /** FS system to use */ system: System; - /** Custom action before creating the program */ - beforeProgramCreate(compilerOptions: CompilerOptions): void; - /** Custom action after new program creation is successful */ - afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + /** If provided, callback to invoke before each program creation */ + beforeProgramCreate?(compilerOptions: CompilerOptions): void; + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } From 944f8b879288581d04e0f69ee649a2361547b700 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 18:17:25 -0800 Subject: [PATCH 016/101] Instead of using system as object on WatchHost, create WatchCompilerHost that combines the functionality --- src/compiler/core.ts | 2 - src/compiler/sys.ts | 4 +- src/compiler/tsc.ts | 41 +++++----- src/compiler/utilities.ts | 4 +- src/compiler/watch.ts | 82 +++++++++++-------- src/server/project.ts | 2 +- src/services/services.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 4 +- tests/baselines/reference/api/typescript.d.ts | 18 ++-- 9 files changed, 87 insertions(+), 72 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 48c1b84dbaa..e1443215c91 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -3023,9 +3023,7 @@ namespace ts { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); return { useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - newLine: host.newLine, readFile: (path, encoding) => host.readFile(path, encoding), - write: s => host.write(s), writeFile, fileExists, directoryExists, diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 841dba8a528..c327bd608de 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -34,9 +34,7 @@ namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ export interface DirectoryStructureHost { - newLine: string; useCaseSensitiveFileNames: boolean; - write(s: string): void; readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; @@ -49,7 +47,9 @@ namespace ts { } export interface System extends DirectoryStructureHost { + newLine: string; args: string[]; + write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 5342319e75d..9a7e98d954c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -150,37 +150,34 @@ namespace ts { return sys.exit(exitStatus); } - function createProgramCompilerWithBuilderState() { - const compilerWithBuilderState = ts.createProgramCompilerWithBuilderState(sys, reportDiagnostic); - return (host: DirectoryStructureHost, program: Program) => { + function createWatchCompilerHost(): WatchCompilerHost { + const watchCompilerHost = ts.createWatchCompilerHost(sys, reportDiagnostic); + const compilerWithBuilderState = watchCompilerHost.afterProgramCreate; + watchCompilerHost.beforeProgramCreate = enableStatistics; + watchCompilerHost.afterProgramCreate = (host, program) => { compilerWithBuilderState(host, program); reportStatistics(program); }; + return watchCompilerHost; } function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) { - createWatch({ - system: sys, - beforeProgramCreate: enableStatistics, - afterProgramCreate: createProgramCompilerWithBuilderState(), - onConfigFileDiagnostic: reportDiagnostic, - rootFiles: configParseResult.fileNames, - options: configParseResult.options, - configFileName: configParseResult.options.configFilePath, - optionsToExtend, - configFileSpecs: configParseResult.configFileSpecs, - configFileWildCardDirectories: configParseResult.wildcardDirectories - }); + const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfConfigFile; + watchCompilerHost.onConfigFileDiagnostic = reportDiagnostic; + watchCompilerHost.rootFiles = configParseResult.fileNames; + watchCompilerHost.options = configParseResult.options; + watchCompilerHost.configFileName = configParseResult.options.configFilePath; + watchCompilerHost.optionsToExtend = optionsToExtend; + watchCompilerHost.configFileSpecs = configParseResult.configFileSpecs; + watchCompilerHost.configFileWildCardDirectories = configParseResult.wildcardDirectories; + createWatch(watchCompilerHost); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { - createWatch({ - system: sys, - beforeProgramCreate: enableStatistics, - afterProgramCreate: createProgramCompilerWithBuilderState(), - rootFiles, - options - }); + const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfFilesAndCompilerOptions; + watchCompilerHost.rootFiles = rootFiles; + watchCompilerHost.options = options; + createWatch(watchCompilerHost); } function compileProgram(program: Program): ExitStatus { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0e62c2d8cea..6cfe8c03e9a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3306,14 +3306,14 @@ namespace ts { const carriageReturnLineFeed = "\r\n"; const lineFeed = "\n"; - export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: { newLine: string }): string { + export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, getNewLine?: () => string): string { switch (options.newLine) { case NewLineKind.CarriageReturnLineFeed: return carriageReturnLineFeed; case NewLineKind.LineFeed: return lineFeed; } - return system ? system.newLine : sys ? sys.newLine : carriageReturnLineFeed; + return getNewLine ? getNewLine() : sys ? sys.newLine : carriageReturnLineFeed; } /** diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index c2ab3aae591..b5128be552e 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -161,7 +161,7 @@ namespace ts { }; } - export interface WatchHost { + export interface WatchCompilerHost { /** FS system to use */ system: System; @@ -170,14 +170,18 @@ namespace ts { /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; - /** Optional module name resolver */ + // Sub set of compiler host methods to read and generate new program + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + + /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options */ - export interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + export interface WatchCompilerHostOfFilesAndCompilerOptions extends WatchCompilerHost { /** root files to use to generate program */ rootFiles: string[]; @@ -188,7 +192,7 @@ namespace ts { /** * Host to create watch with config file */ - export interface WatchOfConfigFileHost extends WatchHost { + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { /** Name of the config file to compile */ configFileName: string; @@ -199,11 +203,11 @@ namespace ts { onConfigFileDiagnostic(diagnostic: Diagnostic): void; } - /*@internal*/ /** * Host to create watch with config file that is already parsed (from tsc) */ - export interface WatchOfConfigFileHost extends WatchHost { + /*@internal*/ + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { rootFiles?: string[]; options?: CompilerOptions; optionsToExtend?: CompilerOptions; @@ -233,40 +237,49 @@ namespace ts { updateRootFileNames(fileNames: string[]): void; } + /** + * Creates the watch compiler host that can be extended with config file or root file names and options host + */ + /*@internal*/ + export function createWatchCompilerHost(system = sys, reportDiagnostic?: DiagnosticReporter): WatchCompilerHost { + return { + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getNewLine: () => system.newLine, + system, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) + }; + } + /** * Create the watched program for config file */ - export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - return createWatch({ - system, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), - onConfigFileDiagnostic: reportDiagnostic || createDiagnosticReporter(system), - configFileName, - optionsToExtend - }); + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + const host = createWatchCompilerHost(system) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + host.configFileName = configFileName; + host.optionsToExtend = optionsToExtend; + return createWatch(host); } /** * Create the watched program for root files and compiler options */ export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { - return createWatch({ - system, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), - rootFiles, - options - }); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + host.rootFiles = rootFiles; + host.options = options; + return createWatch(host); } /** * Creates the watch from the host for root files and compiler options */ - export function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - export function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; - export function createWatch(host: WatchOfFilesAndCompilerOptionsHost | WatchOfConfigFileHost): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -284,10 +297,10 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; - const beforeProgramCreate: WatchHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; - const afterProgramCreate: WatchHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; - let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchOfConfigFileHost; + const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchCompilerHostOfConfigFile; + const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; + const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchCompilerHostOfConfigFile; // From tsc we want to get already parsed result and hence check for rootFileNames const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; @@ -296,7 +309,7 @@ namespace ts { } const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(system.newLine); } : noop; + const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(newLine); } : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; @@ -308,8 +321,9 @@ namespace ts { const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); const realpath = system.realpath && ((path: string) => system.realpath(path)); const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - let newLine = getNewLineCharacter(compilerOptions, system); + const useCaseSensitiveFileNames = memoize(() => host.useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames()); + let newLine = updateNewLine(); const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost @@ -319,7 +333,7 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: notImplemented, getCurrentDirectory, - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, @@ -370,7 +384,7 @@ namespace ts { writeLog(`Synchronizing program`); if (hasChangedCompilerOptions) { - newLine = getNewLineCharacter(compilerOptions, system); + newLine = updateNewLine(); if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) { resolutionCache.clear(); } @@ -423,6 +437,10 @@ namespace ts { scheduleProgramUpdate(); } + function updateNewLine() { + return getNewLineCharacter(compilerOptions, () => host.getNewLine()); + } + function toPath(fileName: string) { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } diff --git a/src/server/project.ts b/src/server/project.ts index 800e93f1949..40ddaeb2a19 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -266,7 +266,7 @@ namespace ts.server { } getNewLine() { - return this.directoryStructureHost.newLine; + return this.projectService.host.newLine; } getProjectVersion() { diff --git a/src/services/services.ts b/src/services/services.ts index 3c39affcacb..b9a50758326 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1255,7 +1255,7 @@ namespace ts { getCancellationToken: () => cancellationToken, getCanonicalFileName, useCaseSensitiveFileNames: () => useCaseSensitivefileNames, - getNewLine: () => getNewLineCharacter(newSettings, { newLine: getNewLineOrDefaultFromHost(host) }), + getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: noop, getCurrentDirectory: () => currentDirectory, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index eb9a788ac7f..f0f9318e687 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2729,9 +2729,7 @@ declare namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ interface DirectoryStructureHost { - newLine: string; useCaseSensitiveFileNames: boolean; - write(s: string): void; readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; @@ -2743,7 +2741,9 @@ declare namespace ts { exit(exitCode?: number): void; } interface System extends DirectoryStructureHost { + newLine: string; args: string[]; + write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 13b5b17f50f..5026237f30e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2729,9 +2729,7 @@ declare namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ interface DirectoryStructureHost { - newLine: string; useCaseSensitiveFileNames: boolean; - write(s: string): void; readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; @@ -2743,7 +2741,9 @@ declare namespace ts { exit(exitCode?: number): void; } interface System extends DirectoryStructureHost { + newLine: string; args: string[]; + write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that @@ -3820,20 +3820,22 @@ declare namespace ts { * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files */ function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; - interface WatchHost { + interface WatchCompilerHost { /** FS system to use */ system: System; /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; - /** Optional module name resolver */ + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options */ - interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + interface WatchCompilerHostOfFilesAndCompilerOptions extends WatchCompilerHost { /** root files to use to generate program */ rootFiles: string[]; /** Compiler options */ @@ -3842,7 +3844,7 @@ declare namespace ts { /** * Host to create watch with config file */ - interface WatchOfConfigFileHost extends WatchHost { + interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ @@ -3876,11 +3878,11 @@ declare namespace ts { /** * Creates the watch from the host for root files and compiler options */ - function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; + function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; } declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; From 43c2610a69748a875b08ca3a3c9e3e9cdece3b83 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 16:34:17 -0800 Subject: [PATCH 017/101] More functions moved from system to WatchCompilerHost --- src/compiler/core.ts | 219 +--------------- src/compiler/program.ts | 1 - src/compiler/resolutionCache.ts | 11 +- src/compiler/sys.ts | 30 +-- src/compiler/tsc.ts | 2 +- src/compiler/types.ts | 1 - src/compiler/watch.ts | 187 ++++++++++---- src/compiler/watchUtilities.ts | 241 ++++++++++++++++++ .../unittests/reuseProgramStructure.ts | 2 +- src/server/editorServices.ts | 2 +- src/server/project.ts | 11 +- src/server/scriptInfo.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 30 +-- tests/baselines/reference/api/typescript.d.ts | 72 ++++-- 14 files changed, 478 insertions(+), 333 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e1443215c91..b6472987398 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1398,6 +1398,9 @@ namespace ts { /** Returns its argument. */ export function identity(x: T) { return x; } + /** Returns lower case string */ + export function toLowerCase(x: string) { return x.toLowerCase(); } + /** Throws an error because a function is not implemented. */ export function notImplemented(): never { throw new Error("Not implemented"); @@ -2877,9 +2880,7 @@ namespace ts { export type GetCanonicalFileName = (fileName: string) => string; export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { - return useCaseSensitiveFileNames - ? ((fileName) => fileName) - : ((fileName) => fileName.toLowerCase()); + return useCaseSensitiveFileNames ? identity : toLowerCase; } /** @@ -3000,215 +3001,7 @@ namespace ts { export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty - export interface FileAndDirectoryExistence { - fileExists: boolean; - directoryExists: boolean; - } - - export interface CachedDirectoryStructureHost extends DirectoryStructureHost { - /** Returns the queried result for the file exists and directory exists if at all it was done */ - addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; - addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; - clearCache(): void; - } - - interface MutableFileSystemEntries { - readonly files: string[]; - readonly directories: string[]; - } - - export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost { - const cachedReadDirectoryResult = createMap(); - const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - return { - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - readFile: (path, encoding) => host.readFile(path, encoding), - writeFile, - fileExists, - directoryExists, - createDirectory, - getCurrentDirectory, - getDirectories, - readDirectory, - addOrDeleteFileOrDirectory, - addOrDeleteFile, - clearCache, - exit: code => host.exit(code) - }; - - function toPath(fileName: string) { - return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); - } - - function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { - return cachedReadDirectoryResult.get(rootDirPath); - } - - function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { - return getCachedFileSystemEntries(getDirectoryPath(path)); - } - - function getBaseNameOfFileName(fileName: string) { - return getBaseFileName(normalizePath(fileName)); - } - - function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { - const resultFromHost: MutableFileSystemEntries = { - files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], - directories: host.getDirectories(rootDir) || [] - }; - - cachedReadDirectoryResult.set(rootDirPath, resultFromHost); - return resultFromHost; - } - - /** - * If the readDirectory result was already cached, it returns that - * Otherwise gets result from host and caches it. - * The host request is done under try catch block to avoid caching incorrect result - */ - function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { - const cachedResult = getCachedFileSystemEntries(rootDirPath); - if (cachedResult) { - return cachedResult; - } - - try { - return createCachedFileSystemEntries(rootDir, rootDirPath); - } - catch (_e) { - // If there is exception to read directories, dont cache the result and direct the calls to host - Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); - return undefined; - } - } - - function fileNameEqual(name1: string, name2: string) { - return getCanonicalFileName(name1) === getCanonicalFileName(name2); - } - - function hasEntry(entries: ReadonlyArray, name: string) { - return some(entries, file => fileNameEqual(file, name)); - } - - function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { - if (hasEntry(entries, baseName)) { - if (!isValid) { - return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); - } - } - else if (isValid) { - return entries.push(baseName); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { - const path = toPath(fileName); - const result = getCachedFileSystemEntriesForBaseDir(path); - if (result) { - updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); - } - return host.writeFile(fileName, data, writeByteOrderMark); - } - - function fileExists(fileName: string): boolean { - const path = toPath(fileName); - const result = getCachedFileSystemEntriesForBaseDir(path); - return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || - host.fileExists(fileName); - } - - function directoryExists(dirPath: string): boolean { - const path = toPath(dirPath); - return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); - } - - function createDirectory(dirPath: string) { - const path = toPath(dirPath); - const result = getCachedFileSystemEntriesForBaseDir(path); - const baseFileName = getBaseNameOfFileName(dirPath); - if (result) { - updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); - } - host.createDirectory(dirPath); - } - - function getDirectories(rootDir: string): string[] { - const rootDirPath = toPath(rootDir); - const result = tryReadDirectory(rootDir, rootDirPath); - if (result) { - return result.directories.slice(); - } - return host.getDirectories(rootDir); - } - - function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { - const rootDirPath = toPath(rootDir); - const result = tryReadDirectory(rootDir, rootDirPath); - if (result) { - return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries); - } - return host.readDirectory(rootDir, extensions, excludes, includes, depth); - - function getFileSystemEntries(dir: string) { - const path = toPath(dir); - if (path === rootDirPath) { - return result; - } - return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); - } - } - - function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { - const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); - if (existingResult) { - // Just clear the cache for now - // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated - clearCache(); - } - else { - // This was earlier a file (hence not in cached directory contents) - // or we never cached the directory containing it - const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); - if (parentResult) { - const baseName = getBaseNameOfFileName(fileOrDirectory); - if (parentResult) { - const fsQueryResult: FileAndDirectoryExistence = { - fileExists: host.fileExists(fileOrDirectoryPath), - directoryExists: host.directoryExists(fileOrDirectoryPath) - }; - if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { - // Folder added or removed, clear the cache instead of updating the folder and its structure - clearCache(); - } - else { - // No need to update the directory structure, just files - updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); - } - return fsQueryResult; - } - } - } - } - - function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { - if (eventKind === FileWatcherEventKind.Changed) { - return; - } - - const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); - if (parentResult) { - updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); - } - } - - function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { - updateFileSystemEntry(parentResult.files, baseName, fileExists); - } - - function clearCache() { - cachedReadDirectoryResult.clear(); - } + export function getBoundFunction(method: T | undefined, methodOf: {}): T | undefined { + return method && method.bind(methodOf); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9560859c7ce..9960b501d78 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -191,7 +191,6 @@ namespace ts { readFile: fileName => sys.readFile(fileName), trace: (s: string) => sys.write(s + newLine), directoryExists: directoryName => sys.directoryExists(directoryName), - getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "", getDirectories: (path: string) => sys.getDirectories(path), realpath }; diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 1c96dc21f08..92eea4140da 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -47,7 +47,7 @@ namespace ts { onInvalidatedResolution(): void; watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onChangedAutomaticTypeDirectiveNames(): void; - getCachedDirectoryStructureHost?(): CachedDirectoryStructureHost; + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; projectName?: string; getGlobalCache?(): string | undefined; writeLog(s: string): void; @@ -87,6 +87,7 @@ namespace ts { const perDirectoryResolvedTypeReferenceDirectives = createMap>(); const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); /** * These are the extensions that failed lookup files will have by default, @@ -467,9 +468,9 @@ namespace ts { function createDirectoryWatcher(directory: string, dirPath: Path) { return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (resolutionHost.getCachedDirectoryStructureHost) { + if (cachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // If the files are added to project root or node_modules directory, always run through the invalidation process @@ -596,9 +597,9 @@ namespace ts { // Create new watch and recursive info return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (resolutionHost.getCachedDirectoryStructureHost) { + if (cachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // For now just recompile diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c327bd608de..f744b9ab0eb 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -30,27 +30,14 @@ namespace ts { mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - export interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - - export interface System extends DirectoryStructureHost { - newLine: string; + export interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -58,7 +45,13 @@ namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -66,6 +59,7 @@ namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; /*@internal*/ getEnvironmentVariable(name: string): string; /*@internal*/ tryEnableSourceMapsForHost?(): void; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 9a7e98d954c..daccfca33b8 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -110,7 +110,7 @@ namespace ts { const commandLineOptions = commandLine.options; if (configFileName) { - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic); + const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, sys, reportDiagnostic); udpateReportDiagnostic(configParseResult.options); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0167bd50116..4b3c15df706 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4327,7 +4327,6 @@ namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index b5128be552e..0bd0fc499b7 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -33,34 +33,52 @@ namespace ts { }; } + /** + * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors + */ + /*@internal*/ + export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { + getCurrentDirectory(): string; + } + + /** Parses config file using System interface */ + /*@internal*/ + export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter) { + const host: ParseConfigFileHost = system; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(sys, reportDiagnostic, diagnostic); + const result = parseConfigFile(configFileName, optionsToExtend, host); + host.onConfigFileDiagnostic = undefined; + host.onUnRecoverableConfigFileDiagnostic = undefined; + return result; + } + /** * Reads the config file, reports errors if any and exits if the config file cannot be found */ /*@internal*/ - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { let configFileText: string; try { - configFileText = system.readFile(configFileName); + configFileText = host.readFile(configFileName); } catch (e) { const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportDiagnostic(error); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } if (!configFileText) { const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostic(error); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } const result = parseJsonText(configFileName, configFileText); - result.parseDiagnostics.forEach(reportDiagnostic); + result.parseDiagnostics.forEach(diagnostic => host.onConfigFileDiagnostic(diagnostic)); - const cwd = system.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - configParseResult.errors.forEach(reportDiagnostic); + const cwd = host.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); + configParseResult.errors.forEach(diagnostic => host.onConfigFileDiagnostic(diagnostic)); return configParseResult; } @@ -90,7 +108,7 @@ namespace ts { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: system.createHash ? system.createHash.bind(system) : identity + computeHash: getBoundFunction(system.createHash, system) || identity }); return (host: DirectoryStructureHost, program: Program) => { @@ -173,8 +191,30 @@ namespace ts { // Sub set of compiler host methods to read and generate new program useCaseSensitiveFileNames(): boolean; getNewLine(): string; + getCurrentDirectory(): string; - /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + /** Symbol links resolution */ + realpath?(path: string): string; + + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } @@ -189,18 +229,36 @@ namespace ts { options: CompilerOptions; } + /** + * Reports config file diagnostics + */ + export interface ConfigFileDiagnosticsReporter { + /** + * Reports the diagnostics in reading/writing or parsing of the config file + */ + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + + /** + * Reports unrecoverable error when parsing config file + */ + onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + /** * Host to create watch with config file */ - export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ optionsToExtend?: CompilerOptions; - // Reports errors in the config file - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } /** @@ -241,21 +299,39 @@ namespace ts { * Creates the watch compiler host that can be extended with config file or root file names and options host */ /*@internal*/ - export function createWatchCompilerHost(system = sys, reportDiagnostic?: DiagnosticReporter): WatchCompilerHost { + export function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { return { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, + getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + fileExists: getBoundFunction(system.fileExists, system), + readFile: getBoundFunction(system.readFile, system), + directoryExists: getBoundFunction(system.directoryExists, system), + getDirectories: getBoundFunction(system.getDirectories, system), + readDirectory: getBoundFunction(system.readDirectory, system), + realpath: getBoundFunction(system.realpath, system), system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; } + /** + * Report error and exit + */ + /*@internal*/ + export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + reportDiagnostic(diagnostic); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + /** * Create the watched program for config file */ export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - const host = createWatchCompilerHost(system) as WatchCompilerHostOfConfigFile; - host.onConfigFileDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); host.configFileName = configFileName; host.optionsToExtend = optionsToExtend; return createWatch(host); @@ -279,7 +355,7 @@ namespace ts { * Creates the watch from the host for config file */ export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -297,13 +373,30 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchCompilerHostOfConfigFile; - const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; - const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; - let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchCompilerHostOfConfigFile; + const system = host.system; + const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); + const currentDirectory = host.getCurrentDirectory(); + const getCurrentDirectory = () => currentDirectory; + const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); + const readFile = getBoundFunction(host.readFile, host); + const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; + const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = getBoundFunction(host.beforeProgramCreate, host) || noop; + const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = getBoundFunction(host.afterProgramCreate, host) || noop; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; + + const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); + const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; + const parseConfigFileHost: ParseConfigFileHost = { + useCaseSensitiveFileNames, + readDirectory: getBoundFunction(directoryStructureHost.readDirectory, directoryStructureHost), + fileExists: getBoundFunction(directoryStructureHost.fileExists, directoryStructureHost), + readFile, + getCurrentDirectory, + onConfigFileDiagnostic, + onUnRecoverableConfigFileDiagnostic: getBoundFunction(host.onUnRecoverableConfigFileDiagnostic, host) + }; // From tsc we want to get already parsed result and hence check for rootFileNames - const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName && !rootFileNames) { parseConfigFile(); } @@ -318,11 +411,7 @@ namespace ts { watchFile(system, configFileName, scheduleProgramReload, writeLog); } - const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); - const realpath = system.realpath && ((path: string) => system.realpath(path)); - const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); - const useCaseSensitiveFileNames = memoize(() => host.useCaseSensitiveFileNames()); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); let newLine = updateNewLine(); const compilerHost: CompilerHost & ResolutionCacheHost = { @@ -333,23 +422,22 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: notImplemented, getCurrentDirectory, - useCaseSensitiveFileNames, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile: fileName => system.readFile(fileName), + readFile, trace: s => system.write(s + newLine), - directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: path => directoryStructureHost.getDirectories(path), - realpath, + directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), + getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), + realpath: getBoundFunction(host.realpath, host), onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation: watchDirectory, watchTypeRootsDirectory: watchDirectory, - getCachedDirectoryStructureHost, + getCachedDirectoryStructureHost: () => cachedDirectoryStructureHost, onInvalidatedResolution: scheduleProgramUpdate, onChangedAutomaticTypeDirectiveNames: () => { hasChangedAutomaticTypeDirectiveNames = true; @@ -359,8 +447,8 @@ namespace ts { }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? - getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : - getCurrentDirectory(), + getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : + currentDirectory, /*logChangesWhenResolvingModule*/ false ); // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names @@ -442,7 +530,7 @@ namespace ts { } function toPath(fileName: string) { - return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } function fileExists(fileName: string) { @@ -505,7 +593,7 @@ namespace ts { let text: string; try { performance.mark("beforeIORead"); - text = system.readFile(fileName, compilerOptions.charset); + text = host.readFile(fileName, compilerOptions.charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -601,7 +689,7 @@ namespace ts { } function reloadFileNamesFromConfigFile() { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } @@ -615,8 +703,9 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); reloadLevel = ConfigFileProgramReloadLevel.None; - const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; - cachedHost.clearCache(); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.clearCache(); + } parseConfigFile(); hasChangedCompilerOptions = true; synchronizeProgram(); @@ -626,7 +715,7 @@ namespace ts { } function parseConfigFile() { - const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, directoryStructureHost as CachedDirectoryStructureHost, onConfigFileDiagnostic); + const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost); rootFileNames = configParseResult.fileNames; compilerOptions = configParseResult.options; configFileSpecs = configParseResult.configFileSpecs; @@ -662,8 +751,8 @@ namespace ts { } function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - if (configFileName) { - (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, path, eventKind); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind); } } @@ -712,7 +801,7 @@ namespace ts { const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - const result = (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + const result = cachedDirectoryStructureHost && cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); // Instead of deleting the file, mark it as changed instead // Many times node calls add/remove/file when watching directories recursively diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 0cf38f372d9..a1d22c20ac1 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -2,6 +2,247 @@ /* @internal */ namespace ts { + /** + * Partial interface of the System thats needed to support the caching of directory structure + */ + export interface DirectoryStructureHost { + fileExists(path: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + + directoryExists?(path: string): boolean; + getDirectories?(path: string): string[]; + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + createDirectory?(path: string): void; + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + } + + interface FileAndDirectoryExistence { + fileExists: boolean; + directoryExists: boolean; + } + + export interface CachedDirectoryStructureHost extends DirectoryStructureHost { + useCaseSensitiveFileNames: boolean; + + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + /** Returns the queried result for the file exists and directory exists if at all it was done */ + addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; + addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; + clearCache(): void; + } + + interface MutableFileSystemEntries { + readonly files: string[]; + readonly directories: string[]; + } + + export function createCachedDirectoryStructureHost(host: DirectoryStructureHost, currentDirectory: string, useCaseSensitiveFileNames: boolean): CachedDirectoryStructureHost | undefined { + if (!host.getDirectories || !host.readDirectory) { + return undefined; + } + + const cachedReadDirectoryResult = createMap(); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return { + useCaseSensitiveFileNames, + fileExists, + readFile: getBoundFunction(host.readFile, host), + directoryExists: host.directoryExists && directoryExists, + getDirectories, + readDirectory, + createDirectory, + writeFile, + addOrDeleteFileOrDirectory, + addOrDeleteFile, + clearCache + }; + + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + + function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { + return cachedReadDirectoryResult.get(rootDirPath); + } + + function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { + return getCachedFileSystemEntries(getDirectoryPath(path)); + } + + function getBaseNameOfFileName(fileName: string) { + return getBaseFileName(normalizePath(fileName)); + } + + function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { + const resultFromHost: MutableFileSystemEntries = { + files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], + directories: host.getDirectories(rootDir) || [] + }; + + cachedReadDirectoryResult.set(rootDirPath, resultFromHost); + return resultFromHost; + } + + /** + * If the readDirectory result was already cached, it returns that + * Otherwise gets result from host and caches it. + * The host request is done under try catch block to avoid caching incorrect result + */ + function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { + const cachedResult = getCachedFileSystemEntries(rootDirPath); + if (cachedResult) { + return cachedResult; + } + + try { + return createCachedFileSystemEntries(rootDir, rootDirPath); + } + catch (_e) { + // If there is exception to read directories, dont cache the result and direct the calls to host + Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); + return undefined; + } + } + + function fileNameEqual(name1: string, name2: string) { + return getCanonicalFileName(name1) === getCanonicalFileName(name2); + } + + function hasEntry(entries: ReadonlyArray, name: string) { + return some(entries, file => fileNameEqual(file, name)); + } + + function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { + if (hasEntry(entries, baseName)) { + if (!isValid) { + return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); + } + } + else if (isValid) { + return entries.push(baseName); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + const path = toPath(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + if (result) { + updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); + } + return host.writeFile(fileName, data, writeByteOrderMark); + } + + function fileExists(fileName: string): boolean { + const path = toPath(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || + host.fileExists(fileName); + } + + function directoryExists(dirPath: string): boolean { + const path = toPath(dirPath); + return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); + } + + function createDirectory(dirPath: string) { + const path = toPath(dirPath); + const result = getCachedFileSystemEntriesForBaseDir(path); + const baseFileName = getBaseNameOfFileName(dirPath); + if (result) { + updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); + } + host.createDirectory(dirPath); + } + + function getDirectories(rootDir: string): string[] { + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return result.directories.slice(); + } + return host.getDirectories(rootDir); + } + + function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries); + } + return host.readDirectory(rootDir, extensions, excludes, includes, depth); + + function getFileSystemEntries(dir: string) { + const path = toPath(dir); + if (path === rootDirPath) { + return result; + } + return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); + } + } + + function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { + const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); + if (existingResult) { + // Just clear the cache for now + // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated + clearCache(); + return undefined; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); + if (!parentResult) { + return undefined; + } + + // This was earlier a file (hence not in cached directory contents) + // or we never cached the directory containing it + + if (!host.directoryExists) { + // Since host doesnt support directory exists, clear the cache as otherwise it might not be same + clearCache(); + return undefined; + } + + const baseName = getBaseNameOfFileName(fileOrDirectory); + const fsQueryResult: FileAndDirectoryExistence = { + fileExists: host.fileExists(fileOrDirectoryPath), + directoryExists: host.directoryExists(fileOrDirectoryPath) + }; + if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { + // Folder added or removed, clear the cache instead of updating the folder and its structure + clearCache(); + } + else { + // No need to update the directory structure, just files + updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); + } + return fsQueryResult; + + } + + function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { + if (eventKind === FileWatcherEventKind.Changed) { + return; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); + if (parentResult) { + updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); + } + } + + function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { + updateFileSystemEntry(parentResult.files, baseName, fileExists); + } + + function clearCache() { + cachedReadDirectoryResult.clear(); + } + } + export enum ConfigFileProgramReloadLevel { None, /** Update the file name list from the disk */ diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 92e40cc8f39..5ff1f08332c 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -903,7 +903,7 @@ namespace ts { function verifyProgramWithConfigFile(system: System, configFileName: string) { const program = createWatchOfConfigFile(configFileName, {}, system).getExistingProgram(); - const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); + const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f..bafe90a59cb 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1497,7 +1497,7 @@ namespace ts.server { } private createConfiguredProject(configFileName: NormalizedPath) { - const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host); + const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames); const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedDirectoryStructureHost); this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); diff --git a/src/server/project.ts b/src/server/project.ts index 40ddaeb2a19..9ae1b1d0b6e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -334,7 +334,7 @@ namespace ts.server { } useCaseSensitiveFileNames() { - return this.directoryStructureHost.useCaseSensitiveFileNames; + return this.projectService.host.useCaseSensitiveFileNames; } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { @@ -342,7 +342,7 @@ namespace ts.server { } readFile(fileName: string): string | undefined { - return this.directoryStructureHost.readFile(fileName); + return this.projectService.host.readFile(fileName); } fileExists(file: string): boolean { @@ -368,6 +368,11 @@ namespace ts.server { return this.directoryStructureHost.getDirectories(path); } + /*@internal*/ + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost { + return undefined; + } + /*@internal*/ toPath(fileName: string) { return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName); @@ -877,7 +882,7 @@ namespace ts.server { missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, missingFilePath, eventKind); + this.getCachedDirectoryStructureHost().addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index f800a1117d0..329d403195d 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -313,7 +313,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + p.getCachedDirectoryStructureHost().addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index f0f9318e687..64f595664b3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2477,7 +2477,6 @@ declare namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -2725,26 +2724,14 @@ declare namespace ts { callback: FileWatcherCallback; mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - interface System extends DirectoryStructureHost { - newLine: string; + interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -2752,7 +2739,13 @@ declare namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -2760,6 +2753,7 @@ declare namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout?(timeoutId: any): void; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5026237f30e..c74fc46d60e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2477,7 +2477,6 @@ declare namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -2725,26 +2724,14 @@ declare namespace ts { callback: FileWatcherCallback; mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - interface System extends DirectoryStructureHost { - newLine: string; + interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -2752,7 +2739,13 @@ declare namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -2760,6 +2753,7 @@ declare namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout?(timeoutId: any): void; @@ -3829,7 +3823,26 @@ declare namespace ts { afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; - /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ + getCurrentDirectory(): string; + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + /** Symbol links resolution */ + realpath?(path: string): string; + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -3841,15 +3854,32 @@ declare namespace ts { /** Compiler options */ options: CompilerOptions; } + /** + * Reports config file diagnostics + */ + interface ConfigFileDiagnosticsReporter { + /** + * Reports the diagnostics in reading/writing or parsing of the config file + */ + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Reports unrecoverable error when parsing config file + */ + onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + } /** * Host to create watch with config file */ - interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ optionsToExtend?: CompilerOptions; - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } interface Watch { /** Synchronize with host and get updated program */ From e694b9e3bae0deb094f52652bc1a94be85e7efef Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 17:50:14 -0800 Subject: [PATCH 018/101] Update the WatchCompilerHost creation --- src/compiler/tsc.ts | 17 ++++++----------- src/compiler/watch.ts | 36 ++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index daccfca33b8..43211313237 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -21,7 +21,7 @@ namespace ts { return diagnostic.messageText; } - let reportDiagnostic = createDiagnosticReporter(); + let reportDiagnostic = createDiagnosticReporter(sys); function udpateReportDiagnostic(options: CompilerOptions) { if (options.pretty) { reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true); @@ -150,33 +150,28 @@ namespace ts { return sys.exit(exitStatus); } - function createWatchCompilerHost(): WatchCompilerHost { - const watchCompilerHost = ts.createWatchCompilerHost(sys, reportDiagnostic); + function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost) { const compilerWithBuilderState = watchCompilerHost.afterProgramCreate; watchCompilerHost.beforeProgramCreate = enableStatistics; watchCompilerHost.afterProgramCreate = (host, program) => { compilerWithBuilderState(host, program); reportStatistics(program); }; - return watchCompilerHost; } function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) { - const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfConfigFile; - watchCompilerHost.onConfigFileDiagnostic = reportDiagnostic; + const watchCompilerHost = ts.createWatchCompilerHostOfConfigFile(configParseResult.options.configFilePath, optionsToExtend, sys, reportDiagnostic); + updateWatchCompilationHost(watchCompilerHost); watchCompilerHost.rootFiles = configParseResult.fileNames; watchCompilerHost.options = configParseResult.options; - watchCompilerHost.configFileName = configParseResult.options.configFilePath; - watchCompilerHost.optionsToExtend = optionsToExtend; watchCompilerHost.configFileSpecs = configParseResult.configFileSpecs; watchCompilerHost.configFileWildCardDirectories = configParseResult.wildcardDirectories; createWatch(watchCompilerHost); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { - const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfFilesAndCompilerOptions; - watchCompilerHost.rootFiles = rootFiles; - watchCompilerHost.options = options; + const watchCompilerHost = ts.createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, sys, reportDiagnostic); + updateWatchCompilationHost(watchCompilerHost); createWatch(watchCompilerHost); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0bd0fc499b7..2653be5e20d 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -15,7 +15,7 @@ namespace ts { * Create a function that reports error by writing to the system and handles the formating of the diagnostic */ /*@internal*/ - export function createDiagnosticReporter(system = sys, pretty?: boolean): DiagnosticReporter { + export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter { const host: FormatDiagnosticsHost = system === sys ? sysFormatDiagnosticsHost : { getCurrentDirectory: () => system.getCurrentDirectory(), getNewLine: () => system.newLine, @@ -266,6 +266,7 @@ namespace ts { */ /*@internal*/ export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + cachedDirectoryStructureHost?: CachedDirectoryStructureHost; rootFiles?: string[]; options?: CompilerOptions; optionsToExtend?: CompilerOptions; @@ -298,8 +299,7 @@ namespace ts { /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ - /*@internal*/ - export function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { + function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { return { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, @@ -325,26 +325,42 @@ namespace ts { } /** - * Create the watched program for config file + * Creates the watch compiler host from system for config file in watch mode */ - export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + /*@internal*/ + export function createWatchCompilerHostOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfConfigFile { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; host.onConfigFileDiagnostic = reportDiagnostic; host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); host.configFileName = configFileName; host.optionsToExtend = optionsToExtend; - return createWatch(host); + return host; + } + + /** + * Creates the watch compiler host from system for compiling root files and options in watch mode + */ + /*@internal*/ + export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + host.rootFiles = rootFiles; + host.options = options; + return host; + } + + /** + * Create the watched program for config file + */ + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + return createWatch(createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend, system, reportDiagnostic)); } /** * Create the watched program for root files and compiler options */ export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; - host.rootFiles = rootFiles; - host.options = options; - return createWatch(host); + return createWatch(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system, reportDiagnostic)); } /** From 8cc293635229b053632b52493988ec4ae781e812 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:09:10 -0800 Subject: [PATCH 019/101] Move watchFile and watchDirectory to WatchCompilerHost --- src/compiler/watch.ts | 17 +++++++---- src/compiler/watchUtilities.ts | 30 ++++++++++++------- src/harness/unittests/session.ts | 3 ++ src/server/types.ts | 4 ++- .../reference/api/tsserverlibrary.d.ts | 2 ++ tests/baselines/reference/api/typescript.d.ts | 4 +++ 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 2653be5e20d..210c1efe409 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -216,6 +216,11 @@ namespace ts { /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; } /** @@ -310,6 +315,8 @@ namespace ts { getDirectories: getBoundFunction(system.getDirectories, system), readDirectory: getBoundFunction(system.readDirectory, system), realpath: getBoundFunction(system.realpath, system), + watchFile: getBoundFunction(system.watchFile, system), + watchDirectory: getBoundFunction(system.watchDirectory, system), system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; @@ -424,7 +431,7 @@ namespace ts { const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; if (configFileName) { - watchFile(system, configFileName, scheduleProgramReload, writeLog); + watchFile(host, configFileName, scheduleProgramReload, writeLog); } const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -581,7 +588,7 @@ namespace ts { hostSourceFile.sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); + hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); } } else { @@ -594,7 +601,7 @@ namespace ts { let fileWatcher: FileWatcher; if (sourceFile) { sourceFile.version = "1"; - fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); + fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); sourceFilesCache.set(path, { sourceFile, version: 1, fileWatcher }); } else { @@ -773,11 +780,11 @@ namespace ts { } function watchDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { - return watchDirectoryWorker(system, directory, cb, flags, writeLog); + return watchDirectoryWorker(host, directory, cb, flags, writeLog); } function watchMissingFilePath(missingFilePath: Path) { - return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog); + return watchFilePath(host, missingFilePath, onMissingFileChange, missingFilePath, writeLog); } function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index a1d22c20ac1..bf5a42b7b08 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -323,53 +323,61 @@ namespace ts { } } - export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { + export interface WatchFileHost { + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + } + + export function addFileWatcher(host: WatchFileHost, file: string, cb: FileWatcherCallback): FileWatcher { return host.watchFile(file, cb); } - export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + export function addFileWatcherWithLogging(host: WatchFileHost, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb); } - export function addFileWatcherWithOnlyTriggerLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + export function addFileWatcherWithOnlyTriggerLogging(host: WatchFileHost, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb); } export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; - export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { + export function addFilePathWatcher(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path)); } - export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + export function addFilePathWatcherWithLogging(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb, path); } - export function addFilePathWatcherWithOnlyTriggerLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + export function addFilePathWatcherWithOnlyTriggerLogging(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb, path); } - export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { + export interface WatchDirectoryHost { + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + } + + export function addDirectoryWatcher(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; return host.watchDirectory(directory, cb, recursive); } - export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + export function addDirectoryWatcherWithLogging(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, directory, cb, flags); } - export function addDirectoryWatcherWithOnlyTriggerLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + export function addDirectoryWatcherWithOnlyTriggerLogging(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, directory, cb, flags); } type WatchCallback = (fileName: string, cbOptional1?: T, optional?: U) => void; - type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; - function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, logOnlyTrigger: boolean, host: System, file: string, cb: WatchCallback, optional?: U): FileWatcher { + type AddWatch = (host: H, file: string, cb: WatchCallback, optional?: U) => FileWatcher; + function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, logOnlyTrigger: boolean, host: H, file: string, cb: WatchCallback, optional?: U): FileWatcher { const info = `PathInfo: ${file}`; if (!logOnlyTrigger) { log(`${watcherCaption}Added: ${info}`); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 9912294d1d2..7d71d83c754 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -2,6 +2,7 @@ namespace ts.server { let lastWrittenToHost: string; + const noopFileWatcher: FileWatcher = { close: noop }; const mockHost: ServerHost = { args: [], newLine: "\n", @@ -24,6 +25,8 @@ namespace ts.server { setImmediate: () => 0, clearImmediate: noop, createHash: Harness.mockHash, + watchFile: () => noopFileWatcher, + watchDirectory: () => noopFileWatcher }; class TestSession extends Session { diff --git a/src/server/types.ts b/src/server/types.ts index 32132ed278b..93ffeeccff1 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -11,6 +11,8 @@ declare namespace ts.server { type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } }; export interface ServerHost extends System { + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout(timeoutId: any): void; setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; @@ -129,4 +131,4 @@ declare namespace ts.server { createDirectory(path: string): void; watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; } -} \ No newline at end of file +} diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 64f595664b3..ca399847583 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4747,6 +4747,8 @@ declare namespace ts.server { }; }; interface ServerHost extends System { + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout(timeoutId: any): void; setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c74fc46d60e..6f347efc46b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3844,6 +3844,10 @@ declare namespace ts { realpath?(path: string): string; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; } /** * Host to create watch with root files and options From abafddded23c4d02332ca9072b44c63ccde34e47 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:13:45 -0800 Subject: [PATCH 020/101] Move internal functions in the watch to separate namespace --- src/compiler/watch.ts | 118 ++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 210c1efe409..ac9ef086fbe 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -2,9 +2,8 @@ /// /// +/*@internal*/ namespace ts { - export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - const sysFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { getCurrentDirectory: () => sys.getCurrentDirectory(), getNewLine: () => sys.newLine, @@ -14,7 +13,6 @@ namespace ts { /** * Create a function that reports error by writing to the system and handles the formating of the diagnostic */ - /*@internal*/ export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter { const host: FormatDiagnosticsHost = system === sys ? sysFormatDiagnosticsHost : { getCurrentDirectory: () => system.getCurrentDirectory(), @@ -36,13 +34,11 @@ namespace ts { /** * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors */ - /*@internal*/ export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { getCurrentDirectory(): string; } /** Parses config file using System interface */ - /*@internal*/ export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter) { const host: ParseConfigFileHost = system; host.onConfigFileDiagnostic = reportDiagnostic; @@ -56,7 +52,6 @@ namespace ts { /** * Reads the config file, reports errors if any and exits if the config file cannot be found */ - /*@internal*/ export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { let configFileText: string; try { @@ -83,6 +78,62 @@ namespace ts { return configParseResult; } + /** + * Creates the watch compiler host that can be extended with config file or root file names and options host + */ + function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { + return { + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getNewLine: () => system.newLine, + getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + fileExists: getBoundFunction(system.fileExists, system), + readFile: getBoundFunction(system.readFile, system), + directoryExists: getBoundFunction(system.directoryExists, system), + getDirectories: getBoundFunction(system.getDirectories, system), + readDirectory: getBoundFunction(system.readDirectory, system), + realpath: getBoundFunction(system.realpath, system), + watchFile: getBoundFunction(system.watchFile, system), + watchDirectory: getBoundFunction(system.watchDirectory, system), + system, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) + }; + } + + /** + * Report error and exit + */ + export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + reportDiagnostic(diagnostic); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + /** + * Creates the watch compiler host from system for config file in watch mode + */ + export function createWatchCompilerHostOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfConfigFile { + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); + host.configFileName = configFileName; + host.optionsToExtend = optionsToExtend; + return host; + } + + /** + * Creates the watch compiler host from system for compiling root files and options in watch mode + */ + export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + host.rootFiles = rootFiles; + host.options = options; + return host; + } +} + +namespace ts { + export type DiagnosticReporter = (diagnostic: Diagnostic) => void; + /** * Writes emitted files, source files depending on options */ @@ -301,61 +352,6 @@ namespace ts { updateRootFileNames(fileNames: string[]): void; } - /** - * Creates the watch compiler host that can be extended with config file or root file names and options host - */ - function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { - return { - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getNewLine: () => system.newLine, - getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), - fileExists: getBoundFunction(system.fileExists, system), - readFile: getBoundFunction(system.readFile, system), - directoryExists: getBoundFunction(system.directoryExists, system), - getDirectories: getBoundFunction(system.getDirectories, system), - readDirectory: getBoundFunction(system.readDirectory, system), - realpath: getBoundFunction(system.realpath, system), - watchFile: getBoundFunction(system.watchFile, system), - watchDirectory: getBoundFunction(system.watchDirectory, system), - system, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) - }; - } - - /** - * Report error and exit - */ - /*@internal*/ - export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { - reportDiagnostic(diagnostic); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - /** - * Creates the watch compiler host from system for config file in watch mode - */ - /*@internal*/ - export function createWatchCompilerHostOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfConfigFile { - reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; - host.onConfigFileDiagnostic = reportDiagnostic; - host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); - host.configFileName = configFileName; - host.optionsToExtend = optionsToExtend; - return host; - } - - /** - * Creates the watch compiler host from system for compiling root files and options in watch mode - */ - /*@internal*/ - export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; - host.rootFiles = rootFiles; - host.options = options; - return host; - } - /** * Create the watched program for config file */ From 77e67311aad296b934b41acfec6b32542402b6c5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:37:57 -0800 Subject: [PATCH 021/101] Handle setTimeout, clearTimeout, clearScreen and report watch Diagnostics --- src/compiler/watch.ts | 49 +++++++++++-------- tests/baselines/reference/api/typescript.d.ts | 6 +++ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ac9ef086fbe..fb0af18ef5b 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -94,15 +94,25 @@ namespace ts { realpath: getBoundFunction(system.realpath, system), watchFile: getBoundFunction(system.watchFile, system), watchDirectory: getBoundFunction(system.watchDirectory, system), + setTimeout: getBoundFunction(system.setTimeout, system), + clearTimeout: getBoundFunction(system.clearTimeout, system), + onWatchStatusChange, system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; + + function onWatchStatusChange(diagnostic: Diagnostic, newLine: string) { + if (system.clearScreen && diagnostic.code !== Diagnostics.Compilation_complete_Watching_for_file_changes.code) { + system.clearScreen(); + } + system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); + } } /** * Report error and exit */ - export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { reportDiagnostic(diagnostic); system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -238,6 +248,8 @@ namespace ts { beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; // Sub set of compiler host methods to read and generate new program useCaseSensitiveFileNames(): boolean; @@ -272,6 +284,10 @@ namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; } /** @@ -399,8 +415,8 @@ namespace ts { const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); const readFile = getBoundFunction(host.readFile, host); const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; - const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = getBoundFunction(host.beforeProgramCreate, host) || noop; - const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = getBoundFunction(host.afterProgramCreate, host) || noop; + const beforeProgramCreate = getBoundFunction(host.beforeProgramCreate, host) || noop; + const afterProgramCreate = getBoundFunction(host.afterProgramCreate, host) || noop; let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); @@ -476,8 +492,7 @@ namespace ts { resolutionCache.resolveModuleNames.bind(resolutionCache); compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); - clearHostScreen(); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode)); + reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); synchronizeProgram(); // Update the wild card directory watch @@ -534,7 +549,7 @@ namespace ts { } afterProgramCreate(directoryStructureHost, program); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + reportWatchDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes); return program; } @@ -660,22 +675,24 @@ namespace ts { } } - function reportWatchDiagnostic(diagnostic: Diagnostic) { - system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); + function reportWatchDiagnostic(message: DiagnosticMessage) { + if (host.onWatchStatusChange) { + host.onWatchStatusChange(createCompilerDiagnostic(message), newLine); + } } // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch // operations (such as saving all modified files in an editor) a chance to complete before we kick // off a new compilation. function scheduleProgramUpdate() { - if (!system.setTimeout || !system.clearTimeout) { + if (!host.setTimeout || !host.clearTimeout) { return; } if (timerToUpdateProgram) { - system.clearTimeout(timerToUpdateProgram); + host.clearTimeout(timerToUpdateProgram); } - timerToUpdateProgram = system.setTimeout(updateProgram, 250); + timerToUpdateProgram = host.setTimeout(updateProgram, 250); } function scheduleProgramReload() { @@ -684,17 +701,9 @@ namespace ts { scheduleProgramUpdate(); } - function clearHostScreen() { - if (system.clearScreen) { - system.clearScreen(); - } - } - function updateProgram() { - clearHostScreen(); - timerToUpdateProgram = undefined; - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); switch (reloadLevel) { case ConfigFileProgramReloadLevel.Partial: diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6f347efc46b..7031f305008 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3821,6 +3821,8 @@ declare namespace ts { beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; @@ -3848,6 +3850,10 @@ declare namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; } /** * Host to create watch with root files and options From d22ba5e9650d2434cb5200171ee45436e38c32de Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:50:50 -0800 Subject: [PATCH 022/101] Move the system.write to trace on WatchCompilerHost --- src/compiler/watch.ts | 10 +++++++--- tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index fb0af18ef5b..f23f94d62c2 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -96,6 +96,7 @@ namespace ts { watchDirectory: getBoundFunction(system.watchDirectory, system), setTimeout: getBoundFunction(system.setTimeout, system), clearTimeout: getBoundFunction(system.clearTimeout, system), + trace: getBoundFunction(system.write, system), onWatchStatusChange, system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) @@ -276,6 +277,8 @@ namespace ts { /** Symbol links resolution */ realpath?(path: string): string; + /** If provided would be used to write log about compilation */ + trace?(s: string): void; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; @@ -436,8 +439,9 @@ namespace ts { parseConfigFile(); } - const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(newLine); } : noop; + const trace = host.trace && ((s: string) => { host.trace(s + newLine) }); + const loggingEnabled = trace && (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics); + const writeLog = loggingEnabled ? trace : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; @@ -462,7 +466,7 @@ namespace ts { getNewLine: () => newLine, fileExists, readFile, - trace: s => system.write(s + newLine), + trace, directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), realpath: getBoundFunction(host.realpath, host), diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7031f305008..c676aa951ac 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3844,6 +3844,8 @@ declare namespace ts { readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; /** Symbol links resolution */ realpath?(path: string): string; + /** If provided would be used to write log about compilation */ + trace?(s: string): void; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; /** Used to watch changes in source files, missing files needed to update the program or config file */ From c9a407e5533475463f15fb268e648dd10c5e9b0d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 19:00:06 -0800 Subject: [PATCH 023/101] Add getDefaultLibLocation and getDefaultLibFileName and remove system from WatchCompilerHost --- src/compiler/watch.ts | 25 +++++++++---------- tests/baselines/reference/api/typescript.d.ts | 4 +-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index f23f94d62c2..67caf3c79ca 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -86,6 +86,8 @@ namespace ts { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), fileExists: getBoundFunction(system.fileExists, system), readFile: getBoundFunction(system.readFile, system), directoryExists: getBoundFunction(system.directoryExists, system), @@ -98,10 +100,13 @@ namespace ts { clearTimeout: getBoundFunction(system.clearTimeout, system), trace: getBoundFunction(system.write, system), onWatchStatusChange, - system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; + function getDefaultLibLocation() { + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); + } + function onWatchStatusChange(diagnostic: Diagnostic, newLine: string) { if (system.clearScreen && diagnostic.code !== Diagnostics.Compilation_complete_Watching_for_file_changes.code) { system.clearScreen(); @@ -242,9 +247,6 @@ namespace ts { } export interface WatchCompilerHost { - /** FS system to use */ - system: System; - /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ @@ -256,6 +258,8 @@ namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; /** * Use to check file presence for source files and @@ -287,7 +291,7 @@ namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; /** If provided, will be used to reset existing delayed compilation */ clearTimeout?(timeoutId: any): void; @@ -411,7 +415,6 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const system = host.system; const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); const currentDirectory = host.getCurrentDirectory(); const getCurrentDirectory = () => currentDirectory; @@ -439,7 +442,7 @@ namespace ts { parseConfigFile(); } - const trace = host.trace && ((s: string) => { host.trace(s + newLine) }); + const trace = host.trace && ((s: string) => { host.trace(s + newLine); }); const loggingEnabled = trace && (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics); const writeLog = loggingEnabled ? trace : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; @@ -457,8 +460,8 @@ namespace ts { // Members for CompilerHost getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile), getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + getDefaultLibLocation: getBoundFunction(host.getDefaultLibLocation, host), + getDefaultLibFileName: getBoundFunction(host.getDefaultLibFileName, host), writeFile: notImplemented, getCurrentDirectory, useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, @@ -581,10 +584,6 @@ namespace ts { return directoryStructureHost.fileExists(fileName); } - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(system.getExecutingFilePath())); - } - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { const hostSourceFile = sourceFilesCache.get(path); // No source file on the host diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c676aa951ac..521fd1e0ad6 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3815,8 +3815,6 @@ declare namespace ts { */ function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; interface WatchCompilerHost { - /** FS system to use */ - system: System; /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ @@ -3826,6 +3824,8 @@ declare namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; /** * Use to check file presence for source files and * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well From 14f66efcc595f51adf612167bc68dc745a3265de Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 20:23:14 -0800 Subject: [PATCH 024/101] Update the emitting file, reporting errors part of the watch api --- src/compiler/core.ts | 4 - src/compiler/tsc.ts | 43 +-- src/compiler/watch.ts | 357 ++++++++++++------ src/compiler/watchUtilities.ts | 6 +- src/server/editorServices.ts | 4 +- src/server/project.ts | 6 +- .../reference/api/tsserverlibrary.d.ts | 5 +- tests/baselines/reference/api/typescript.d.ts | 22 +- 8 files changed, 266 insertions(+), 181 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index b6472987398..1c180d5bc89 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -3000,8 +3000,4 @@ namespace ts { } export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty - - export function getBoundFunction(method: T | undefined, methodOf: {}): T | undefined { - return method && method.bind(methodOf); - } } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 43211313237..ed5284e696c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -144,17 +144,16 @@ namespace ts { enableStatistics(compilerOptions); const program = createProgram(rootFileNames, compilerOptions, compilerHost); - const exitStatus = compileProgram(program); - + const exitStatus = emitFilesAndReportErrors(program, reportDiagnostic, s => sys.write(s + sys.newLine)); reportStatistics(program); return sys.exit(exitStatus); } function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost) { - const compilerWithBuilderState = watchCompilerHost.afterProgramCreate; + const compileUsingBuilder = watchCompilerHost.afterProgramCreate; watchCompilerHost.beforeProgramCreate = enableStatistics; - watchCompilerHost.afterProgramCreate = (host, program) => { - compilerWithBuilderState(host, program); + watchCompilerHost.afterProgramCreate = program => { + compileUsingBuilder(program); reportStatistics(program); }; } @@ -175,40 +174,6 @@ namespace ts { createWatch(watchCompilerHost); } - function compileProgram(program: Program): ExitStatus { - let diagnostics: Diagnostic[]; - - // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics().slice(); - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics().slice(); - } - } - - // Emit and report any errors we ran into. - const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); - addRange(diagnostics, emitDiagnostics); - - sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); - writeFileAndEmittedFileList(sys, program, emittedFiles); - if (emitSkipped && diagnostics.length > 0) { - // If the emitter didn't emit anything, then pass that value along. - return ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - else if (diagnostics.length > 0) { - // The emitter emitted something, inform the caller if that happened in the presence - // of diagnostics or not. - return ExitStatus.DiagnosticsPresent_OutputsGenerated; - } - return ExitStatus.Success; - } - function enableStatistics(compilerOptions: CompilerOptions) { if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 67caf3c79ca..fcee17e0328 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -78,30 +78,152 @@ namespace ts { return configParseResult; } + /** + * Program structure needed to emit the files and report diagnostics + */ + export interface ProgramToEmitFilesAndReportErrors { + getCurrentDirectory(): string; + getCompilerOptions(): CompilerOptions; + getSourceFiles(): ReadonlyArray; + getSyntacticDiagnostics(): ReadonlyArray; + getOptionsDiagnostics(): ReadonlyArray; + getGlobalDiagnostics(): ReadonlyArray; + getSemanticDiagnostics(): ReadonlyArray; + emit(): EmitResult; + } + + /** + * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options + */ + export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void) { + // First get and report any syntactic errors. + const diagnostics = program.getSyntacticDiagnostics().slice(); + let reportSemanticDiagnostics = false; + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + addRange(diagnostics, program.getOptionsDiagnostics()); + addRange(diagnostics, program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + reportSemanticDiagnostics = true; + } + } + + // Emit and report any errors we ran into. + const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); + addRange(diagnostics, emitDiagnostics); + + if (reportSemanticDiagnostics) { + addRange(diagnostics, program.getSemanticDiagnostics()); + } + + sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); + if (writeFileName) { + const currentDir = program.getCurrentDirectory(); + forEach(emittedFiles, file => { + const filepath = getNormalizedAbsolutePath(file, currentDir); + writeFileName(`TSFILE: ${filepath}`); + }); + + if (program.getCompilerOptions().listFiles) { + forEach(program.getSourceFiles(), file => { + writeFileName(file.fileName); + }); + } + } + + if (emitSkipped && diagnostics.length > 0) { + // If the emitter didn't emit anything, then pass that value along. + return ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + else if (diagnostics.length > 0) { + // The emitter emitted something, inform the caller if that happened in the presence + // of diagnostics or not. + return ExitStatus.DiagnosticsPresent_OutputsGenerated; + } + return ExitStatus.Success; + } + + /** + * Creates the function that emits files and reports errors when called with program + */ + function createEmitFilesAndReportErrorsWithBuilderUsingSystem(system: System, reportDiagnostic: DiagnosticReporter) { + const emitErrorsAndReportErrorsWithBuilder = createEmitFilesAndReportErrorsWithBuilder({ + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + createHash: system.createHash && (s => system.createHash(s)), + writeFile, + reportDiagnostic, + writeFileName: s => system.write(s + system.newLine) + }); + let host: CachedDirectoryStructureHost | undefined; + return { + emitFilesAndReportError: (program: Program) => emitErrorsAndReportErrorsWithBuilder(program), + setHost: (cachedDirectoryStructureHost: CachedDirectoryStructureHost) => host = cachedDirectoryStructureHost + }; + + function getHost() { + return host || system; + } + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !getHost().directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + getHost().createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + getHost().writeFile(fileName, text, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); + } + } + } + } + + const noopFileWatcher: FileWatcher = { close: noop }; + /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ - function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { - return { + function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter): WatchCompilerHost { + const { emitFilesAndReportError, setHost } = createEmitFilesAndReportErrorsWithBuilderUsingSystem(system, reportDiagnostic); + const host: WatchCompilerHost = { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, - getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + getCurrentDirectory: () => system.getCurrentDirectory(), getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - fileExists: getBoundFunction(system.fileExists, system), - readFile: getBoundFunction(system.readFile, system), - directoryExists: getBoundFunction(system.directoryExists, system), - getDirectories: getBoundFunction(system.getDirectories, system), - readDirectory: getBoundFunction(system.readDirectory, system), - realpath: getBoundFunction(system.realpath, system), - watchFile: getBoundFunction(system.watchFile, system), - watchDirectory: getBoundFunction(system.watchDirectory, system), - setTimeout: getBoundFunction(system.setTimeout, system), - clearTimeout: getBoundFunction(system.clearTimeout, system), - trace: getBoundFunction(system.write, system), + fileExists: path => system.fileExists(path), + readFile: (path, encoding) => system.readFile(path, encoding), + 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)), + 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, + trace: s => system.write(s), onWatchStatusChange, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) + createDirectory: path => system.createDirectory(path), + writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), + onCachedDirectoryStructureHostCreate: host => setHost(host), + afterProgramCreate: emitFilesAndReportError, }; + return host; function getDefaultLibLocation() { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); @@ -140,7 +262,7 @@ namespace ts { * Creates the watch compiler host from system for compiling root files and options in watch mode */ export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + const host = createWatchCompilerHost(system, reportDiagnostic || createDiagnosticReporter(system)) as WatchCompilerHostOfFilesAndCompilerOptions; host.rootFiles = rootFiles; host.options = options; return host; @@ -150,99 +272,75 @@ namespace ts { namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Writes emitted files, source files depending on options - */ - /*@internal*/ - export function writeFileAndEmittedFileList(system: System, program: Program, emittedFiles: string[]) { - const currentDir = system.getCurrentDirectory(); - forEach(emittedFiles, file => { - const filepath = getNormalizedAbsolutePath(file, currentDir); - system.write(`TSFILE: ${filepath}${system.newLine}`); - }); + interface BuilderProgram extends ProgramToEmitFilesAndReportErrors { + updateProgram(program: Program): void; + } - if (program.getCompilerOptions().listFiles) { - forEach(program.getSourceFiles(), file => { - system.write(file.fileName + system.newLine); - }); + function createBuilderProgram(host: BuilderEmitHost): BuilderProgram { + const builder = createEmitAndSemanticDiagnosticsBuilder({ + getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames()), + computeHash: host.createHash ? host.createHash : identity + }); + let program: Program; + return { + getCurrentDirectory: () => program.getCurrentDirectory(), + getCompilerOptions: () => program.getCompilerOptions(), + getSourceFiles: () => program.getSourceFiles(), + getSyntacticDiagnostics: () => program.getSyntacticDiagnostics(), + getOptionsDiagnostics: () => program.getOptionsDiagnostics(), + getGlobalDiagnostics: () => program.getGlobalDiagnostics(), + getSemanticDiagnostics: () => builder.getSemanticDiagnostics(program), + emit, + updateProgram + }; + + function updateProgram(p: Program) { + program = p; + builder.updateProgram(p); + } + + function emit(): EmitResult { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapData[]; + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + let emittedFiles: string[]; + + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = builder.emitNextAffectedFile(program, host.writeFile)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); + } + return { + emitSkipped, + diagnostics, + emittedFiles, + sourceMaps + }; } } /** - * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files + * Host needed to emit files and report errors using builder */ - export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) { - reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: getBoundFunction(system.createHash, system) || identity - }); + export interface BuilderEmitHost { + useCaseSensitiveFileNames(): boolean; + createHash?: (data: string) => string; + writeFile: WriteFileCallback; + reportDiagnostic: DiagnosticReporter; + writeFileName?: (s: string) => void; + } - return (host: DirectoryStructureHost, program: Program) => { - builder.updateProgram(program); - - // First get and report any syntactic errors. - const diagnostics = program.getSyntacticDiagnostics().slice(); - let reportSemanticDiagnostics = false; - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - addRange(diagnostics, program.getOptionsDiagnostics()); - addRange(diagnostics, program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - reportSemanticDiagnostics = true; - } - } - - // Emit and report any errors we ran into. - const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - addRange(diagnostics, affectedEmitResult.result.diagnostics); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - - if (reportSemanticDiagnostics) { - addRange(diagnostics, builder.getSemanticDiagnostics(program)); - } - - sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); - writeFileAndEmittedFileList(system, program, emittedFiles); - - 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"); - - if (emittedFiles) { - emittedFiles.push(fileName); - } - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } + /** + * Creates the function that reports the program errors and emit files every time it is called with argument as program + */ + export function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost) { + const builderProgram = createBuilderProgram(host); + return (program: Program) => { + builderProgram.updateProgram(program); + emitFilesAndReportErrors(builderProgram, host.reportDiagnostic, host.writeFileName); }; } @@ -250,7 +348,7 @@ namespace ts { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + afterProgramCreate?(program: Program): void; /** If provided, called with Diagnostic message that informs about change in watch status */ onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; @@ -297,6 +395,14 @@ namespace ts { clearTimeout?(timeoutId: any): void; } + /** Internal interface used to wire emit through same host */ + /*@internal*/ + export interface WatchCompilerHost { + createDirectory?(path: string): void; + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void; + } + /** * Host to create watch with root files and options */ @@ -315,12 +421,12 @@ namespace ts { /** * Reports the diagnostics in reading/writing or parsing of the config file */ - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + onConfigFileDiagnostic: DiagnosticReporter; /** * Reports unrecoverable error when parsing config file */ - onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; } /** @@ -345,7 +451,6 @@ namespace ts { */ /*@internal*/ export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { - cachedDirectoryStructureHost?: CachedDirectoryStructureHost; rootFiles?: string[]; options?: CompilerOptions; optionsToExtend?: CompilerOptions; @@ -418,23 +523,23 @@ namespace ts { const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); const currentDirectory = host.getCurrentDirectory(); const getCurrentDirectory = () => currentDirectory; - const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); - const readFile = getBoundFunction(host.readFile, host); + const readFile: (path: string, encoding?: string) => string | undefined = (path, encoding) => host.readFile(path, encoding); const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; - const beforeProgramCreate = getBoundFunction(host.beforeProgramCreate, host) || noop; - const afterProgramCreate = getBoundFunction(host.afterProgramCreate, host) || noop; let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); + if (cachedDirectoryStructureHost && host.onCachedDirectoryStructureHostCreate) { + host.onCachedDirectoryStructureHostCreate(cachedDirectoryStructureHost); + } const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; const parseConfigFileHost: ParseConfigFileHost = { useCaseSensitiveFileNames, - readDirectory: getBoundFunction(directoryStructureHost.readDirectory, directoryStructureHost), - fileExists: getBoundFunction(directoryStructureHost.fileExists, directoryStructureHost), + readDirectory: (path, extensions, exclude, include, depth) => directoryStructureHost.readDirectory(path, extensions, exclude, include, depth), + fileExists: path => host.fileExists(path), readFile, getCurrentDirectory, - onConfigFileDiagnostic, - onUnRecoverableConfigFileDiagnostic: getBoundFunction(host.onUnRecoverableConfigFileDiagnostic, host) + onConfigFileDiagnostic: host.onConfigFileDiagnostic, + onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic }; // From tsc we want to get already parsed result and hence check for rootFileNames @@ -460,8 +565,8 @@ namespace ts { // Members for CompilerHost getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile), getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation: getBoundFunction(host.getDefaultLibLocation, host), - getDefaultLibFileName: getBoundFunction(host.getDefaultLibFileName, host), + getDefaultLibLocation: host.getDefaultLibLocation && (() => host.getDefaultLibLocation()), + getDefaultLibFileName: options => host.getDefaultLibFileName(options), writeFile: notImplemented, getCurrentDirectory, useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, @@ -470,9 +575,9 @@ namespace ts { fileExists, readFile, trace, - directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), - getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), - realpath: getBoundFunction(host.realpath, host), + directoryExists: directoryStructureHost.directoryExists && (path => directoryStructureHost.directoryExists(path)), + getDirectories: directoryStructureHost.getDirectories && (path => directoryStructureHost.getDirectories(path)), + realpath: host.realpath && (s => host.realpath(s)), onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, @@ -495,8 +600,8 @@ namespace ts { ); // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names compilerHost.resolveModuleNames = host.resolveModuleNames ? - host.resolveModuleNames.bind(host) : - resolutionCache.resolveModuleNames.bind(resolutionCache); + ((moduleNames, containingFile, reusedNames) => host.resolveModuleNames(moduleNames, containingFile, reusedNames)) : + ((moduleNames, containingFile, reusedNames) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames)); compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); @@ -524,7 +629,9 @@ namespace ts { return program; } - beforeProgramCreate(compilerOptions); + if (host.beforeProgramCreate) { + host.beforeProgramCreate(compilerOptions); + } // Compile the program const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program; @@ -555,7 +662,9 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterProgramCreate(directoryStructureHost, program); + if (host.afterProgramCreate) { + host.afterProgramCreate(program); + } reportWatchDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes); return program; } @@ -722,7 +831,7 @@ namespace ts { function reloadFileNamesFromConfigFile() { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { - onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + host.onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } rootFileNames = result.fileNames; diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index bf5a42b7b08..24c9737fb26 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -49,12 +49,12 @@ namespace ts { return { useCaseSensitiveFileNames, fileExists, - readFile: getBoundFunction(host.readFile, host), + readFile: (path, encoding) => host.readFile(path, encoding), directoryExists: host.directoryExists && directoryExists, getDirectories, readDirectory, - createDirectory, - writeFile, + createDirectory: host.createDirectory && createDirectory, + writeFile: host.writeFile && writeFile, addOrDeleteFileOrDirectory, addOrDeleteFile, clearCache diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index bafe90a59cb..6afeae16ef5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1765,11 +1765,11 @@ namespace ts.server { return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent); } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: { fileExists(path: string): boolean; }) { return this.getOrCreateScriptInfoWorker(fileName, this.currentDirectory, openedByClient, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } - private getOrCreateScriptInfoWorker(fileName: NormalizedPath, currentDirectory: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { + private getOrCreateScriptInfoWorker(fileName: NormalizedPath, currentDirectory: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: { fileExists(path: string): boolean; }) { Debug.assert(fileContent === undefined || openedByClient, "ScriptInfo needs to be opened by client to be able to set its user defined content"); const path = normalizedPathToPath(fileName, currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); diff --git a/src/server/project.ts b/src/server/project.ts index 9ae1b1d0b6e..26d8aba0bd5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -201,6 +201,9 @@ namespace ts.server { /*@internal*/ readonly currentDirectory: string; + /*@internal*/ + public directoryStructureHost: DirectoryStructureHost; + /*@internal*/ constructor( /*@internal*/readonly projectName: string, @@ -211,8 +214,9 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - /*@internal*/public directoryStructureHost: DirectoryStructureHost, + directoryStructureHost: DirectoryStructureHost, currentDirectory: string | undefined) { + this.directoryStructureHost = directoryStructureHost; this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || ""); this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ca399847583..4abb45a11e4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7270,7 +7270,6 @@ declare namespace ts.server { private documentRegistry; private compilerOptions; compileOnSaveEnabled: boolean; - directoryStructureHost: DirectoryStructureHost; private rootFiles; private rootFilesMap; private program; @@ -7741,7 +7740,9 @@ declare namespace ts.server { getScriptInfo(uncheckedFileName: string): ScriptInfo; private watchClosedScriptInfo(info); private stopWatchingScriptInfo(info); - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost): ScriptInfo; + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: { + fileExists(path: string): boolean; + }): ScriptInfo; private getOrCreateScriptInfoWorker(fileName, currentDirectory, openedByClient, fileContent?, scriptKind?, hasMixedContent?, hostToQueryFileExistsOn?); /** * This gets the script info for the normalized path. If the path is not rooted disk path then the open script info with project root context is preferred diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 521fd1e0ad6..eed0c7cc5d8 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3811,14 +3811,24 @@ declare namespace ts { declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** - * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files + * Host needed to emit files and report errors using builder */ - function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; + interface BuilderEmitHost { + useCaseSensitiveFileNames(): boolean; + createHash?: (data: string) => string; + writeFile: WriteFileCallback; + reportDiagnostic: DiagnosticReporter; + writeFileName?: (s: string) => void; + } + /** + * Creates the function that reports the program errors and emit files every time it is called with argument as program + */ + function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost): (program: Program) => void; interface WatchCompilerHost { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + afterProgramCreate?(program: Program): void; /** If provided, called with Diagnostic message that informs about change in watch status */ onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; useCaseSensitiveFileNames(): boolean; @@ -3852,7 +3862,7 @@ declare namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; /** If provided, will be used to reset existing delayed compilation */ clearTimeout?(timeoutId: any): void; @@ -3873,11 +3883,11 @@ declare namespace ts { /** * Reports the diagnostics in reading/writing or parsing of the config file */ - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + onConfigFileDiagnostic: DiagnosticReporter; /** * Reports unrecoverable error when parsing config file */ - onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; } /** * Host to create watch with config file From c1cbf588ff96721ce71fb6689358f07fb43cc2b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Dec 2017 12:27:46 -0800 Subject: [PATCH 025/101] Update the project graph before checking if opened file is present in the existing project Fixes #20017 --- .../unittests/tsserverProjectSystem.ts | 32 +++++++++++++++++++ src/server/editorServices.ts | 26 +++++++++------ .../reference/api/tsserverlibrary.d.ts | 2 +- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 39dde23b28a..9d2566b7b0b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3468,6 +3468,38 @@ namespace ts.projectSystem { it("works when project root is used with case-insensitive system", () => { verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); }); + + it("uses existing project even if project refresh is pending", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: FileOrFolder = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: FileOrFolder = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); + + const bFile: FileOrFolder = { + path: `${projectFolder}/src/b.ts`, + content: `export {}; declare module "./a" { export const y: number; }` + }; + files.push(bFile); + host.reloadFS(files); + service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); + + function verifyProject() { + assert.isDefined(service.configuredProjects.get(configFile.path)); + const project = service.configuredProjects.get(configFile.path); + checkProjectActualFiles(project, files.map(f => f.path)); + } + }); }); describe("tsserverProjectSystem Language service", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f..c4446c049b6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -725,15 +725,6 @@ namespace ts.server { } } - private findContainingExternalProject(fileName: NormalizedPath): ExternalProject { - for (const proj of this.externalProjects) { - if (proj.containsFile(fileName)) { - return proj; - } - } - return undefined; - } - getFormatCodeOptions(file?: NormalizedPath) { let formatCodeSettings: FormatCodeSettings; if (file) { @@ -1991,13 +1982,24 @@ namespace ts.server { return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined); } + private findExternalProjetContainingOpenScriptInfo(info: ScriptInfo): ExternalProject { + for (const proj of this.externalProjects) { + // Ensure project structure is uptodate to check if info is present in external project + proj.updateGraph(); + if (proj.containsScriptInfo(info)) { + return proj; + } + } + return undefined; + } + openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult { let configFileName: NormalizedPath; let sendConfigFileDiagEvent = false; let configFileErrors: ReadonlyArray; const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent); - let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); + let project: ConfiguredProject | ExternalProject = this.findExternalProjetContainingOpenScriptInfo(info); if (!project) { configFileName = this.getConfigFileNameForFile(info, projectRootPath); if (configFileName) { @@ -2007,6 +2009,10 @@ namespace ts.server { // Send the event only if the project got created as part of this open request sendConfigFileDiagEvent = true; } + else { + // Ensure project is ready to check if it contains opened script info + project.updateGraph(); + } } } if (project && !project.languageServiceEnabled) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index abd03c26b0b..8ae40d84afd 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7605,7 +7605,6 @@ declare namespace ts.server { * @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures */ private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?); - private findContainingExternalProject(fileName); getFormatCodeOptions(file?: NormalizedPath): FormatCodeSettings; private updateProjectGraphs(projects); private onSourceFileChanged(fileName, eventKind); @@ -7723,6 +7722,7 @@ declare namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult; + private findExternalProjetContainingOpenScriptInfo(info); openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; /** * Close file whose contents is managed by the client From a21b07405570dae526eb4f54e9bbd714d0a3b4cd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Dec 2017 13:59:53 -0800 Subject: [PATCH 026/101] Update the builder to take options aligning with the WatchCompilerHost --- src/compiler/builder.ts | 25 +++++++++++++++---- src/compiler/watch.ts | 9 ++----- src/harness/unittests/builder.ts | 10 ++------ src/server/project.ts | 4 +-- .../reference/api/tsserverlibrary.d.ts | 10 ++++++-- tests/baselines/reference/api/typescript.d.ts | 14 +++++++---- 6 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ddbb015a2ee..c25cbb0d5a4 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -66,6 +66,15 @@ namespace ts { export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder; export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder; export function createBuilder(options: BuilderOptions, builderType: BuilderType) { + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(options.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = options.createHash || identity; + /** * Information of the file eg. its version, signature etc */ @@ -572,7 +581,7 @@ namespace ts { else { const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = options.computeHash(emitOutput.outputFiles[0].text); + latestSignature = computeHash(emitOutput.outputFiles[0].text); } else { latestSignature = prevSignature; @@ -609,7 +618,7 @@ namespace ts { // Handle triple slash references if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, options.getCanonicalFileName); + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); addReferencedFile(referencedPath); } } @@ -622,7 +631,7 @@ namespace ts { } const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, sourceFileDirectory, options.getCanonicalFileName); + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); addReferencedFile(typeFilePath); }); } @@ -738,8 +747,14 @@ namespace ts { export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; export interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; } /** diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index fcee17e0328..6770c036b10 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -277,10 +277,7 @@ namespace ts { } function createBuilderProgram(host: BuilderEmitHost): BuilderProgram { - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames()), - computeHash: host.createHash ? host.createHash : identity - }); + const builder = createEmitAndSemanticDiagnosticsBuilder(host); let program: Program; return { getCurrentDirectory: () => program.getCurrentDirectory(), @@ -325,9 +322,7 @@ namespace ts { /** * Host needed to emit files and report errors using builder */ - export interface BuilderEmitHost { - useCaseSensitiveFileNames(): boolean; - createHash?: (data: string) => string; + export interface BuilderEmitHost extends BuilderOptions { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 8ad7f4da3f9..a6353d9c0c3 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -81,10 +81,7 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: identity, - computeHash: identity - }); + const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); return fileNames => { const program = getProgram(); builder.updateProgram(program); @@ -97,10 +94,7 @@ namespace ts { } function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: ReadonlyArray, cancelAfterEmitLength?: number) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: identity, - computeHash: identity - }); + const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); let cancel = false; const cancellationToken: CancellationToken = { isCancellationRequested: () => cancel, diff --git a/src/server/project.ts b/src/server/project.ts index 26d8aba0bd5..1fa12e50120 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -462,8 +462,8 @@ namespace ts.server { this.updateGraph(); if (!this.builder) { this.builder = createInternalBuilder({ - getCanonicalFileName: this.projectService.toCanonicalFileName, - computeHash: data => this.projectService.host.createHash(data) + useCaseSensitiveFileNames: () => this.useCaseSensitiveFileNames(), + createHash: data => this.projectService.host.createHash(data) }); } this.builder.updateProgram(this.program); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4abb45a11e4..c613293c9ba 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3772,8 +3772,14 @@ declare namespace ts { affected: SourceFile | Program; } | undefined; interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; } /** * Builder to manage the program state changes diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index eed0c7cc5d8..c9d4e3dd0a3 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3719,8 +3719,14 @@ declare namespace ts { affected: SourceFile | Program; } | undefined; interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; } /** * Builder to manage the program state changes @@ -3813,9 +3819,7 @@ declare namespace ts { /** * Host needed to emit files and report errors using builder */ - interface BuilderEmitHost { - useCaseSensitiveFileNames(): boolean; - createHash?: (data: string) => string; + interface BuilderEmitHost extends BuilderOptions { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; From 39bf33d8414b56a5642644e0086dd63bd67df477 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 10:02:02 -0800 Subject: [PATCH 027/101] Few renames --- src/compiler/builder.ts | 42 +++++++++---------- src/compiler/watch.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 6 +-- tests/baselines/reference/api/typescript.d.ts | 8 ++-- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c25cbb0d5a4..9c7707bd1d8 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -28,14 +28,14 @@ namespace ts { /** * Create the internal builder to get files affected by sourceFile */ - export function createInternalBuilder(options: BuilderOptions): InternalBuilder { - return createBuilder(options, BuilderType.InternalBuilder); + export function createInternalBuilder(host: BuilderHost): InternalBuilder { + return createBuilder(host, BuilderKind.BuilderKindInternal); } - export enum BuilderType { - InternalBuilder, - SemanticDiagnosticsBuilder, - EmitAndSemanticDiagnosticsBuilder + export enum BuilderKind { + BuilderKindInternal, + BuilderKindSemanticDiagnostics, + BuilderKindEmitAndSemanticDiagnostics } /** @@ -62,18 +62,18 @@ namespace ts { return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); } - export function createBuilder(options: BuilderOptions, builderType: BuilderType.InternalBuilder): InternalBuilder; - export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder; - export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder; - export function createBuilder(options: BuilderOptions, builderType: BuilderType) { + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindInternal): InternalBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { /** * Create the canonical file name for identity */ - const getCanonicalFileName = createGetCanonicalFileName(options.useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); /** * Computing hash to for signature verification */ - const computeHash = options.createHash || identity; + const computeHash = host.createHash || identity; /** * Information of the file eg. its version, signature etc @@ -141,12 +141,12 @@ namespace ts { */ const seenAffectedFiles = createMap(); - switch (builderType) { - case BuilderType.InternalBuilder: + switch (builderKind) { + case BuilderKind.BuilderKindInternal: return getInternalBuilder(); - case BuilderType.SemanticDiagnosticsBuilder: + case BuilderKind.BuilderKindSemanticDiagnostics: return getSemanticDiagnosticsBuilder(); - case BuilderType.EmitAndSemanticDiagnosticsBuilder: + case BuilderKind.BuilderKindEmitAndSemanticDiagnostics: return getEmitAndSemanticDiagnosticsBuilder(); default: notImplemented(); @@ -746,7 +746,7 @@ namespace ts { export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - export interface BuilderOptions { + export interface BuilderHost { /** * return true if file names are treated with case sensitivity */ @@ -813,15 +813,15 @@ namespace ts { /** * Create the builder to manage semantic diagnostics and cache them */ - export function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder { - return createBuilder(options, BuilderType.SemanticDiagnosticsBuilder); + export function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder { + return createBuilder(host, BuilderKind.BuilderKindSemanticDiagnostics); } /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - export function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder { - return createBuilder(options, BuilderType.EmitAndSemanticDiagnosticsBuilder); + export function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder { + return createBuilder(host, BuilderKind.BuilderKindEmitAndSemanticDiagnostics); } } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 6770c036b10..1c5a0ad5d99 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -322,7 +322,7 @@ namespace ts { /** * Host needed to emit files and report errors using builder */ - export interface BuilderEmitHost extends BuilderOptions { + export interface BuilderEmitHost extends BuilderHost { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 97a24adb74d..d7f3821a25d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,7 +3771,7 @@ declare namespace ts { result: T; affected: SourceFile | Program; } | undefined; - interface BuilderOptions { + interface BuilderHost { /** * return true if file names are treated with case sensitivity */ @@ -3831,12 +3831,12 @@ declare namespace ts { /** * Create the builder to manage semantic diagnostics and cache them */ - function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; + function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; + function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c9d4e3dd0a3..06c75e843c0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,7 +3718,7 @@ declare namespace ts { result: T; affected: SourceFile | Program; } | undefined; - interface BuilderOptions { + interface BuilderHost { /** * return true if file names are treated with case sensitivity */ @@ -3778,12 +3778,12 @@ declare namespace ts { /** * Create the builder to manage semantic diagnostics and cache them */ - function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; + function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; + function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -3819,7 +3819,7 @@ declare namespace ts { /** * Host needed to emit files and report errors using builder */ - interface BuilderEmitHost extends BuilderOptions { + interface BuilderEmitHost extends BuilderHost { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; From 4c21cbf145b3dab90c6e5c6b6f2642125ce7b312 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 11:47:49 -0800 Subject: [PATCH 028/101] Create builderState so that when FilesAffectedBy is only api needed, we arent tracking changed files --- src/compiler/builderState.ts | 431 +++++++++++++++++++++++++++++++++++ src/compiler/program.ts | 1 - src/server/project.ts | 14 +- 3 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 src/compiler/builderState.ts diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts new file mode 100644 index 00000000000..f6706195a75 --- /dev/null +++ b/src/compiler/builderState.ts @@ -0,0 +1,431 @@ +/// +namespace ts { + export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + + export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } +} + +/*@internal*/ +namespace ts { + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { + const outputFiles: OutputFile[] = []; + const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + return { outputFiles, emitSkipped: emitResult.emitSkipped }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + } + } + + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature?: string; + } + + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { + if (map1 === undefined) { + return map2 === undefined; + } + if (map2 === undefined) { + return map1 === undefined; + } + // Has same size and every key is present in both maps + return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); + } + + export interface BuilderStateHost { + /** + * true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames: boolean; + /** + * if provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; + /** + * Called when programState is initialized, indicating if isModuleEmit is changed + */ + onUpdateProgramInitialized(isModuleEmitChanged: boolean): void; + onSourceFileAdd(path: Path): void; + onSourceFileChanged(path: Path): void; + onSourceFileRemoved(path: Path): void; + } + + export interface BuilderState { + /** + * Updates the program in the builder to represent new state + */ + updateProgram(newProgram: Program): void; + /** + * Gets the files affected by the file path + */ + getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray; + } + + export function createBuilderState(host: BuilderStateHost): BuilderState { + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + /** + * Computing hash to for signature verification + */ + const computeHash = host.createHash || identity; + + /** + * Information of the file eg. its version, signature etc + */ + const fileInfos = createMap(); + + /** + * true if module emit is enabled + */ + let isModuleEmit: boolean; + + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + */ + let referencedMap: Map | undefined; + + /** + * Get the files affected by the source file. + * This is dependent on whether its a module emit or not and hence function expression + */ + let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; + + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + const hasCalledUpdateShapeSignature = createMap(); + + /** + * Cache of all files excluding default library file for the current program + */ + let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + + return { + updateProgram, + getFilesAffectedBy, + }; + + /** + * Update current state to reflect new program + * Updates changed files, references, file infos etc + */ + function updateProgram(newProgram: Program) { + const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; + const oldReferencedMap = referencedMap; + const isModuleEmitChanged = isModuleEmit !== newProgramHasModuleEmit; + if (isModuleEmitChanged) { + // Changes in the module emit, clear out everything and initialize as if first time + + // Clear file information + fileInfos.clear(); + + // Update the reference map creation + referencedMap = newProgramHasModuleEmit ? createMap() : undefined; + + // Update the module emit + isModuleEmit = newProgramHasModuleEmit; + getEmitDependentFilesAffectedBy = isModuleEmit ? + getFilesAffectedByUpdatedShapeWhenModuleEmit : + getFilesAffectedByUpdatedShapeWhenNonModuleEmit; + } + host.onUpdateProgramInitialized(isModuleEmitChanged); + + // Clear datas that cant be retained beyond previous state + hasCalledUpdateShapeSignature.clear(); + allFilesExcludingDefaultLibraryFile = undefined; + + // Create the reference map and update changed files + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + const oldInfo = fileInfos.get(sourceFile.path); + let oldReferences: ReferencedSet; + + // Register changed file if its new file or we arent reusing old state + if (!oldInfo) { + // New file: Set the file info + fileInfos.set(sourceFile.path, { version }); + host.onSourceFileAdd(sourceFile.path); + } + // versions dont match + else if (oldInfo.version !== version || + // Referenced files changed + !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || + // Referenced file was deleted in the new program + newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { + + // Changed file: Update the version, set as changed file + oldInfo.version = version; + host.onSourceFileChanged(sourceFile.path); + } + + // Set the references + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + else if (referencedMap) { + referencedMap.delete(sourceFile.path); + } + } + + // For removed files, remove the semantic diagnostics and file info + if (fileInfos.size > newProgram.getSourceFiles().length) { + fileInfos.forEach((_value, path) => { + if (!newProgram.getSourceFileByPath(path as Path)) { + fileInfos.delete(path); + host.onSourceFileRemoved(path as Path); + if (referencedMap) { + referencedMap.delete(path); + } + } + }); + } + } + + /** + * Gets the files affected by the path from the program + */ + function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be commited once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are commited only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; + } + + if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { + return [sourceFile]; + } + + const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(signatureCache); + } + return result; + } + + /** + * Updates the signatures from the cache + * This should be called whenever it is safe to commit the state of the builder + */ + function updateSignaturesFromCache(signatureCache: Map) { + signatureCache.forEach((signature, path) => { + fileInfos.get(path).signature = signature; + hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Returns if the shape of the signature has changed since last emit + * Note that it also updates the current signature as the latest signature for the file + */ + function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { + Debug.assert(!!sourceFile); + + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { + return false; + } + + const info = fileInfos.get(sourceFile.path); + Debug.assert(!!info); + + const prevSignature = info.signature; + let latestSignature: string; + if (sourceFile.isDeclarationFile) { + latestSignature = sourceFile.version; + } + else { + const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); + if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { + latestSignature = computeHash(emitOutput.outputFiles[0].text); + } + else { + latestSignature = prevSignature; + } + } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + + return !prevSignature || latestSignature !== prevSignature; + } + + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { + let referencedFiles: Map | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const symbol = checker.getSymbolAtLocation(importName); + if (symbol && symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); + if (declarationSourceFile) { + addReferencedFile(declarationSourceFile.path); + } + } + } + } + + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); + } + } + + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; + } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName; + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + + return referencedFiles; + + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); + } + } + + /** + * Gets the files referenced by the the file path + */ + function getReferencedByPaths(referencedFilePath: Path) { + return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); + } + + /** + * Gets all files of the program excluding the default library file + */ + function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { + // Use cached result + if (allFilesExcludingDefaultLibraryFile) { + return allFilesExcludingDefaultLibraryFile; + } + + let result: SourceFile[]; + addSourceFile(firstSourceFile); + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); + } + } + allFilesExcludingDefaultLibraryFile = result || emptyArray; + return allFilesExcludingDefaultLibraryFile; + + function addSourceFile(sourceFile: SourceFile) { + if (!program.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); + } + } + } + + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); + } + + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { + if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { + queue.push(...getReferencedByPaths(currentPath)); + } + } + } + + // Return array of values that needs emit + return flatMapIter(seenFileNamesMap.values(), value => value); + } + } +} diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9960b501d78..7a8911a5cbb 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,7 +1,6 @@ /// /// /// -/// namespace ts { const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; diff --git a/src/server/project.ts b/src/server/project.ts index 1fa12e50120..a5542cb89e7 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -3,7 +3,7 @@ /// /// /// -/// +/// namespace ts.server { @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builder: InternalBuilder | undefined; + private builder: BuilderState | undefined; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -461,9 +461,13 @@ namespace ts.server { } this.updateGraph(); if (!this.builder) { - this.builder = createInternalBuilder({ - useCaseSensitiveFileNames: () => this.useCaseSensitiveFileNames(), - createHash: data => this.projectService.host.createHash(data) + this.builder = createBuilderState({ + useCaseSensitiveFileNames: this.useCaseSensitiveFileNames(), + createHash: data => this.projectService.host.createHash(data), + onUpdateProgramInitialized: noop, + onSourceFileAdd: noop, + onSourceFileChanged: noop, + onSourceFileRemoved: noop }); } this.builder.updateProgram(this.program); From 2586bb303c976eb8f55a47e95a9b450690f8b119 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 12:39:26 -0800 Subject: [PATCH 029/101] From builder use the builderState containing references and file infos --- src/compiler/builder.ts | 480 ++---------------- src/compiler/builderState.ts | 214 +++++--- .../reference/api/tsserverlibrary.d.ts | 92 +--- tests/baselines/reference/api/typescript.d.ts | 62 +-- 4 files changed, 214 insertions(+), 634 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 9c7707bd1d8..ff372479fad 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,101 +1,26 @@ -/// +/// /*@internal*/ namespace ts { - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { - const outputFiles: OutputFile[] = []; - const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - return { outputFiles, emitSkipped: emitResult.emitSkipped }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - } - } - - /** - * Internal Builder to get files affected by another file - */ - export interface InternalBuilder extends BaseBuilder { - /** - * Gets the files affected by the file path - * This api is only for internal use - */ - /*@internal*/ - getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken): ReadonlyArray; - } - - /** - * Create the internal builder to get files affected by sourceFile - */ - export function createInternalBuilder(host: BuilderHost): InternalBuilder { - return createBuilder(host, BuilderKind.BuilderKindInternal); - } - export enum BuilderKind { - BuilderKindInternal, BuilderKindSemanticDiagnostics, BuilderKindEmitAndSemanticDiagnostics } - /** - * Information about the source file: Its version and optional signature from last emit - */ - interface FileInfo { - version: string; - signature?: string; - } - - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - type ReferencedSet = ReadonlyMap; - - function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { - if (map1 === undefined) { - return map2 === undefined; - } - if (map2 === undefined) { - return map1 === undefined; - } - // Has same size and every key is present in both maps - return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); - } - - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindInternal): InternalBuilder; export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { /** - * Create the canonical file name for identity + * State corresponding to all the file references and shapes of the module etc */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - /** - * Computing hash to for signature verification - */ - const computeHash = host.createHash || identity; - - /** - * Information of the file eg. its version, signature etc - */ - const fileInfos = createMap(); - - /** - * true if module emit is enabled - */ - let isModuleEmit: boolean; - - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - */ - let referencedMap: Map | undefined; - - /** - * Get the files affected by the source file. - * This is dependent on whether its a module emit or not and hence function expression - */ - let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; + const state = createBuilderState({ + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), + createHash: host.createHash, + onUpdateProgramInitialized, + onSourceFileAdd: addToChangedFilesSet, + onSourceFileChanged: path => { addToChangedFilesSet(path); deleteSemanticDiagnostics(path); }, + onSourceFileRemoved: deleteSemanticDiagnostics + }); /** * Cache of semantic diagnostics for files with their Path being the key @@ -107,18 +32,6 @@ namespace ts { */ const changedFilesSet = createMap(); - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - const hasCalledUpdateShapeSignature = createMap(); - - /** - * Cache of all files excluding default library file for the current program - */ - let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - /** * Set of affected files being iterated */ @@ -142,8 +55,6 @@ namespace ts { const seenAffectedFiles = createMap(); switch (builderKind) { - case BuilderKind.BuilderKindInternal: - return getInternalBuilder(); case BuilderKind.BuilderKindSemanticDiagnostics: return getSemanticDiagnosticsBuilder(); case BuilderKind.BuilderKindEmitAndSemanticDiagnostics: @@ -152,14 +63,6 @@ namespace ts { notImplemented(); } - function getInternalBuilder(): InternalBuilder { - return { - updateProgram, - getFilesAffectedBy, - getAllDependencies - }; - } - function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder { return { updateProgram, @@ -179,17 +82,13 @@ namespace ts { } /** - * Update current state to reflect new program - * Updates changed files, references, file infos etc + * Initialize changedFiles, affected files set, cached diagnostics, signatures */ - function updateProgram(newProgram: Program) { - const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; - const oldReferencedMap = referencedMap; - if (isModuleEmit !== newProgramHasModuleEmit) { + function onUpdateProgramInitialized(isModuleEmitChanged: boolean) { + if (isModuleEmitChanged) { // Changes in the module emit, clear out everything and initialize as if first time // Clear file information and semantic diagnostics - fileInfos.clear(); semanticDiagnosticsPerFile.clear(); // Clear changed files and affected files information @@ -197,21 +96,12 @@ namespace ts { affectedFiles = undefined; currentChangedFilePath = undefined; currentAffectedFilesSignatures.clear(); - - // Update the reference map creation - referencedMap = newProgramHasModuleEmit ? createMap() : undefined; - - // Update the module emit - isModuleEmit = newProgramHasModuleEmit; - getEmitDependentFilesAffectedBy = isModuleEmit ? - getFilesAffectedByUpdatedShapeWhenModuleEmit : - getFilesAffectedByUpdatedShapeWhenNonModuleEmit; } else { if (currentChangedFilePath) { // Remove the diagnostics for all the affected files since we should resume the state such that // the whole iteration on currentChangedFile never happened - affectedFiles.map(sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path)); + affectedFiles.forEach(sourceFile => deleteSemanticDiagnostics(sourceFile.path)); affectedFiles = undefined; currentAffectedFilesSignatures.clear(); } @@ -219,100 +109,27 @@ namespace ts { // Verify the sanity of old state Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated"); } - Debug.assert(!forEachEntry(changedFilesSet, (_value, path) => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - } - - // Clear datas that cant be retained beyond previous state - seenAffectedFiles.clear(); - hasCalledUpdateShapeSignature.clear(); - allFilesExcludingDefaultLibraryFile = undefined; - - // Create the reference map and update changed files - for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); - const oldInfo = fileInfos.get(sourceFile.path); - let oldReferences: ReferencedSet; - - // Register changed file if its new file or we arent reusing old state - if (!oldInfo) { - // New file: Set the file info - fileInfos.set(sourceFile.path, { version }); - changedFilesSet.set(sourceFile.path, true); - } - // versions dont match - else if (oldInfo.version !== version || - // Referenced files changed - !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || - // Referenced file was deleted in the new program - newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { - - // Changed file: Update the version, set as changed file - oldInfo.version = version; - changedFilesSet.set(sourceFile.path, true); - - // All changed files need to re-evaluate its semantic diagnostics - semanticDiagnosticsPerFile.delete(sourceFile.path); - } - - // Set the references - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - else if (referencedMap) { - referencedMap.delete(sourceFile.path); - } - } - - // For removed files, remove the semantic diagnostics and file info - if (fileInfos.size > newProgram.getSourceFiles().length) { - fileInfos.forEach((_value, path) => { - if (!newProgram.getSourceFileByPath(path as Path)) { - fileInfos.delete(path); - semanticDiagnosticsPerFile.delete(path); - if (referencedMap) { - referencedMap.delete(path); - } - } - }); + Debug.assert(!forEachKey(changedFilesSet, path => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); } } /** - * Gets the files affected by the path from the program + * Add file to the changed files set */ - function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be commited once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are commited only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || createMap(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; - } + function addToChangedFilesSet(path: Path) { + changedFilesSet.set(path, true); + } - if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { - return [sourceFile]; - } - - const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(signatureCache); - } - return result; + function deleteSemanticDiagnostics(path: Path) { + semanticDiagnosticsPerFile.delete(path); } /** - * Updates the signatures from the cache - * This should be called whenever it is safe to commit the state of the builder + * Update current state to reflect new program + * Updates changed files, references, file infos etc which happens through the state callbacks */ - function updateSignaturesFromCache(signatureCache: Map) { - signatureCache.forEach((signature, path) => { - fileInfos.get(path).signature = signature; - hasCalledUpdateShapeSignature.set(path, true); - }); + function updateProgram(newProgram: Program) { + state.updateProgram(newProgram); } /** @@ -339,7 +156,7 @@ namespace ts { changedFilesSet.delete(currentChangedFilePath); currentChangedFilePath = undefined; // Commit the changes in file signature - updateSignaturesFromCache(currentAffectedFilesSignatures); + state.updateSignaturesFromCache(currentAffectedFilesSignatures); currentAffectedFilesSignatures.clear(); affectedFiles = undefined; } @@ -361,7 +178,7 @@ namespace ts { // Get next batch of affected files currentAffectedFilesSignatures.clear(); - affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); + affectedFiles = state.getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); currentChangedFilePath = nextKey.value as Path; semanticDiagnosticsPerFile.delete(currentChangedFilePath); affectedFilesIndex = 0; @@ -500,250 +317,13 @@ namespace ts { /** * Get all the dependencies of the sourceFile */ - function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[] { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (compilerOptions.outFile || compilerOptions.out) { - return programOfThisState.getSourceFiles().map(getFileName); - } - - // If this is non module emit, or its a global file, it depends on all the source files - if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { - return programOfThisState.getSourceFiles().map(getFileName); - } - - // Get the references, traversing deep from the referenceMap - Debug.assert(!!referencedMap); - const seenMap = createMap(); - const queue = [sourceFile.path]; - while (queue.length) { - const path = queue.pop(); - if (!seenMap.has(path)) { - seenMap.set(path, true); - const references = referencedMap.get(path); - if (references) { - const iterator = references.keys(); - for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { - queue.push(value as Path); - } - } - } - } - - return flatMapIter(seenMap.keys(), path => { - const file = programOfThisState.getSourceFileByPath(path as Path); - if (file) { - return file.fileName; - } - return path; - }); - } - - function getFileName(sourceFile: SourceFile) { - return sourceFile.fileName; - } - - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } - - /** - * Returns if the shape of the signature has changed since last emit - * Note that it also updates the current signature as the latest signature for the file - */ - function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - Debug.assert(!!sourceFile); - - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { - return false; - } - - const info = fileInfos.get(sourceFile.path); - Debug.assert(!!info); - - const prevSignature = info.signature; - let latestSignature: string; - if (sourceFile.isDeclarationFile) { - latestSignature = sourceFile.version; - } - else { - const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); - if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = computeHash(emitOutput.outputFiles[0].text); - } - else { - latestSignature = prevSignature; - } - } - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - - return !prevSignature || latestSignature !== prevSignature; - } - - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { - let referencedFiles: Map | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const symbol = checker.getSymbolAtLocation(importName); - if (symbol && symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); - if (declarationSourceFile) { - addReferencedFile(declarationSourceFile.path); - } - } - } - } - - const sourceFileDirectory = getDirectoryPath(sourceFile.path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } - - return referencedFiles; - - function addReferencedFile(referencedPath: Path) { - if (!referencedFiles) { - referencedFiles = createMap(); - } - referencedFiles.set(referencedPath, true); - } - } - - /** - * Gets the files referenced by the the file path - */ - function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); - } - - /** - * Gets all files of the program excluding the default library file - */ - function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { - // Use cached result - if (allFilesExcludingDefaultLibraryFile) { - return allFilesExcludingDefaultLibraryFile; - } - - let result: SourceFile[]; - addSourceFile(firstSourceFile); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } - } - allFilesExcludingDefaultLibraryFile = result || emptyArray; - return allFilesExcludingDefaultLibraryFile; - - function addSourceFile(sourceFile: SourceFile) { - if (!program.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } - } - } - - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); - while (queue.length > 0) { - const currentPath = queue.pop(); - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { - queue.push(...getReferencedByPaths(currentPath)); - } - } - } - - // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); + function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) { + return state.getAllDependencies(programOfThisState, sourceFile); } } } namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; export interface BuilderHost { @@ -769,7 +349,7 @@ namespace ts { /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } /** diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index f6706195a75..eb3887eed15 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -46,7 +46,76 @@ namespace ts { return map1 === undefined; } // Has same size and every key is present in both maps - return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); + return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Map | undefined { + let referencedFiles: Map | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const symbol = checker.getSymbolAtLocation(importName); + if (symbol && symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); + if (declarationSourceFile) { + addReferencedFile(declarationSourceFile.path); + } + } + } + } + + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); + } + } + + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; + } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName; + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + + return referencedFiles; + + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); + } } export interface BuilderStateHost { @@ -76,6 +145,15 @@ namespace ts { * Gets the files affected by the file path */ getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray; + /** + * Updates the signatures from the cache + * This should be called whenever it is safe to commit the state of the builder + */ + updateSignaturesFromCache(signatureCache: Map): void; + /** + * Get all the dependencies of the sourceFile + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } export function createBuilderState(host: BuilderStateHost): BuilderState { @@ -121,11 +199,17 @@ namespace ts { * Cache of all files excluding default library file for the current program */ let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + /** + * Cache of all the file names + */ + let allFileNames: ReadonlyArray | undefined; return { updateProgram, getFilesAffectedBy, - }; + getAllDependencies, + updateSignaturesFromCache + }; /** * Update current state to reflect new program @@ -155,11 +239,12 @@ namespace ts { // Clear datas that cant be retained beyond previous state hasCalledUpdateShapeSignature.clear(); allFilesExcludingDefaultLibraryFile = undefined; + allFileNames = undefined; // Create the reference map and update changed files for (const sourceFile of newProgram.getSourceFiles()) { const version = sourceFile.version; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); const oldInfo = fileInfos.get(sourceFile.path); let oldReferences: ReferencedSet; @@ -174,7 +259,7 @@ namespace ts { // Referenced files changed !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || // Referenced file was deleted in the new program - newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { + newReferences && forEachKey(newReferences, path => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { // Changed file: Update the version, set as changed file oldInfo.version = version; @@ -230,6 +315,58 @@ namespace ts { return result; } + /** + * Get all the dependencies of the sourceFile + */ + function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return getAllFileNames(programOfThisState); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + return getAllFileNames(programOfThisState); + } + + // Get the references, traversing deep from the referenceMap + Debug.assert(!!referencedMap); + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop(); + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { + queue.push(value as Path); + } + } + } + } + + return flatMapIter(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + if (file) { + return file.fileName; + } + return path; + }); + } + + /** + * Gets the names of all files from the program + */ + function getAllFileNames(programOfThisState: Program): ReadonlyArray { + if (!allFileNames) { + allFileNames = programOfThisState.getSourceFiles().map(file => file.fileName); + } + return allFileNames; + } + /** * Updates the signatures from the cache * This should be called whenever it is safe to commit the state of the builder @@ -241,21 +378,6 @@ namespace ts { }); } - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } - /** * Returns if the shape of the signature has changed since last emit * Note that it also updates the current signature as the latest signature for the file @@ -290,60 +412,6 @@ namespace ts { return !prevSignature || latestSignature !== prevSignature; } - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { - let referencedFiles: Map | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const symbol = checker.getSymbolAtLocation(importName); - if (symbol && symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); - if (declarationSourceFile) { - addReferencedFile(declarationSourceFile.path); - } - } - } - } - - const sourceFileDirectory = getDirectoryPath(sourceFile.path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } - - return referencedFiles; - - function addReferencedFile(referencedPath: Path) { - if (!referencedFiles) { - referencedFiles = createMap(); - } - referencedFiles.set(referencedPath, true); - } - } - /** * Gets the files referenced by the the file path */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d7f3821a25d..32f4b78a8c5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3757,87 +3757,6 @@ declare namespace ts { declare namespace ts { function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer; } -declare namespace ts { - interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - type AffectedFileResult = { - result: T; - affected: SourceFile | Program; - } | undefined; - interface BuilderHost { - /** - * return true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames(): boolean; - /** - * If provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - } - /** - * Builder to manage the program state changes - */ - interface BaseBuilder { - /** - * Updates the program in the builder to represent new state - */ - updateProgram(newProgram: Program): void; - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; - } - /** - * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files - */ - interface SemanticDiagnosticsBuilder extends BaseBuilder { - /** - * Gets the semantic diagnostics from the program for the next affected file and caches it - * Returns undefined if the iteration is complete - */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - } - /** - * The builder that can handle the changes in program and iterate through changed file to emit the files - * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files - */ - interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - } - /** - * Create the builder to manage semantic diagnostics and cache them - */ - function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; - /** - * Create the builder that can handle the changes in program and iterate through changed files - * to emit the those files and manage semantic diagnostics cache as well - */ - function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; -} declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; function resolveTripleslashReference(moduleName: string, containingFile: string): string; @@ -7233,6 +7152,17 @@ declare namespace ts.server { onProjectClosed(project: Project): void; } } +declare namespace ts { + interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } +} declare namespace ts.server { enum ProjectKind { Inferred = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 06c75e843c0..514c140f05e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3704,6 +3704,35 @@ declare namespace ts { declare namespace ts { function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer; } +declare namespace ts { + function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; + function resolveTripleslashReference(moduleName: string, containingFile: string): string; + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; + function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + } + function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string; + function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; + /** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @returns A 'Program' object. + */ + function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; +} declare namespace ts { interface EmitOutput { outputFiles: OutputFile[]; @@ -3714,6 +3743,8 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } +} +declare namespace ts { type AffectedFileResult = { result: T; affected: SourceFile | Program; @@ -3739,7 +3770,7 @@ declare namespace ts { /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files @@ -3785,35 +3816,6 @@ declare namespace ts { */ function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; } -declare namespace ts { - function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; - function resolveTripleslashReference(moduleName: string, containingFile: string): string; - function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; - function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; - interface FormatDiagnosticsHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; - } - function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; - function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string; - function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; - function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param rootNames - A set of root files. - * @param options - The compiler options which should be used. - * @param host - The host interacts with the underlying file system. - * @param oldProgram - Reuses an old program structure. - * @returns A 'Program' object. - */ - function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; -} declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** From bb0fc0d2bcd88d99c148fbff650d6746b3bc7413 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 14:04:40 -0800 Subject: [PATCH 030/101] Convert builder state to mutable data, so that later we can create builder Program out of this --- src/compiler/builder.ts | 2 +- src/compiler/builderState.ts | 249 +++++++++++++++++++++++++++++++++-- src/server/project.ts | 20 +-- 3 files changed, 243 insertions(+), 28 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ff372479fad..198bf0556f0 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -13,7 +13,7 @@ namespace ts { /** * State corresponding to all the file references and shapes of the module etc */ - const state = createBuilderState({ + const state = createBuilderStateOld({ useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), createHash: host.createHash, onUpdateProgramInitialized, diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index eb3887eed15..0cd55ebd61f 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -136,7 +136,7 @@ namespace ts { onSourceFileRemoved(path: Path): void; } - export interface BuilderState { + export interface BuilderStateOld { /** * Updates the program in the builder to represent new state */ @@ -156,7 +156,65 @@ namespace ts { getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } - export function createBuilderState(host: BuilderStateHost): BuilderState { + export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: Map; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap: ReadonlyMap | undefined; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature: Map; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + /** + * Cache of all the file names + */ + allFileNames: ReadonlyArray | undefined; + } + + export function createBuilderState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: BuilderState): BuilderState { + const fileInfos = createMap(); + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; + const hasCalledUpdateShapeSignature = createMap(); + const useOldState = oldState && !!oldState.referencedMap !== !!referencedMap; + + // Create the reference map, and set the file infos + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + const oldInfo = useOldState && oldState.fileInfos.get(sourceFile.path); + if (referencedMap) { + const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + } + fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + } + + oldState = undefined; + newProgram = undefined; + + return { + fileInfos, + referencedMap, + hasCalledUpdateShapeSignature, + allFilesExcludingDefaultLibraryFile: undefined, + allFileNames: undefined + }; + } + + export function createBuilderStateOld(host: BuilderStateHost): BuilderStateOld { /** * Create the canonical file name for identity */ @@ -171,11 +229,6 @@ namespace ts { */ const fileInfos = createMap(); - /** - * true if module emit is enabled - */ - let isModuleEmit: boolean; - /** * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled * Otherwise undefined @@ -218,7 +271,7 @@ namespace ts { function updateProgram(newProgram: Program) { const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; const oldReferencedMap = referencedMap; - const isModuleEmitChanged = isModuleEmit !== newProgramHasModuleEmit; + const isModuleEmitChanged = !!referencedMap !== newProgramHasModuleEmit; if (isModuleEmitChanged) { // Changes in the module emit, clear out everything and initialize as if first time @@ -229,8 +282,7 @@ namespace ts { referencedMap = newProgramHasModuleEmit ? createMap() : undefined; // Update the module emit - isModuleEmit = newProgramHasModuleEmit; - getEmitDependentFilesAffectedBy = isModuleEmit ? + getEmitDependentFilesAffectedBy = newProgramHasModuleEmit ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit; } @@ -326,12 +378,11 @@ namespace ts { } // If this is non module emit, or its a global file, it depends on all the source files - if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + if (!referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { return getAllFileNames(programOfThisState); } // Get the references, traversing deep from the referenceMap - Debug.assert(!!referencedMap); const seenMap = createMap(); const queue = [sourceFile.path]; while (queue.length) { @@ -497,3 +548,177 @@ namespace ts { } } } + +/*@internal*/ +namespace ts.BuilderState { + type ComputeHash = (data: string) => string; + + /** + * Gets the files affected by the path from the program + */ + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash?: ComputeHash, cacheToUpdateSignature?: Map): ReadonlyArray { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be commited once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are commited only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; + } + + if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash)) { + return [sourceFile]; + } + + const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(state, signatureCache); + } + return result; + } + + /** + * Updates the signatures from the cache into state's fileinfo signatures + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateSignaturesFromCache(state: BuilderState, signatureCache: Map) { + signatureCache.forEach((signature, path) => { + state.fileInfos.get(path).signature = signature; + state.hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * Returns if the shape of the signature has changed since last emit + */ + function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) { + Debug.assert(!!sourceFile); + + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { + return false; + } + + const info = state.fileInfos.get(sourceFile.path); + Debug.assert(!!info); + + const prevSignature = info.signature; + let latestSignature: string; + if (sourceFile.isDeclarationFile) { + latestSignature = sourceFile.version; + } + else { + const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); + if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { + latestSignature = (computeHash || identity)(emitOutput.outputFiles[0].text); + } + else { + latestSignature = prevSignature; + } + } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + + return !prevSignature || latestSignature !== prevSignature; + } + + /** + * Gets the files referenced by the the file path + */ + function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { + return mapDefinedIter(state.referencedMap.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Gets all files of the program excluding the default library file + */ + function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): ReadonlyArray { + // Use cached result + if (state.allFilesExcludingDefaultLibraryFile) { + return state.allFilesExcludingDefaultLibraryFile; + } + + let result: SourceFile[]; + addSourceFile(firstSourceFile); + for (const sourceFile of programOfThisState.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); + } + } + state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + return state.allFilesExcludingDefaultLibraryFile; + + function addSourceFile(sourceFile: SourceFile) { + if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); + } + } + } + + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) { + if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.path); + 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)) { + queue.push(...getReferencedByPaths(state, currentPath)); + } + } + } + + // Return array of values that needs emit + return flatMapIter(seenFileNamesMap.values(), value => value); + } +} diff --git a/src/server/project.ts b/src/server/project.ts index a5542cb89e7..95c41eaaf44 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builder: BuilderState | undefined; + private builderState: BuilderState | undefined; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -460,18 +460,8 @@ namespace ts.server { return []; } this.updateGraph(); - if (!this.builder) { - this.builder = createBuilderState({ - useCaseSensitiveFileNames: this.useCaseSensitiveFileNames(), - createHash: data => this.projectService.host.createHash(data), - onUpdateProgramInitialized: noop, - onSourceFileAdd: noop, - onSourceFileChanged: noop, - onSourceFileRemoved: noop - }); - } - this.builder.updateProgram(this.program); - return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path, this.cancellationToken), + this.builderState = createBuilderState(this.program, this.projectService.toCanonicalFileName, this.builderState); + return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash(data)), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } @@ -507,7 +497,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; - this.builder = undefined; + this.builderState = undefined; this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -548,7 +538,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.externalFiles = undefined; this.program = undefined; - this.builder = undefined; + this.builderState = undefined; this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; From 965f40f2132a9aa09c6647eecc6f12b0800e7aaa Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 16:46:36 -0800 Subject: [PATCH 031/101] Use builder state in the semantic/emit builder as well --- src/compiler/builder.ts | 204 ++++--- src/compiler/builderState.ts | 540 ++++-------------- src/server/project.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 2 +- 4 files changed, 219 insertions(+), 529 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 198bf0556f0..110bdd5130a 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -7,52 +7,118 @@ namespace ts { BuilderKindEmitAndSemanticDiagnostics } - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { - /** - * State corresponding to all the file references and shapes of the module etc - */ - const state = createBuilderStateOld({ - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), - createHash: host.createHash, - onUpdateProgramInitialized, - onSourceFileAdd: addToChangedFilesSet, - onSourceFileChanged: path => { addToChangedFilesSet(path); deleteSemanticDiagnostics(path); }, - onSourceFileRemoved: deleteSemanticDiagnostics - }); - + interface BuilderStateWithChangedFiles extends BuilderState { /** * Cache of semantic diagnostics for files with their Path being the key */ - const semanticDiagnosticsPerFile = createMap>(); - + semanticDiagnosticsPerFile: Map> | undefined; /** * The map has key by source file's path that has been changed */ - const changedFilesSet = createMap(); - + changedFilesSet: Map; /** * Set of affected files being iterated */ - let affectedFiles: ReadonlyArray | undefined; + affectedFiles: ReadonlyArray | undefined; /** * Current index to retrieve affected file from */ - let affectedFilesIndex = 0; + affectedFilesIndex: number | undefined; /** * Current changed file for iterating over affected files */ - let currentChangedFilePath: Path | undefined; + currentChangedFilePath: Path | undefined; /** * Map of file signatures, with key being file path, calculated while getting current changed file's affected files * These will be commited whenever the iteration through affected files of current changed file is complete */ - const currentAffectedFilesSignatures = createMap(); + currentAffectedFilesSignatures: Map | undefined; /** * Already seen affected files */ - const seenAffectedFiles = createMap(); + seenAffectedFiles: Map | undefined; + } + + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { + if (map1 === undefined) { + return map2 === undefined; + } + if (map2 === undefined) { + return map1 === undefined; + } + // Has same size and every key is present in both maps + return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); + } + + /** + * Create the state so that we can iterate on changedFiles/affected files + */ + function createBuilderStateWithChangedFiles(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderStateWithChangedFiles { + const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderStateWithChangedFiles; + const compilerOptions = newProgram.getCompilerOptions(); + if (!compilerOptions.outFile && !compilerOptions.out) { + state.semanticDiagnosticsPerFile = createMap>(); + } + state.changedFilesSet = createMap(); + const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); + const canCopySemanticDiagnostics = useOldState && oldState.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile; + if (useOldState) { + // Verify the sanity of old state + if (!oldState.currentChangedFilePath) { + Debug.assert(!oldState.affectedFiles && (!oldState.currentAffectedFilesSignatures || !oldState.currentAffectedFilesSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); + } + if (canCopySemanticDiagnostics) { + Debug.assert(!forEachKey(oldState.changedFilesSet, path => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + } + + // Copy old state's changed files set + copyEntries(oldState.changedFilesSet, state.changedFilesSet); + } + + // Update changed files and copy semantic diagnostics if we can + const referencedMap = state.referencedMap; + const oldReferencedMap = useOldState && oldState.referencedMap; + state.fileInfos.forEach((info, sourceFilePath) => { + let oldInfo: Readonly; + let newReferences: BuilderState.ReferencedSet; + + // if not using old state, every file is changed + if (!useOldState || + // File wasnt present in old state + !(oldInfo = oldState.fileInfos.get(sourceFilePath)) || + // versions dont match + oldInfo.version !== info.version || + // Referenced files changed + !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) || + // Referenced file was deleted in the new program + newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState.fileInfos.has(path))) { + // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated + state.changedFilesSet.set(sourceFilePath, true); + } + else if (canCopySemanticDiagnostics) { + // Unchanged file copy diagnostics + const diagnostics = oldState.semanticDiagnosticsPerFile.get(sourceFilePath); + if (diagnostics) { + state.semanticDiagnosticsPerFile.set(sourceFilePath, diagnostics); + } + } + }); + + return state; + } + + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = host.createHash || identity; + let state: BuilderStateWithChangedFiles; switch (builderKind) { case BuilderKind.BuilderKindSemanticDiagnostics: @@ -81,55 +147,12 @@ namespace ts { }; } - /** - * Initialize changedFiles, affected files set, cached diagnostics, signatures - */ - function onUpdateProgramInitialized(isModuleEmitChanged: boolean) { - if (isModuleEmitChanged) { - // Changes in the module emit, clear out everything and initialize as if first time - - // Clear file information and semantic diagnostics - semanticDiagnosticsPerFile.clear(); - - // Clear changed files and affected files information - changedFilesSet.clear(); - affectedFiles = undefined; - currentChangedFilePath = undefined; - currentAffectedFilesSignatures.clear(); - } - else { - if (currentChangedFilePath) { - // Remove the diagnostics for all the affected files since we should resume the state such that - // the whole iteration on currentChangedFile never happened - affectedFiles.forEach(sourceFile => deleteSemanticDiagnostics(sourceFile.path)); - affectedFiles = undefined; - currentAffectedFilesSignatures.clear(); - } - else { - // Verify the sanity of old state - Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated"); - } - Debug.assert(!forEachKey(changedFilesSet, path => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - } - } - - /** - * Add file to the changed files set - */ - function addToChangedFilesSet(path: Path) { - changedFilesSet.set(path, true); - } - - function deleteSemanticDiagnostics(path: Path) { - semanticDiagnosticsPerFile.delete(path); - } - /** * Update current state to reflect new program * Updates changed files, references, file infos etc which happens through the state callbacks */ function updateProgram(newProgram: Program) { - state.updateProgram(newProgram); + state = createBuilderStateWithChangedFiles(newProgram, getCanonicalFileName, state); } /** @@ -140,11 +163,15 @@ namespace ts { */ function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined { while (true) { + const { affectedFiles } = state; if (affectedFiles) { + const { seenAffectedFiles, semanticDiagnosticsPerFile } = state; + let { affectedFilesIndex } = state; while (affectedFilesIndex < affectedFiles.length) { const affectedFile = affectedFiles[affectedFilesIndex]; if (!seenAffectedFiles.has(affectedFile.path)) { // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; semanticDiagnosticsPerFile.delete(affectedFile.path); return affectedFile; } @@ -153,16 +180,16 @@ namespace ts { } // Remove the changed file from the change set - changedFilesSet.delete(currentChangedFilePath); - currentChangedFilePath = undefined; + state.changedFilesSet.delete(state.currentChangedFilePath); + state.currentChangedFilePath = undefined; // Commit the changes in file signature - state.updateSignaturesFromCache(currentAffectedFilesSignatures); - currentAffectedFilesSignatures.clear(); - affectedFiles = undefined; + BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures); + state.currentAffectedFilesSignatures.clear(); + state.affectedFiles = undefined; } // Get next changed file - const nextKey = changedFilesSet.keys().next(); + const nextKey = state.changedFilesSet.keys().next(); if (nextKey.done) { // Done return undefined; @@ -172,16 +199,17 @@ namespace ts { // With --out or --outFile all outputs go into single file // so operations are performed directly on program, return program if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(semanticDiagnosticsPerFile.size === 0); + Debug.assert(!state.semanticDiagnosticsPerFile); return programOfThisState; } // Get next batch of affected files - currentAffectedFilesSignatures.clear(); - affectedFiles = state.getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); - currentChangedFilePath = nextKey.value as Path; - semanticDiagnosticsPerFile.delete(currentChangedFilePath); - affectedFilesIndex = 0; + state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); + state.affectedFiles = BuilderState.getFilesAffectedBy(state, programOfThisState, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures); + state.currentChangedFilePath = nextKey.value as Path; + state.semanticDiagnosticsPerFile.delete(nextKey.value as Path); + state.affectedFilesIndex = 0; + state.seenAffectedFiles = state.seenAffectedFiles || createMap(); } } @@ -191,11 +219,11 @@ namespace ts { */ function doneWithAffectedFile(programOfThisState: Program, affected: SourceFile | Program) { if (affected === programOfThisState) { - changedFilesSet.clear(); + state.changedFilesSet.clear(); } else { - seenAffectedFiles.set((affected).path, true); - affectedFilesIndex++; + state.seenAffectedFiles.set((affected as SourceFile).path, true); + state.affectedFilesIndex++; } } @@ -277,10 +305,10 @@ namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { - Debug.assert(!affectedFiles || affectedFiles[affectedFilesIndex - 1] !== sourceFile || !semanticDiagnosticsPerFile.has(sourceFile.path)); + Debug.assert(!state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path)); const compilerOptions = programOfThisState.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(semanticDiagnosticsPerFile.size === 0); + Debug.assert(!state.semanticDiagnosticsPerFile); // We dont need to cache the diagnostics just return them from program return programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken); } @@ -302,7 +330,7 @@ namespace ts { */ function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { const path = sourceFile.path; - const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); // Report the semantic diagnostics from the cache if we already have those diagnostics present if (cachedDiagnostics) { return cachedDiagnostics; @@ -310,7 +338,7 @@ namespace ts { // Diagnostics werent cached, get them from program, and cache the result const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); - semanticDiagnosticsPerFile.set(path, diagnostics); + state.semanticDiagnosticsPerFile.set(path, diagnostics); return diagnostics; } @@ -318,7 +346,7 @@ namespace ts { * Get all the dependencies of the sourceFile */ function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) { - return state.getAllDependencies(programOfThisState, sourceFile); + return BuilderState.getAllDependencies(state, programOfThisState, sourceFile); } } } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 0cd55ebd61f..79ba3767b93 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -25,44 +25,51 @@ namespace ts { } } + export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: Map; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap: ReadonlyMap | undefined; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature: Map; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + /** + * Cache of all the file names + */ + allFileNames: ReadonlyArray | undefined; + } +} + +/*@internal*/ +namespace ts.BuilderState { /** * Information about the source file: Its version and optional signature from last emit */ - interface FileInfo { - version: string; - signature?: string; + export interface FileInfo { + readonly version: string; + signature: string | undefined; } - /** * Referenced files with values for the keys as referenced file's path to be true */ - type ReferencedSet = ReadonlyMap; - - function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { - if (map1 === undefined) { - return map2 === undefined; - } - if (map2 === undefined) { - return map1 === undefined; - } - // Has same size and every key is present in both maps - return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); - } - + export type ReferencedSet = ReadonlyMap; /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. + * Compute the hash to store the shape of the file */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } + export type ComputeHash = (data: string) => string; /** * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true @@ -118,76 +125,21 @@ namespace ts { } } - export interface BuilderStateHost { - /** - * true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames: boolean; - /** - * if provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - /** - * Called when programState is initialized, indicating if isModuleEmit is changed - */ - onUpdateProgramInitialized(isModuleEmitChanged: boolean): void; - onSourceFileAdd(path: Path): void; - onSourceFileChanged(path: Path): void; - onSourceFileRemoved(path: Path): void; + /** + * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed + */ + export function canReuseOldState(newReferencedMap: ReadonlyMap, oldState: Readonly | undefined) { + return oldState && !oldState.referencedMap === !newReferencedMap; } - export interface BuilderStateOld { - /** - * Updates the program in the builder to represent new state - */ - updateProgram(newProgram: Program): void; - /** - * Gets the files affected by the file path - */ - getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray; - /** - * Updates the signatures from the cache - * This should be called whenever it is safe to commit the state of the builder - */ - updateSignaturesFromCache(signatureCache: Map): void; - /** - * Get all the dependencies of the sourceFile - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; - } - - export interface BuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: Map; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap: ReadonlyMap | undefined; - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - hasCalledUpdateShapeSignature: Map; - /** - * Cache of all files excluding default library file for the current program - */ - allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - /** - * Cache of all the file names - */ - allFileNames: ReadonlyArray | undefined; - } - - export function createBuilderState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: BuilderState): BuilderState { + /** + * Creates the state of file references and signature for the new program from oldState if it is safe + */ + export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderState { const fileInfos = createMap(); const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; const hasCalledUpdateShapeSignature = createMap(); - const useOldState = oldState && !!oldState.referencedMap !== !!referencedMap; + const useOldState = canReuseOldState(referencedMap, oldState); // Create the reference map, and set the file infos for (const sourceFile of newProgram.getSourceFiles()) { @@ -202,9 +154,6 @@ namespace ts { fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); } - oldState = undefined; - newProgram = undefined; - return { fileInfos, referencedMap, @@ -214,349 +163,10 @@ namespace ts { }; } - export function createBuilderStateOld(host: BuilderStateHost): BuilderStateOld { - /** - * Create the canonical file name for identity - */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - /** - * Computing hash to for signature verification - */ - const computeHash = host.createHash || identity; - - /** - * Information of the file eg. its version, signature etc - */ - const fileInfos = createMap(); - - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - */ - let referencedMap: Map | undefined; - - /** - * Get the files affected by the source file. - * This is dependent on whether its a module emit or not and hence function expression - */ - let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; - - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - const hasCalledUpdateShapeSignature = createMap(); - - /** - * Cache of all files excluding default library file for the current program - */ - let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - /** - * Cache of all the file names - */ - let allFileNames: ReadonlyArray | undefined; - - return { - updateProgram, - getFilesAffectedBy, - getAllDependencies, - updateSignaturesFromCache - }; - - /** - * Update current state to reflect new program - * Updates changed files, references, file infos etc - */ - function updateProgram(newProgram: Program) { - const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; - const oldReferencedMap = referencedMap; - const isModuleEmitChanged = !!referencedMap !== newProgramHasModuleEmit; - if (isModuleEmitChanged) { - // Changes in the module emit, clear out everything and initialize as if first time - - // Clear file information - fileInfos.clear(); - - // Update the reference map creation - referencedMap = newProgramHasModuleEmit ? createMap() : undefined; - - // Update the module emit - getEmitDependentFilesAffectedBy = newProgramHasModuleEmit ? - getFilesAffectedByUpdatedShapeWhenModuleEmit : - getFilesAffectedByUpdatedShapeWhenNonModuleEmit; - } - host.onUpdateProgramInitialized(isModuleEmitChanged); - - // Clear datas that cant be retained beyond previous state - hasCalledUpdateShapeSignature.clear(); - allFilesExcludingDefaultLibraryFile = undefined; - allFileNames = undefined; - - // Create the reference map and update changed files - for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); - const oldInfo = fileInfos.get(sourceFile.path); - let oldReferences: ReferencedSet; - - // Register changed file if its new file or we arent reusing old state - if (!oldInfo) { - // New file: Set the file info - fileInfos.set(sourceFile.path, { version }); - host.onSourceFileAdd(sourceFile.path); - } - // versions dont match - else if (oldInfo.version !== version || - // Referenced files changed - !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || - // Referenced file was deleted in the new program - newReferences && forEachKey(newReferences, path => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { - - // Changed file: Update the version, set as changed file - oldInfo.version = version; - host.onSourceFileChanged(sourceFile.path); - } - - // Set the references - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - else if (referencedMap) { - referencedMap.delete(sourceFile.path); - } - } - - // For removed files, remove the semantic diagnostics and file info - if (fileInfos.size > newProgram.getSourceFiles().length) { - fileInfos.forEach((_value, path) => { - if (!newProgram.getSourceFileByPath(path as Path)) { - fileInfos.delete(path); - host.onSourceFileRemoved(path as Path); - if (referencedMap) { - referencedMap.delete(path); - } - } - }); - } - } - - /** - * Gets the files affected by the path from the program - */ - function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be commited once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are commited only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || createMap(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; - } - - if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { - return [sourceFile]; - } - - const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(signatureCache); - } - return result; - } - - /** - * Get all the dependencies of the sourceFile - */ - function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (compilerOptions.outFile || compilerOptions.out) { - return getAllFileNames(programOfThisState); - } - - // If this is non module emit, or its a global file, it depends on all the source files - if (!referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { - return getAllFileNames(programOfThisState); - } - - // Get the references, traversing deep from the referenceMap - const seenMap = createMap(); - const queue = [sourceFile.path]; - while (queue.length) { - const path = queue.pop(); - if (!seenMap.has(path)) { - seenMap.set(path, true); - const references = referencedMap.get(path); - if (references) { - const iterator = references.keys(); - for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { - queue.push(value as Path); - } - } - } - } - - return flatMapIter(seenMap.keys(), path => { - const file = programOfThisState.getSourceFileByPath(path as Path); - if (file) { - return file.fileName; - } - return path; - }); - } - - /** - * Gets the names of all files from the program - */ - function getAllFileNames(programOfThisState: Program): ReadonlyArray { - if (!allFileNames) { - allFileNames = programOfThisState.getSourceFiles().map(file => file.fileName); - } - return allFileNames; - } - - /** - * Updates the signatures from the cache - * This should be called whenever it is safe to commit the state of the builder - */ - function updateSignaturesFromCache(signatureCache: Map) { - signatureCache.forEach((signature, path) => { - fileInfos.get(path).signature = signature; - hasCalledUpdateShapeSignature.set(path, true); - }); - } - - /** - * Returns if the shape of the signature has changed since last emit - * Note that it also updates the current signature as the latest signature for the file - */ - function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - Debug.assert(!!sourceFile); - - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { - return false; - } - - const info = fileInfos.get(sourceFile.path); - Debug.assert(!!info); - - const prevSignature = info.signature; - let latestSignature: string; - if (sourceFile.isDeclarationFile) { - latestSignature = sourceFile.version; - } - else { - const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); - if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = computeHash(emitOutput.outputFiles[0].text); - } - else { - latestSignature = prevSignature; - } - } - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - - return !prevSignature || latestSignature !== prevSignature; - } - - /** - * Gets the files referenced by the the file path - */ - function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); - } - - /** - * Gets all files of the program excluding the default library file - */ - function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { - // Use cached result - if (allFilesExcludingDefaultLibraryFile) { - return allFilesExcludingDefaultLibraryFile; - } - - let result: SourceFile[]; - addSourceFile(firstSourceFile); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } - } - allFilesExcludingDefaultLibraryFile = result || emptyArray; - return allFilesExcludingDefaultLibraryFile; - - function addSourceFile(sourceFile: SourceFile) { - if (!program.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } - } - } - - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); - while (queue.length > 0) { - const currentPath = queue.pop(); - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { - queue.push(...getReferencedByPaths(currentPath)); - } - } - } - - // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); - } - } -} - -/*@internal*/ -namespace ts.BuilderState { - type ComputeHash = (data: string) => string; - /** * Gets the files affected by the path from the program */ - export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash?: ComputeHash, cacheToUpdateSignature?: Map): ReadonlyArray { + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: Map): ReadonlyArray { // Since the operation could be cancelled, the signatures are always stored in the cache // They will be commited once it is safe to use them // eg when calling this api from tsserver, if there is no cancellation of the operation @@ -593,7 +203,7 @@ namespace ts.BuilderState { /** * Returns if the shape of the signature has changed since last emit */ - function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) { + function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash) { Debug.assert(!!sourceFile); // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate @@ -612,7 +222,7 @@ namespace ts.BuilderState { else { const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = (computeHash || identity)(emitOutput.outputFiles[0].text); + latestSignature = computeHash(emitOutput.outputFiles[0].text); } else { latestSignature = prevSignature; @@ -623,6 +233,58 @@ namespace ts.BuilderState { return !prevSignature || latestSignature !== prevSignature; } + /** + * Get all the dependencies of the sourceFile + */ + export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return getAllFileNames(state, programOfThisState); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!state.referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + return getAllFileNames(state, programOfThisState); + } + + // Get the references, traversing deep from the referenceMap + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop(); + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = state.referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { + queue.push(value as Path); + } + } + } + } + + return flatMapIter(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + if (file) { + return file.fileName; + } + return path; + }); + } + + /** + * Gets the names of all files from the program + */ + function getAllFileNames(state: BuilderState, programOfThisState: Program): ReadonlyArray { + if (!state.allFileNames) { + const sourceFiles = programOfThisState.getSourceFiles(); + state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); + } + return state.allFileNames; + } + /** * Gets the files referenced by the the file path */ diff --git a/src/server/project.ts b/src/server/project.ts index 95c41eaaf44..b4e15774cfb 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -460,7 +460,7 @@ namespace ts.server { return []; } this.updateGraph(); - this.builderState = createBuilderState(this.program, this.projectService.toCanonicalFileName, this.builderState); + this.builderState = BuilderState.create(this.program, this.projectService.toCanonicalFileName, this.builderState); return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash(data)), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 32f4b78a8c5..575c1dc07ce 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7217,7 +7217,7 @@ declare namespace ts.server { languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; - private builder; + private builderState; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ From dc62bb9abc28aed804b055db3c8e45b24eda336b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 18:55:11 -0800 Subject: [PATCH 032/101] Change builder to BuilderProgram so it is similar to operating on program --- src/compiler/builder.ts | 519 ++++++++++-------- src/compiler/tsconfig.json | 1 + src/compiler/watch.ts | 163 ++---- src/harness/unittests/builder.ts | 14 +- src/services/tsconfig.json | 1 + tests/baselines/reference/api/typescript.d.ts | 100 ++-- 6 files changed, 417 insertions(+), 381 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 110bdd5130a..21c3b9021ee 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -2,12 +2,10 @@ /*@internal*/ namespace ts { - export enum BuilderKind { - BuilderKindSemanticDiagnostics, - BuilderKindEmitAndSemanticDiagnostics - } - - interface BuilderStateWithChangedFiles extends BuilderState { + /** + * State to store the changed files, affected files and cache semantic diagnostics + */ + export interface BuilderProgramState extends BuilderState { /** * Cache of semantic diagnostics for files with their Path being the key */ @@ -37,6 +35,10 @@ namespace ts { * Already seen affected files */ seenAffectedFiles: Map | undefined; + /** + * program corresponding to this state + */ + program: Program; } function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { @@ -53,8 +55,9 @@ namespace ts { /** * Create the state so that we can iterate on changedFiles/affected files */ - function createBuilderStateWithChangedFiles(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderStateWithChangedFiles { - const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderStateWithChangedFiles; + function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderProgramState { + const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState; + state.program = newProgram; const compilerOptions = newProgram.getCompilerOptions(); if (!compilerOptions.outFile && !compilerOptions.out) { state.semanticDiagnosticsPerFile = createMap>(); @@ -107,9 +110,119 @@ namespace ts { return state; } - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { + /** + * Verifies that source file is ok to be used in calls that arent handled by next + */ + function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { + Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path)); + } + + /** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ + function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { + while (true) { + const { affectedFiles } = state; + if (affectedFiles) { + const { seenAffectedFiles, semanticDiagnosticsPerFile } = state; + let { affectedFilesIndex } = state; + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + if (!seenAffectedFiles.has(affectedFile.path)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; + semanticDiagnosticsPerFile.delete(affectedFile.path); + return affectedFile; + } + seenAffectedFiles.set(affectedFile.path, true); + affectedFilesIndex++; + } + + // Remove the changed file from the change set + state.changedFilesSet.delete(state.currentChangedFilePath); + state.currentChangedFilePath = undefined; + // Commit the changes in file signature + BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures); + state.currentAffectedFilesSignatures.clear(); + state.affectedFiles = undefined; + } + + // Get next changed file + const nextKey = state.changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + const compilerOptions = state.program.getCompilerOptions(); + if (compilerOptions.outFile || compilerOptions.out) { + Debug.assert(!state.semanticDiagnosticsPerFile); + return state.program; + } + + // Get next batch of affected files + state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); + state.affectedFiles = BuilderState.getFilesAffectedBy(state, state.program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures); + state.currentChangedFilePath = nextKey.value as Path; + state.semanticDiagnosticsPerFile.delete(nextKey.value as Path); + state.affectedFilesIndex = 0; + state.seenAffectedFiles = state.seenAffectedFiles || createMap(); + } + } + + /** + * 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) { + if (affected === state.program) { + state.changedFilesSet.clear(); + } + else { + state.seenAffectedFiles.set((affected as SourceFile).path, true); + state.affectedFilesIndex++; + } + } + + /** + * Returns the result with affected file + */ + function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { + doneWithAffectedFile(state, affected); + return { result, affected }; + } + + /** + * Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set + */ + function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + const path = sourceFile.path; + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); + // Report the semantic diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + return cachedDiagnostics; + } + + // Diagnostics werent cached, get them from program, and cache the result + const diagnostics = state.program.getSemanticDiagnostics(sourceFile, cancellationToken); + state.semanticDiagnosticsPerFile.set(path, diagnostics); + return diagnostics; + } + + export enum BuilderProgramKind { + SemanticDiagnosticsBuilderProgram, + EmitAndSemanticDiagnosticsBuilderProgram + } + + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind) { /** * Create the canonical file name for identity */ @@ -118,183 +231,129 @@ namespace ts { * Computing hash to for signature verification */ const computeHash = host.createHash || identity; - let state: BuilderStateWithChangedFiles; + const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldProgram && oldProgram.getState()); - switch (builderKind) { - case BuilderKind.BuilderKindSemanticDiagnostics: - return getSemanticDiagnosticsBuilder(); - case BuilderKind.BuilderKindEmitAndSemanticDiagnostics: - return getEmitAndSemanticDiagnosticsBuilder(); - default: - notImplemented(); + // To ensure that we arent storing any references to old program or new program without state + newProgram = undefined; + oldProgram = undefined; + + const result: BaseBuilderProgram = { + getState: () => state, + 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), + getSyntacticDiagnostics: (sourceFile, cancellationToken) => state.program.getSyntacticDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics, + emit, + getAllDependencies: sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile) + }; + + if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { + (result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + } + else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (result as EmitAndSemanticDiagnosticsBuilderProgram).getCurrentDirectory = () => state.program.getCurrentDirectory(); + (result as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; + } + else { + notImplemented(); } - function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder { - return { - updateProgram, - getAllDependencies, - getSemanticDiagnosticsOfNextAffectedFile, - getSemanticDiagnostics - }; - } - - function getEmitAndSemanticDiagnosticsBuilder(): EmitAndSemanticDiagnosticsBuilder { - return { - updateProgram, - getAllDependencies, - emitNextAffectedFile, - getSemanticDiagnostics - }; - } + return result; /** - * Update current state to reflect new program - * Updates changed files, references, file infos etc which happens through the state callbacks + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - function updateProgram(newProgram: Program) { - state = createBuilderStateWithChangedFiles(newProgram, getCanonicalFileName, state); - } - - /** - * This function returns the next affected file to be processed. - * Note that until doneAffected is called it would keep reporting same result - * This is to allow the callers to be able to actually remove affected file only when the operation is complete - * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained - */ - function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined { - while (true) { - const { affectedFiles } = state; - if (affectedFiles) { - const { seenAffectedFiles, semanticDiagnosticsPerFile } = state; - let { affectedFilesIndex } = state; - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - if (!seenAffectedFiles.has(affectedFile.path)) { - // Set the next affected file as seen and remove the cached semantic diagnostics - state.affectedFilesIndex = affectedFilesIndex; - semanticDiagnosticsPerFile.delete(affectedFile.path); - return affectedFile; - } - seenAffectedFiles.set(affectedFile.path, true); - affectedFilesIndex++; - } - - // Remove the changed file from the change set - state.changedFilesSet.delete(state.currentChangedFilePath); - state.currentChangedFilePath = undefined; - // Commit the changes in file signature - BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures); - state.currentAffectedFilesSignatures.clear(); - state.affectedFiles = undefined; - } - - // Get next changed file - const nextKey = state.changedFilesSet.keys().next(); - if (nextKey.done) { - // Done - return undefined; - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file - // so operations are performed directly on program, return program - if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(!state.semanticDiagnosticsPerFile); - return programOfThisState; - } - - // Get next batch of affected files - state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); - state.affectedFiles = BuilderState.getFilesAffectedBy(state, programOfThisState, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures); - state.currentChangedFilePath = nextKey.value as Path; - state.semanticDiagnosticsPerFile.delete(nextKey.value as Path); - state.affectedFilesIndex = 0; - state.seenAffectedFiles = state.seenAffectedFiles || createMap(); - } - } - - /** - * 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(programOfThisState: Program, affected: SourceFile | Program) { - if (affected === programOfThisState) { - state.changedFilesSet.clear(); - } - else { - state.seenAffectedFiles.set((affected as SourceFile).path, true); - state.affectedFilesIndex++; - } - } - - /** - * Returns the result with affected file - */ - function toAffectedFileResult(programOfThisState: Program, result: T, affected: SourceFile | Program): AffectedFileResult { - doneWithAffectedFile(programOfThisState, affected); - return { result, affected }; - } - - /** - * Emits the next affected file, and returns the EmitResult along with source files emitted - * Returns undefined when iteration is complete - */ - function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult { - const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); - if (!affectedFile) { + function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { + const affected = getNextAffectedFile(state, cancellationToken, computeHash); + if (!affected) { // Done return undefined; } - else if (affectedFile === programOfThisState) { - // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - return toAffectedFileResult( - programOfThisState, - programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), - programOfThisState - ); - } - // Emit the affected file - const targetSourceFile = affectedFile as SourceFile; return toAffectedFileResult( - programOfThisState, - programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), - targetSourceFile + 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 ); } + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); + if (!targetSourceFile) { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapData[] = []; + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + let emittedFiles: string[] = []; + + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); + } + return { + emitSkipped, + diagnostics: diagnostics || emptyArray, + emittedFiles, + sourceMaps + }; + } + } + return state.program.emit(targetSourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + } + /** * Return the semantic diagnostics for the next affected file or undefined if iteration is complete * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true */ - function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { + function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { while (true) { - const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); - if (!affectedFile) { + const affected = getNextAffectedFile(state, cancellationToken, computeHash); + if (!affected) { // Done return undefined; } - else if (affectedFile === programOfThisState) { + else if (affected === state.program) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) return toAffectedFileResult( - programOfThisState, - programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), - programOfThisState + state, + state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), + affected ); } // Get diagnostics for the affected file if its not ignored - const targetSourceFile = affectedFile as SourceFile; - if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) { + if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { // Get next affected file - doneWithAffectedFile(programOfThisState, targetSourceFile); + doneWithAffectedFile(state, affected); continue; } return toAffectedFileResult( - programOfThisState, - getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken), - targetSourceFile + state, + getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken), + affected ); } } @@ -302,59 +361,46 @@ namespace ts { /** * 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 - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics */ - function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { - Debug.assert(!state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path)); - const compilerOptions = programOfThisState.getCompilerOptions(); + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); + const compilerOptions = 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 programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken); + return state.program.getSemanticDiagnostics(sourceFile, cancellationToken); } if (sourceFile) { - return getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken); + 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); + } } let diagnostics: Diagnostic[]; - for (const sourceFile of programOfThisState.getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken)); + for (const sourceFile of state.program.getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); } return diagnostics || emptyArray; } - - /** - * Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set - */ - function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { - const path = sourceFile.path; - const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); - // Report the semantic diagnostics from the cache if we already have those diagnostics present - if (cachedDiagnostics) { - return cachedDiagnostics; - } - - // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); - state.semanticDiagnosticsPerFile.set(path, diagnostics); - return diagnostics; - } - - /** - * Get all the dependencies of the sourceFile - */ - function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) { - return BuilderState.getAllDependencies(state, programOfThisState, sourceFile); - } } } namespace ts { export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - export interface BuilderHost { + export interface BuilderProgramHost { /** * return true if file names are treated with case sensitivity */ @@ -363,73 +409,110 @@ namespace ts { * If provided this would be used this hash instead of actual file shape text for detecting changes */ createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; } /** * Builder to manage the program state changes */ - export interface BaseBuilder { + export interface BaseBuilderProgram { + /*@internal*/ + getState(): BuilderProgramState; /** - * Updates the program in the builder to represent new state + * Get compiler options of the program */ - updateProgram(newProgram: Program): void; - + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + /** + * 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 + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - export interface SemanticDiagnosticsBuilder extends BaseBuilder { + export interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; - - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; } /** * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - export interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { + export interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; } /** * Create the builder to manage semantic diagnostics and cache them */ - export function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder { - return createBuilder(host, BuilderKind.BuilderKindSemanticDiagnostics); + export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram { + return createBuilderProgram(newProgram, host, oldProgram, BuilderProgramKind.SemanticDiagnosticsBuilderProgram); } /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - export function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder { - return createBuilder(host, BuilderKind.BuilderKindEmitAndSemanticDiagnostics); + export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram { + return createBuilderProgram(newProgram, host, oldProgram, BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram); } } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 07f69ddfe28..92d9f099441 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -38,6 +38,7 @@ "emitter.ts", "watchUtilities.ts", "program.ts", + "builderState.ts", "builder.ts", "resolutionCache.ts", "watch.ts", diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 1c5a0ad5d99..c07722cb7d3 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -146,62 +146,23 @@ namespace ts { return ExitStatus.Success; } - /** - * Creates the function that emits files and reports errors when called with program - */ - function createEmitFilesAndReportErrorsWithBuilderUsingSystem(system: System, reportDiagnostic: DiagnosticReporter) { - const emitErrorsAndReportErrorsWithBuilder = createEmitFilesAndReportErrorsWithBuilder({ - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - createHash: system.createHash && (s => system.createHash(s)), - writeFile, - reportDiagnostic, - writeFileName: s => system.write(s + system.newLine) - }); - let host: CachedDirectoryStructureHost | undefined; - return { - emitFilesAndReportError: (program: Program) => emitErrorsAndReportErrorsWithBuilder(program), - setHost: (cachedDirectoryStructureHost: CachedDirectoryStructureHost) => host = cachedDirectoryStructureHost - }; - - function getHost() { - return host || system; - } - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !getHost().directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - getHost().createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - getHost().writeFile(fileName, text, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } - } - const noopFileWatcher: FileWatcher = { close: noop }; /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter): WatchCompilerHost { - const { emitFilesAndReportError, setHost } = createEmitFilesAndReportErrorsWithBuilderUsingSystem(system, reportDiagnostic); - const host: WatchCompilerHost = { - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + let host: DirectoryStructureHost = system; + const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames; + const writeFileName = (s: string) => system.write(s + system.newLine); + const builderProgramHost: BuilderProgramHost = { + useCaseSensitiveFileNames, + createHash: system.createHash && (s => system.createHash(s)), + writeFile + }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; + return { + useCaseSensitiveFileNames, getNewLine: () => system.newLine, getCurrentDirectory: () => system.getCurrentDirectory(), getDefaultLibLocation, @@ -220,10 +181,9 @@ namespace ts { onWatchStatusChange, createDirectory: path => system.createDirectory(path), writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), - onCachedDirectoryStructureHostCreate: host => setHost(host), - afterProgramCreate: emitFilesAndReportError, + onCachedDirectoryStructureHostCreate: cacheHost => host = cacheHost || system, + afterProgramCreate: emitFilesAndReportErrorUsingBuilder, }; - return host; function getDefaultLibLocation() { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); @@ -235,6 +195,36 @@ namespace ts { } system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); } + + function emitFilesAndReportErrorUsingBuilder(program: Program) { + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, builderProgramHost, builderProgram); + emitFilesAndReportErrors(builderProgram, reportDiagnostic, writeFileName); + } + + 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); + } + } + } } /** @@ -272,73 +262,6 @@ namespace ts { namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - interface BuilderProgram extends ProgramToEmitFilesAndReportErrors { - updateProgram(program: Program): void; - } - - function createBuilderProgram(host: BuilderEmitHost): BuilderProgram { - const builder = createEmitAndSemanticDiagnosticsBuilder(host); - let program: Program; - return { - getCurrentDirectory: () => program.getCurrentDirectory(), - getCompilerOptions: () => program.getCompilerOptions(), - getSourceFiles: () => program.getSourceFiles(), - getSyntacticDiagnostics: () => program.getSyntacticDiagnostics(), - getOptionsDiagnostics: () => program.getOptionsDiagnostics(), - getGlobalDiagnostics: () => program.getGlobalDiagnostics(), - getSemanticDiagnostics: () => builder.getSemanticDiagnostics(program), - emit, - updateProgram - }; - - function updateProgram(p: Program) { - program = p; - builder.updateProgram(p); - } - - function emit(): EmitResult { - // Emit and report any errors we ran into. - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - let diagnostics: Diagnostic[]; - let emittedFiles: string[]; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = builder.emitNextAffectedFile(program, host.writeFile)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); - emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - return { - emitSkipped, - diagnostics, - emittedFiles, - sourceMaps - }; - } - } - - /** - * Host needed to emit files and report errors using builder - */ - export interface BuilderEmitHost extends BuilderHost { - writeFile: WriteFileCallback; - reportDiagnostic: DiagnosticReporter; - writeFileName?: (s: string) => void; - } - - /** - * Creates the function that reports the program errors and emit files every time it is called with argument as program - */ - export function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost) { - const builderProgram = createBuilderProgram(host); - return (program: Program) => { - builderProgram.updateProgram(program); - emitFilesAndReportErrors(builderProgram, host.reportDiagnostic, host.writeFileName); - }; - } - export interface WatchCompilerHost { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index a6353d9c0c3..8808a151c04 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -81,20 +81,22 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); + const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; return fileNames => { const program = getProgram(); - builder.updateProgram(program); + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); const outputFileNames: string[] = []; // tslint:disable-next-line no-empty - while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { + while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { } assert.deepEqual(outputFileNames, fileNames); }; } function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: ReadonlyArray, cancelAfterEmitLength?: number) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); + const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; let cancel = false; const cancellationToken: CancellationToken = { isCancellationRequested: () => cancel, @@ -108,7 +110,7 @@ namespace ts { cancel = false; let operationWasCancelled = false; const program = getProgram(); - builder.updateProgram(program); + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); const outputFileNames: string[] = []; try { // tslint:disable-next-line no-empty @@ -117,7 +119,7 @@ namespace ts { if (outputFileNames.length === cancelAfterEmitLength) { cancel = true; } - } while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName), cancellationToken)); + } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); } catch (e) { assert.isFalse(operationWasCancelled); diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index ba944bafd9d..13a7a30d845 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -37,6 +37,7 @@ "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", "../compiler/program.ts", + "../compiler/builderState.ts", "../compiler/builder.ts", "../compiler/resolutionCache.ts", "../compiler/watch.ts", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 514c140f05e..59f3916bbfe 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3749,7 +3749,7 @@ declare namespace ts { result: T; affected: SourceFile | Program; } | undefined; - interface BuilderHost { + interface BuilderProgramHost { /** * return true if file names are treated with case sensitivity */ @@ -3758,78 +3758,104 @@ declare namespace ts { * If provided this would be used this hash instead of actual file shape text for detecting changes */ createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; } /** * Builder to manage the program state changes */ - interface BaseBuilder { + interface BaseBuilderProgram { /** - * Updates the program in the builder to represent new state + * Get compiler options of the program */ - updateProgram(newProgram: Program): void; + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + /** + * 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 + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - interface SemanticDiagnosticsBuilder extends BaseBuilder { + interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; } /** * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { + interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - /** - * 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 - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; } /** * Create the builder to manage semantic diagnostics and cache them */ - function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; + function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; + function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; } declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Host needed to emit files and report errors using builder - */ - interface BuilderEmitHost extends BuilderHost { - writeFile: WriteFileCallback; - reportDiagnostic: DiagnosticReporter; - writeFileName?: (s: string) => void; - } - /** - * Creates the function that reports the program errors and emit files every time it is called with argument as program - */ - function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost): (program: Program) => void; interface WatchCompilerHost { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; From 9b54d2e458248dfdd4dc5ff0078ccf9f56a2719b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 19:20:05 -0800 Subject: [PATCH 033/101] Create api to create Watch --- src/compiler/builder.ts | 11 ++++- src/compiler/tsc.ts | 4 +- src/compiler/watch.ts | 42 +++++++++++++------ tests/baselines/reference/api/typescript.d.ts | 24 +++++++---- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 21c3b9021ee..d0a6c99179e 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -223,6 +223,14 @@ namespace ts { export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind) { + // Return same program if underlying program doesnt change + let oldState = oldProgram && oldProgram.getState(); + if (oldState && newProgram === oldState.program) { + newProgram = undefined; + oldState = undefined; + return oldProgram; + } + /** * Create the canonical file name for identity */ @@ -231,11 +239,12 @@ namespace ts { * Computing hash to for signature verification */ const computeHash = host.createHash || identity; - const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldProgram && oldProgram.getState()); + const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); // To ensure that we arent storing any references to old program or new program without state newProgram = undefined; oldProgram = undefined; + oldState = undefined; const result: BaseBuilderProgram = { getState: () => state, diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index ed5284e696c..ec869f89ce2 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -165,13 +165,13 @@ namespace ts { watchCompilerHost.options = configParseResult.options; watchCompilerHost.configFileSpecs = configParseResult.configFileSpecs; watchCompilerHost.configFileWildCardDirectories = configParseResult.wildcardDirectories; - createWatch(watchCompilerHost); + createWatchProgram(watchCompilerHost); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { const watchCompilerHost = ts.createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, sys, reportDiagnostic); updateWatchCompilationHost(watchCompilerHost); - createWatch(watchCompilerHost); + createWatchProgram(watchCompilerHost); } function enableStatistics(compilerOptions: CompilerOptions) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index c07722cb7d3..0b197c6a0f6 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -376,24 +376,24 @@ namespace ts { configFileWildCardDirectories?: MapLike; } - export interface Watch { + export interface Watch { /** Synchronize with host and get updated program */ - getProgram(): Program; + getProgram(): T; /** Gets the existing program without synchronizing with changes on host */ /*@internal*/ - getExistingProgram(): Program; + getExistingProgram(): T; } /** * Creates the watch what generates program using the config file */ - export interface WatchOfConfigFile extends Watch { + export interface WatchOfConfigFile extends Watch { } /** * Creates the watch that generates program using the root files and compiler options */ - export interface WatchOfFilesAndCompilerOptions extends Watch { + export interface WatchOfFilesAndCompilerOptions extends Watch { /** Updates the root files in the program, only if this is not config file compilation */ updateRootFileNames(fileNames: string[]): void; } @@ -401,26 +401,26 @@ namespace ts { /** * Create the watched program for config file */ - export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - return createWatch(createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend, system, reportDiagnostic)); + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + return createWatchProgram(createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend, system, reportDiagnostic)); } /** * Create the watched program for root files and compiler options */ - export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { - return createWatch(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system, reportDiagnostic)); + export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { + return createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system, reportDiagnostic)); } /** * Creates the watch from the host for root files and compiler options */ - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; + export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -890,4 +890,22 @@ namespace ts { ); } } + + /** + * Creates the watch from the host for root files and compiler options + */ + export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + export function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; + export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + const watch = createWatchProgram(host); + let builderProgram: T | undefined; + return { + getProgram: () => builderProgram = createBuilderProgram(watch.getProgram(), host, builderProgram), + getExistingProgram: () => builderProgram = createBuilderProgram(watch.getExistingProgram(), host, builderProgram), + updateRootFileNames: watch.updateRootFileNames && (fileNames => watch.updateRootFileNames(fileNames)) + }; + } } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 59f3916bbfe..9544057047a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3935,38 +3935,46 @@ declare namespace ts { */ readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } - interface Watch { + interface Watch { /** Synchronize with host and get updated program */ - getProgram(): Program; + getProgram(): T; } /** * Creates the watch what generates program using the config file */ - interface WatchOfConfigFile extends Watch { + interface WatchOfConfigFile extends Watch { } /** * Creates the watch that generates program using the root files and compiler options */ - interface WatchOfFilesAndCompilerOptions extends Watch { + interface WatchOfFilesAndCompilerOptions extends Watch { /** Updates the root files in the program, only if this is not config file compilation */ updateRootFileNames(fileNames: string[]): void; } /** * Create the watched program for config file */ - function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile; + function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile; /** * Create the watched program for root files and compiler options */ - function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions; + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for root files and compiler options */ - function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; + function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + /** + * Creates the watch from the host for root files and compiler options + */ + function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; } declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; From 8ad9a6254c24742641df25b0d2c33a64888c0c77 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 19:44:47 -0800 Subject: [PATCH 034/101] Api to get underlying program from builder --- src/compiler/builder.ts | 5 +++++ tests/baselines/reference/api/typescript.d.ts | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index d0a6c99179e..fdbfe32d450 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -248,6 +248,7 @@ namespace ts { const result: BaseBuilderProgram = { getState: () => state, + getProgram: () => state.program, getCompilerOptions: () => state.program.getCompilerOptions(), getSourceFile: fileName => state.program.getSourceFile(fileName), getSourceFiles: () => state.program.getSourceFiles(), @@ -431,6 +432,10 @@ namespace ts { export interface BaseBuilderProgram { /*@internal*/ getState(): BuilderProgramState; + /** + * Returns current program + */ + getProgram(): Program; /** * Get compiler options of the program */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 9544057047a..8014fe09702 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3768,6 +3768,10 @@ declare namespace ts { * Builder to manage the program state changes */ interface BaseBuilderProgram { + /** + * Returns current program + */ + getProgram(): Program; /** * Get compiler options of the program */ From a75badfd11c4d5fdca27142ce73c0935f4937221 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 19:56:46 -0800 Subject: [PATCH 035/101] Rename on WatchBuilderProgram --- src/compiler/builder.ts | 14 +++++++------- src/compiler/watch.ts | 6 +++--- tests/baselines/reference/api/typescript.d.ts | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index fdbfe32d450..13c5a4a2ca4 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -220,9 +220,9 @@ namespace ts { EmitAndSemanticDiagnosticsBuilderProgram } - export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind) { + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BuilderProgram | undefined, kind: BuilderProgramKind) { // Return same program if underlying program doesnt change let oldState = oldProgram && oldProgram.getState(); if (oldState && newProgram === oldState.program) { @@ -246,7 +246,7 @@ namespace ts { oldProgram = undefined; oldState = undefined; - const result: BaseBuilderProgram = { + const result: BuilderProgram = { getState: () => state, getProgram: () => state.program, getCompilerOptions: () => state.program.getCompilerOptions(), @@ -429,7 +429,7 @@ namespace ts { /** * Builder to manage the program state changes */ - export interface BaseBuilderProgram { + export interface BuilderProgram { /*@internal*/ getState(): BuilderProgramState; /** @@ -490,7 +490,7 @@ namespace ts { /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - export interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete @@ -502,7 +502,7 @@ namespace ts { * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - export interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + export interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Get the current directory of the program */ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0b197c6a0f6..abf29d59c8b 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -894,12 +894,12 @@ namespace ts { /** * Creates the watch from the host for root files and compiler options */ - export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + export function createWatchBuilderProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - export function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; - export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatchBuilderProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; + export function createWatchBuilderProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { const watch = createWatchProgram(host); let builderProgram: T | undefined; return { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8014fe09702..4678e23d443 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3767,7 +3767,7 @@ declare namespace ts { /** * Builder to manage the program state changes */ - interface BaseBuilderProgram { + interface BuilderProgram { /** * Returns current program */ @@ -3825,7 +3825,7 @@ declare namespace ts { /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete @@ -3836,7 +3836,7 @@ declare namespace ts { * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Get the current directory of the program */ @@ -3974,11 +3974,11 @@ declare namespace ts { /** * Creates the watch from the host for root files and compiler options */ - function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + function createWatchBuilderProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; + function createWatchBuilderProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; } declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; From cb2636679b9339a65d8d09771f1244078ba6c9f9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 8 Dec 2017 12:35:37 -0800 Subject: [PATCH 036/101] When user provided resolution is used, invalidate resolutions for all files In this case there is no way to tell if resolution has changed so resolution cache wont have answers --- src/compiler/resolutionCache.ts | 6 +++--- src/compiler/watch.ts | 10 ++++++++-- tests/baselines/reference/api/typescript.d.ts | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 92eea4140da..d365ec21379 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,7 +14,7 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; removeResolutionsOfFile(filePath: Path): void; - createHasInvalidatedResolution(): HasInvalidatedResolution; + createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; startCachingPerDirectoryResolution(): void; finishCachingPerDirectoryResolution(): void; @@ -159,8 +159,8 @@ namespace ts { return collected; } - function createHasInvalidatedResolution(): HasInvalidatedResolution { - if (allFilesHaveInvalidatedResolution) { + function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { + if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) { // Any file asked would have invalidated resolution filesWithInvalidatedResolutions = undefined; return returnTrue; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index abf29d59c8b..762487dc0c7 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -302,6 +302,8 @@ namespace ts { /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; /** Used to watch changes in source files, missing files needed to update the program or config file */ watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; @@ -520,7 +522,10 @@ namespace ts { compilerHost.resolveModuleNames = host.resolveModuleNames ? ((moduleNames, containingFile, reusedNames) => host.resolveModuleNames(moduleNames, containingFile, reusedNames)) : ((moduleNames, containingFile, reusedNames) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames)); - compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); + compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? + ((typeDirectiveNames, containingFile) => host.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile)) : + ((typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile)); + const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); synchronizeProgram(); @@ -542,7 +547,8 @@ namespace ts { } } - const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); + // All resolutions are invalid if user provided resolutions + const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution); if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { return program; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 52b7d4bd707..427218aa390 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3894,6 +3894,8 @@ declare namespace ts { trace?(s: string): void; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; /** Used to watch changes in source files, missing files needed to update the program or config file */ watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ From 4acdca5258f895bbca8ee091184131686cddf731 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 01:46:36 +0000 Subject: [PATCH 037/101] Enforce strictNullChecks for RHS of empty destructuring assignment When strictNullChecks is on, check the RHS of the following destructuring assignments for possible null or undefined: const {} = ... const [] = ... let {} = ... let [] = ... ({} = ...) --- src/compiler/checker.ts | 9 +- .../strictNullEmptyDestructuring.errors.txt | 60 +++++++++++++ .../reference/strictNullEmptyDestructuring.js | 39 +++++++++ .../strictNullEmptyDestructuring.symbols | 49 +++++++++++ .../strictNullEmptyDestructuring.types | 87 +++++++++++++++++++ .../compiler/strictNullEmptyDestructuring.ts | 25 ++++++ 6 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.errors.txt create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.js create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.symbols create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.types create mode 100644 tests/cases/compiler/strictNullEmptyDestructuring.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 16bfd7fa7e2..7a93ad42105 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18908,6 +18908,9 @@ namespace ts { target = (target).left; } if (target.kind === SyntaxKind.ObjectLiteralExpression) { + if (strictNullChecks && (target).properties.length === 0) { + return checkNonNullType(sourceType, target); + } return checkObjectLiteralAssignment(target, sourceType); } if (target.kind === SyntaxKind.ArrayLiteralExpression) { @@ -21833,7 +21836,11 @@ namespace ts { if (isBindingPattern(node.name)) { // Don't validate for-in initializer as it is already an error if (node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - checkTypeAssignableTo(checkExpressionCached(node.initializer), getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); + const initializerType = checkExpressionCached(node.initializer); + if (strictNullChecks && node.name.elements.length === 0) { + checkNonNullType(initializerType, node); + } + checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); checkParameterInitializer(node); } return; diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.errors.txt b/tests/baselines/reference/strictNullEmptyDestructuring.errors.txt new file mode 100644 index 00000000000..2406ac27d98 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.errors.txt @@ -0,0 +1,60 @@ +tests/cases/compiler/strictNullEmptyDestructuring.ts(3,5): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(5,5): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(7,2): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(9,5): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(11,2): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(13,5): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(15,2): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(17,5): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(19,2): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(21,5): error TS2533: Object is possibly 'null' or 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(23,2): error TS2533: Object is possibly 'null' or 'undefined'. + + +==== tests/cases/compiler/strictNullEmptyDestructuring.ts (11 errors) ==== + // Repro from #20873 + + let [] = null; + ~~ +!!! error TS2531: Object is possibly 'null'. + + let { } = null; + ~~~ +!!! error TS2531: Object is possibly 'null'. + + ({} = null); + ~~ +!!! error TS2531: Object is possibly 'null'. + + let { } = undefined; + ~~~ +!!! error TS2532: Object is possibly 'undefined'. + + ({} = undefined); + ~~ +!!! error TS2532: Object is possibly 'undefined'. + + let { } = Math.random() ? {} : null; + ~~~ +!!! error TS2531: Object is possibly 'null'. + + ({} = Math.random() ? {} : null); + ~~ +!!! error TS2531: Object is possibly 'null'. + + let { } = Math.random() ? {} : undefined; + ~~~ +!!! error TS2532: Object is possibly 'undefined'. + + ({} = Math.random() ? {} : undefined); + ~~ +!!! error TS2532: Object is possibly 'undefined'. + + let { } = Math.random() ? null : undefined; + ~~~ +!!! error TS2533: Object is possibly 'null' or 'undefined'. + + ({} = Math.random() ? null : undefined); + ~~ +!!! error TS2533: Object is possibly 'null' or 'undefined'. + \ No newline at end of file diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.js b/tests/baselines/reference/strictNullEmptyDestructuring.js new file mode 100644 index 00000000000..c46b44e21d3 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.js @@ -0,0 +1,39 @@ +//// [strictNullEmptyDestructuring.ts] +// Repro from #20873 + +let [] = null; + +let { } = null; + +({} = null); + +let { } = undefined; + +({} = undefined); + +let { } = Math.random() ? {} : null; + +({} = Math.random() ? {} : null); + +let { } = Math.random() ? {} : undefined; + +({} = Math.random() ? {} : undefined); + +let { } = Math.random() ? null : undefined; + +({} = Math.random() ? null : undefined); + + +//// [strictNullEmptyDestructuring.js] +// Repro from #20873 +var _a = null; +var _b = null; +(null); +var _c = undefined; +(undefined); +var _d = Math.random() ? {} : null; +(Math.random() ? {} : null); +var _e = Math.random() ? {} : undefined; +(Math.random() ? {} : undefined); +var _f = Math.random() ? null : undefined; +(Math.random() ? null : undefined); diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.symbols b/tests/baselines/reference/strictNullEmptyDestructuring.symbols new file mode 100644 index 00000000000..5968efa4925 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.symbols @@ -0,0 +1,49 @@ +=== tests/cases/compiler/strictNullEmptyDestructuring.ts === +// Repro from #20873 + +let [] = null; + +let { } = null; + +({} = null); + +let { } = undefined; +>undefined : Symbol(undefined) + +({} = undefined); +>undefined : Symbol(undefined) + +let { } = Math.random() ? {} : null; +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) + +({} = Math.random() ? {} : null); +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) + +let { } = Math.random() ? {} : undefined; +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + +({} = Math.random() ? {} : undefined); +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + +let { } = Math.random() ? null : undefined; +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + +({} = Math.random() ? null : undefined); +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.types b/tests/baselines/reference/strictNullEmptyDestructuring.types new file mode 100644 index 00000000000..10215a0e0d1 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.types @@ -0,0 +1,87 @@ +=== tests/cases/compiler/strictNullEmptyDestructuring.ts === +// Repro from #20873 + +let [] = null; +>null : null + +let { } = null; +>null : null + +({} = null); +>({} = null) : any +>{} = null : any +>{} : {} +>null : null + +let { } = undefined; +>undefined : undefined + +({} = undefined); +>({} = undefined) : any +>{} = undefined : any +>{} : {} +>undefined : undefined + +let { } = Math.random() ? {} : null; +>Math.random() ? {} : null : {} | null +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>null : null + +({} = Math.random() ? {} : null); +>({} = Math.random() ? {} : null) : {} +>{} = Math.random() ? {} : null : {} +>{} : {} +>Math.random() ? {} : null : {} | null +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>null : null + +let { } = Math.random() ? {} : undefined; +>Math.random() ? {} : undefined : {} | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>undefined : undefined + +({} = Math.random() ? {} : undefined); +>({} = Math.random() ? {} : undefined) : {} +>{} = Math.random() ? {} : undefined : {} +>{} : {} +>Math.random() ? {} : undefined : {} | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>undefined : undefined + +let { } = Math.random() ? null : undefined; +>Math.random() ? null : undefined : null | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>null : null +>undefined : undefined + +({} = Math.random() ? null : undefined); +>({} = Math.random() ? null : undefined) : any +>{} = Math.random() ? null : undefined : any +>{} : {} +>Math.random() ? null : undefined : null | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>null : null +>undefined : undefined + diff --git a/tests/cases/compiler/strictNullEmptyDestructuring.ts b/tests/cases/compiler/strictNullEmptyDestructuring.ts new file mode 100644 index 00000000000..97126154182 --- /dev/null +++ b/tests/cases/compiler/strictNullEmptyDestructuring.ts @@ -0,0 +1,25 @@ +// @strictNullChecks: true + +// Repro from #20873 + +let [] = null; + +let { } = null; + +({} = null); + +let { } = undefined; + +({} = undefined); + +let { } = Math.random() ? {} : null; + +({} = Math.random() ? {} : null); + +let { } = Math.random() ? {} : undefined; + +({} = Math.random() ? {} : undefined); + +let { } = Math.random() ? null : undefined; + +({} = Math.random() ? null : undefined); From b9543bf6172b2249c18dd0db0cbde7ae0188c22c Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 15:20:04 +0000 Subject: [PATCH 038/101] Update initializerType when checking RHS of empty object destructure --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7a93ad42105..4cb639684c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21836,9 +21836,9 @@ namespace ts { if (isBindingPattern(node.name)) { // Don't validate for-in initializer as it is already an error if (node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - const initializerType = checkExpressionCached(node.initializer); + let initializerType = checkExpressionCached(node.initializer); if (strictNullChecks && node.name.elements.length === 0) { - checkNonNullType(initializerType, node); + initializerType = checkNonNullType(initializerType, node); } checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); checkParameterInitializer(node); From 4a86bc60a3150113f772379ee435e2ca46bdb7ac Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 19:48:48 +0000 Subject: [PATCH 039/101] Add review suggestions Move object destructuring assignment to checkObjectLiteralAssignment Only check assignability of types in checkVariableLikeDeclaration for object/array destructuring when there are properties present in the pattern. --- src/compiler/checker.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4cb639684c3..6cb777651a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18769,6 +18769,9 @@ namespace ts { function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type): Type { const properties = node.properties; + if (strictNullChecks && properties.length === 0) { + return checkNonNullType(sourceType, node); + } for (const p of properties) { checkObjectLiteralDestructuringPropertyAssignment(sourceType, p, properties); } @@ -18908,9 +18911,6 @@ namespace ts { target = (target).left; } if (target.kind === SyntaxKind.ObjectLiteralExpression) { - if (strictNullChecks && (target).properties.length === 0) { - return checkNonNullType(sourceType, target); - } return checkObjectLiteralAssignment(target, sourceType); } if (target.kind === SyntaxKind.ArrayLiteralExpression) { @@ -21836,12 +21836,14 @@ namespace ts { if (isBindingPattern(node.name)) { // Don't validate for-in initializer as it is already an error if (node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - let initializerType = checkExpressionCached(node.initializer); + const initializerType = checkExpressionCached(node.initializer); if (strictNullChecks && node.name.elements.length === 0) { - initializerType = checkNonNullType(initializerType, node); + checkNonNullType(initializerType, node); + } + else { + checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); + checkParameterInitializer(node); } - checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); - checkParameterInitializer(node); } return; } From b16594b2393cbf7ff2d03e7ed343b6d29447e1de Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 20:33:00 +0000 Subject: [PATCH 040/101] Ensure checkParameterInitializer always gets called --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6cb777651a6..12f7833ad61 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21842,8 +21842,8 @@ namespace ts { } else { checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); - checkParameterInitializer(node); } + checkParameterInitializer(node); } return; } From ef7f13139838101e305a716b3fe1bdeba9a1804f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 Jan 2018 15:45:05 -0800 Subject: [PATCH 041/101] Add test case for symLink and rename --- .../unittests/tsserverProjectSystem.ts | 107 ++++++++++++++ src/harness/virtualFileSystemWithWatch.ts | 139 +++++++++++++++--- 2 files changed, 224 insertions(+), 22 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fe7a2095b86..2811584bf1b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -6560,4 +6560,111 @@ namespace ts.projectSystem { checkProjectActualFiles(project, [file.path]); }); }); + + describe("tsserverProjectSystem with symLinks", () => { + it("rename in common file renames all project", () => { + const projects = "/users/username/projects"; + const folderA = `${projects}/a`; + const aFile: FileOrFolder = { + path: `${folderA}/a.ts`, + content: `import {C} from "./c/fc"; console.log(C)` + }; + const aTsconfig: FileOrFolder = { + path: `${folderA}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) + }; + const aC: FileOrFolder = { + path: `${folderA}/c`, + symLink: "../c" + }; + const aFc = `${folderA}/c/fc.ts`; + + const folderB = `${projects}/b`; + const bFile: FileOrFolder = { + path: `${folderB}/b.ts`, + content: `import {C} from "./c/fc"; console.log(C)` + }; + const bTsconfig: FileOrFolder = { + path: `${folderB}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) + }; + const bC: FileOrFolder = { + path: `${folderB}/c`, + symLink: "../c" + }; + const bFc = `${folderB}/c/fc.ts`; + + const folderC = `${projects}/c`; + const cFile: FileOrFolder = { + path: `${folderC}/fc.ts`, + content: `export const C = 8` + }; + + const files = [cFile, libFile, aFile, aTsconfig, aC, bFile, bTsconfig, bC]; + const host = createServerHost(files); + const session = createSession(host); + const projectService = session.getProjectService(); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: aFile.path, + projectRootPath: folderA + } + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: bFile.path, + projectRootPath: folderB + } + }); + + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: aFc, + projectRootPath: folderA + } + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: bFc, + projectRootPath: folderB + } + }); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.isDefined(projectService.configuredProjects.get(aTsconfig.path)); + assert.isDefined(projectService.configuredProjects.get(bTsconfig.path)); + + verifyRenameResponse(session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { + file: aFc, + line: 1, + offset: 14, + findInStrings: false, + findInComments: false + } + }).response as protocol.RenameResponseBody); + + function verifyRenameResponse({ info, locs }: protocol.RenameResponseBody) { + assert.isTrue(info.canRename); + assert.equal(locs.length, 2); // Currently 2 but needs to be 4 + assert.deepEqual(locs[0], { + file: aFile.path, + locs: [ + { start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } }, + { start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } } + ] + }); + assert.deepEqual(locs[1], { + file: aFc, + locs: [ + { start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } } + ] + }); + } + }); + }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 921d4674231..5b326789247 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -70,6 +70,7 @@ interface Array {}` path: string; content?: string; fileSize?: number; + symLink?: string; } interface FSEntry { @@ -86,6 +87,10 @@ interface Array {}` entries: FSEntry[]; } + interface SymLink extends FSEntry { + symLink: Path; + } + function isFolder(s: FSEntry): s is Folder { return s && isArray((s).entries); } @@ -94,6 +99,10 @@ interface Array {}` return s && isString((s).content); } + function isSymLink(s: FSEntry): s is SymLink { + return s && isString((s).symLink); + } + function invokeWatcherCallbacks(callbacks: T[], invokeCallback: (cb: T) => void): void { if (callbacks) { // The array copy is made to ensure that even if one of the callback removes the callbacks, @@ -316,9 +325,12 @@ interface Array {}` } } else { - // TODO: Changing from file => folder + // TODO: Changing from file => folder/Symlink } } + else if (isSymLink(currentEntry)) { + // TODO: update symlinks + } else { // Folder if (isString(fileOrDirectory.content)) { @@ -339,7 +351,7 @@ interface Array {}` // If this entry is not from the new file or folder if (!mapNewLeaves.get(path)) { // Leaf entries that arent in new list => remove these - if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) { + if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) { this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path)); } } @@ -387,6 +399,12 @@ interface Array {}` const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate); } + else if (isString(fileOrDirectory.symLink)) { + const symLink = this.toSymLink(fileOrDirectory); + Debug.assert(!this.fs.get(symLink.path)); + const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath)); + this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate); + } else { const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); this.ensureFolder(fullPath); @@ -414,20 +432,20 @@ interface Array {}` return folder; } - private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder, ignoreWatch?: boolean) { + private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder | SymLink, ignoreWatch?: boolean) { folder.entries.push(fileOrDirectory); this.fs.set(fileOrDirectory.path, fileOrDirectory); if (ignoreWatch) { return; } - if (isFile(fileOrDirectory)) { + if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) { this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created); } this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath); } - private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) { + private removeFileOrFolder(fileOrDirectory: File | Folder | SymLink, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) { const basePath = getDirectoryPath(fileOrDirectory.path); const baseFolder = this.fs.get(basePath) as Folder; if (basePath !== fileOrDirectory.path) { @@ -436,7 +454,7 @@ interface Array {}` } this.fs.delete(fileOrDirectory.path); - if (isFile(fileOrDirectory)) { + if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) { this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted); } else { @@ -503,6 +521,15 @@ interface Array {}` }; } + private toSymLink(fileOrDirectory: FileOrFolder): SymLink { + const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); + return { + path: this.toPath(fullPath), + fullPath, + symLink: this.toPath(getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath))) + }; + } + private toFolder(path: string): Folder { const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory); return { @@ -512,14 +539,70 @@ interface Array {}` }; } - fileExists(s: string) { - const path = this.toFullPath(s); - return isFile(this.fs.get(path)); + private isFile(fsEntry: FSEntry) { + return !!this.getRealFile(fsEntry.path, fsEntry); } - readFile(s: string) { - const fsEntry = this.fs.get(this.toFullPath(s)); - return isFile(fsEntry) ? fsEntry.content : undefined; + private getRealFile(path: Path, fsEntry = this.fs.get(path)): File | undefined { + if (isFile(fsEntry)) { + return fsEntry; + } + + if (isSymLink(fsEntry)) { + return this.getRealFile(fsEntry.symLink); + } + + if (fsEntry) { + // This fs entry is something else + return undefined; + } + + const dir = getDirectoryPath(path); + const dirEntry = this.getRealFolder(dir); + if (dirEntry && dirEntry.path !== dir) { + return this.getRealFile(combinePaths(dirEntry.path, getBaseFileName(path)) as Path); + } + return undefined; + } + + private isFolder(fsEntry: FSEntry) { + return !!this.getRealFolder(fsEntry.path, fsEntry); + } + + private getRealFolder(path: Path, fsEntry = this.fs.get(path)): Folder | undefined { + if (isFolder(fsEntry)) { + return fsEntry; + } + + if (isSymLink(fsEntry)) { + return this.getRealFolder(fsEntry.symLink); + } + + if (fsEntry) { + // This fs entry is something else + return undefined; + } + + const baseName = getBaseFileName(path); + const dir = getDirectoryPath(path); + if (dir !== baseName) { + const dirEntry = this.getRealFolder(dir); + if (dirEntry && dirEntry.path !== dir) { + return this.getRealFolder(combinePaths(dirEntry.path, baseName) as Path); + } + } + + return undefined; + } + + fileExists(s: string) { + const path = this.toFullPath(s); + return !!this.getRealFile(path); + } + + readFile(s: string): string { + const fsEntry = this.getRealFile(this.toFullPath(s)); + return fsEntry ? fsEntry.content : undefined; } getFileSize(s: string) { @@ -533,14 +616,14 @@ interface Array {}` directoryExists(s: string) { const path = this.toFullPath(s); - return isFolder(this.fs.get(path)); + return !!this.getRealFolder(path); } - getDirectories(s: string) { + getDirectories(s: string): string[] { const path = this.toFullPath(s); - const folder = this.fs.get(path); - if (isFolder(folder)) { - return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); + const folder = this.getRealFolder(path); + if (folder) { + return mapDefined(folder.entries, entry => this.isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); } Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder"); return []; @@ -550,13 +633,13 @@ interface Array {}` return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const directories: string[] = []; const files: string[] = []; - const dirEntry = this.fs.get(this.toPath(dir)); - if (isFolder(dirEntry)) { - dirEntry.entries.forEach((entry) => { - if (isFolder(entry)) { + const folder = this.getRealFolder(this.toPath(dir)); + if (folder) { + folder.entries.forEach((entry) => { + if (this.isFolder(entry)) { directories.push(getBaseFileName(entry.fullPath)); } - else if (isFile(entry)) { + else if (this.isFile(entry)) { files.push(getBaseFileName(entry.fullPath)); } else { @@ -682,6 +765,18 @@ interface Array {}` clear(this.output); } + realpath(s: string) { + while (true) { + const fsEntry = this.fs.get(this.toFullPath(s)); + if (isSymLink(fsEntry)) { + s = fsEntry.symLink; + } + else { + return s; + } + } + } + readonly existMessage = "System Exit"; exitCode: number; readonly resolvePath = (s: string) => s; From 428e0529fd81d9701e025ce98346b6b48e75dbd2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 12 Jan 2018 13:54:49 -0800 Subject: [PATCH 042/101] Rename through all projects with the same file symLink --- src/compiler/sys.ts | 7 +- .../unittests/tsserverProjectSystem.ts | 35 +++--- src/harness/virtualFileSystemWithWatch.ts | 77 ++++++-------- src/server/editorServices.ts | 70 +++++++++--- src/server/scriptInfo.ts | 32 ++++++ src/server/session.ts | 100 ++++++++++++++---- .../reference/api/tsserverlibrary.d.ts | 6 +- 7 files changed, 226 insertions(+), 101 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 841dba8a528..aa4bd61edce 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -524,7 +524,12 @@ namespace ts { process.exit(exitCode); }, realpath(path: string): string { - return _fs.realpathSync(path); + try { + return _fs.realpathSync(path); + } + catch { + return path; + } }, debugMode: some(process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), tryEnableSourceMapsForHost() { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 2811584bf1b..e54988e223d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -6604,6 +6604,7 @@ namespace ts.projectSystem { const host = createServerHost(files); const session = createSession(host); const projectService = session.getProjectService(); + debugger; session.executeCommandSeq({ command: protocol.CommandTypes.Open, arguments: { @@ -6637,6 +6638,7 @@ namespace ts.projectSystem { assert.isDefined(projectService.configuredProjects.get(aTsconfig.path)); assert.isDefined(projectService.configuredProjects.get(bTsconfig.path)); + debugger; verifyRenameResponse(session.executeCommandSeq({ command: protocol.CommandTypes.Rename, arguments: { @@ -6650,20 +6652,25 @@ namespace ts.projectSystem { function verifyRenameResponse({ info, locs }: protocol.RenameResponseBody) { assert.isTrue(info.canRename); - assert.equal(locs.length, 2); // Currently 2 but needs to be 4 - assert.deepEqual(locs[0], { - file: aFile.path, - locs: [ - { start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } }, - { start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } } - ] - }); - assert.deepEqual(locs[1], { - file: aFc, - locs: [ - { start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } } - ] - }); + assert.equal(locs.length, 4); + verifyLocations(0, aFile.path, aFc); + verifyLocations(2, bFile.path, bFc); + + function verifyLocations(locStartIndex: number, firstFile: string, secondFile: string) { + assert.deepEqual(locs[locStartIndex], { + file: firstFile, + locs: [ + { start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } }, + { start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } } + ] + }); + assert.deepEqual(locs[locStartIndex + 1], { + file: secondFile, + locs: [ + { start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } } + ] + }); + } } }); }); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 5b326789247..90129e8e0f5 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -88,7 +88,7 @@ interface Array {}` } interface SymLink extends FSEntry { - symLink: Path; + symLink: string; } function isFolder(s: FSEntry): s is Folder { @@ -526,7 +526,7 @@ interface Array {}` return { path: this.toPath(fullPath), fullPath, - symLink: this.toPath(getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath))) + symLink: getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath)) }; } @@ -539,17 +539,13 @@ interface Array {}` }; } - private isFile(fsEntry: FSEntry) { - return !!this.getRealFile(fsEntry.path, fsEntry); - } - - private getRealFile(path: Path, fsEntry = this.fs.get(path)): File | undefined { - if (isFile(fsEntry)) { + private getRealFsEntry(isFsEntry: (fsEntry: FSEntry) => fsEntry is T, path: Path, fsEntry = this.fs.get(path)): T | undefined { + if (isFsEntry(fsEntry)) { return fsEntry; } if (isSymLink(fsEntry)) { - return this.getRealFile(fsEntry.symLink); + return this.getRealFsEntry(isFsEntry, this.toPath(fsEntry.symLink)); } if (fsEntry) { @@ -557,42 +553,28 @@ interface Array {}` return undefined; } - const dir = getDirectoryPath(path); - const dirEntry = this.getRealFolder(dir); - if (dirEntry && dirEntry.path !== dir) { - return this.getRealFile(combinePaths(dirEntry.path, getBaseFileName(path)) as Path); + const realpath = this.realpath(path); + if (path !== realpath) { + return this.getRealFsEntry(isFsEntry, realpath as Path); } + return undefined; } + private isFile(fsEntry: FSEntry) { + return !!this.getRealFile(fsEntry.path, fsEntry); + } + + private getRealFile(path: Path, fsEntry?: FSEntry): File | undefined { + return this.getRealFsEntry(isFile, path, fsEntry); + } + private isFolder(fsEntry: FSEntry) { return !!this.getRealFolder(fsEntry.path, fsEntry); } private getRealFolder(path: Path, fsEntry = this.fs.get(path)): Folder | undefined { - if (isFolder(fsEntry)) { - return fsEntry; - } - - if (isSymLink(fsEntry)) { - return this.getRealFolder(fsEntry.symLink); - } - - if (fsEntry) { - // This fs entry is something else - return undefined; - } - - const baseName = getBaseFileName(path); - const dir = getDirectoryPath(path); - if (dir !== baseName) { - const dirEntry = this.getRealFolder(dir); - if (dirEntry && dirEntry.path !== dir) { - return this.getRealFolder(combinePaths(dirEntry.path, baseName) as Path); - } - } - - return undefined; + return this.getRealFsEntry(isFolder, path, fsEntry); } fileExists(s: string) { @@ -765,16 +747,21 @@ interface Array {}` clear(this.output); } - realpath(s: string) { - while (true) { - const fsEntry = this.fs.get(this.toFullPath(s)); - if (isSymLink(fsEntry)) { - s = fsEntry.symLink; - } - else { - return s; - } + realpath(s: string): string { + const fullPath = this.toNormalizedAbsolutePath(s); + const path = this.toPath(fullPath); + if (getDirectoryPath(path) === path) { + // Root + return s; } + const dirFullPath = this.realpath(getDirectoryPath(fullPath)); + const realFullPath = combinePaths(dirFullPath, getBaseFileName(fullPath)); + const fsEntry = this.fs.get(this.toPath(realFullPath)); + if (isSymLink(fsEntry)) { + return this.realpath(fsEntry.symLink); + } + + return realFullPath; } readonly existMessage = "System Exit"; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 664938a4cdc..864383d1052 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -198,16 +198,6 @@ namespace ts.server { } } - /** - * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. - */ - export function combineProjectOutput(projects: ReadonlyArray, action: (project: Project) => ReadonlyArray, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { - const outputs = flatMap(projects, action); - return comparer - ? sortAndDeduplicate(outputs, comparer, areEqual) - : deduplicate(outputs, areEqual); - } - export interface HostConfiguration { formatCodeOptions: FormatCodeSettings; hostInfo: string; @@ -335,6 +325,11 @@ namespace ts.server { * Container of all known scripts */ private readonly filenameToScriptInfo = createMap(); + /** + * Map to the real path of the infos + */ + /* @internal */ + readonly realpathToScriptInfos: MultiMap | undefined; /** * maps external project file name to list of config files that were the part of this project */ @@ -427,7 +422,9 @@ namespace ts.server { this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation; Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService"); - + if (this.host.realpath) { + this.realpathToScriptInfos = createMultiMap(); + } this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.throttledOperations = new ThrottledOperations(this.host, this.logger); @@ -768,7 +765,7 @@ namespace ts.server { if (info.containingProjects.length === 0) { // Orphan script info, remove it as we can always reload it on next open file request this.stopWatchingScriptInfo(info); - this.filenameToScriptInfo.delete(info.path); + this.deleteScriptInfo(info); } else { // file has been changed which might affect the set of referenced files in projects that include @@ -785,7 +782,7 @@ namespace ts.server { // TODO: handle isOpen = true case if (!info.isScriptOpen()) { - this.filenameToScriptInfo.delete(info.path); + this.deleteScriptInfo(info); // capture list of projects since detachAllProjects will wipe out original list const containingProjects = info.containingProjects.slice(); @@ -1019,11 +1016,19 @@ namespace ts.server { if (!info.isScriptOpen() && info.isOrphan()) { // if there are not projects that include this script info - delete it this.stopWatchingScriptInfo(info); - this.filenameToScriptInfo.delete(info.path); + this.deleteScriptInfo(info); } }); } + private deleteScriptInfo(info: ScriptInfo) { + this.filenameToScriptInfo.delete(info.path); + const realpath = info.getRealpathIfDifferent(); + if (realpath) { + this.realpathToScriptInfos.remove(realpath, info); + } + } + private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo) { @@ -1736,6 +1741,43 @@ namespace ts.server { return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName)); } + /** + * Returns the projects that contain script info through SymLink + * Note that this does not return projects in info.containingProjects + */ + /*@internal*/ + getSymlinkedProjects(info: ScriptInfo): MultiMap | undefined { + let projects: MultiMap | undefined; + if (this.realpathToScriptInfos) { + const realpath = info.getRealpathIfDifferent(); + if (realpath) { + forEach(this.realpathToScriptInfos.get(realpath), combineProjects); + } + forEach(this.realpathToScriptInfos.get(info.path), combineProjects); + } + + return projects; + + function combineProjects(toAddInfo: ScriptInfo) { + if (toAddInfo !== info) { + for (const project of toAddInfo.containingProjects) { + // Add the projects only if they can use symLink targets and not already in the list + if (project.languageServiceEnabled && + !project.getCompilerOptions().preserveSymlinks && + !contains(info.containingProjects, project)) { + if (!projects) { + projects = createMultiMap(); + projects.add(toAddInfo.path, project); + } + else if (!forEachEntry(projects, (projs, path) => path === toAddInfo.path ? false : contains(projs, project))) { + projects.add(toAddInfo.path, project); + } + } + } + } + } + } + private watchClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.fileWatcher); // do not watch files with mixed content - server doesn't know how to interpret it diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index f800a1117d0..bf3cc05c0c8 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -213,6 +213,10 @@ namespace ts.server { /*@internal*/ readonly isDynamic: boolean; + /*@internal*/ + /** Set to real path if path is different from info.path */ + private realpath: Path | undefined; + constructor( private readonly host: ServerHost, readonly fileName: NormalizedPath, @@ -224,6 +228,7 @@ namespace ts.server { this.textStorage = new TextStorage(host, fileName); if (hasMixedContent || this.isDynamic) { this.textStorage.reload(""); + this.realpath = this.path; } this.scriptKind = scriptKind ? scriptKind @@ -264,6 +269,30 @@ namespace ts.server { return this.textStorage.getSnapshot(); } + private ensureRealPath() { + if (this.realpath === undefined) { + // Default is just the path + this.realpath = this.path; + if (this.host.realpath) { + Debug.assert(!!this.containingProjects.length); + const project = this.containingProjects[0]; + const realpath = this.host.realpath(this.path); + if (realpath) { + this.realpath = project.toPath(realpath); + // If it is different from this.path, add to the map + if (this.realpath !== this.path) { + project.projectService.realpathToScriptInfos.add(this.realpath, this); + } + } + } + } + } + + /*@internal*/ + getRealpathIfDifferent(): Path | undefined { + return this.realpath && this.realpath !== this.path ? this.realpath : undefined; + } + getFormatCodeSettings() { return this.formatCodeSettings; } @@ -272,6 +301,9 @@ namespace ts.server { const isNew = !this.isAttached(project); if (isNew) { this.containingProjects.push(project); + if (!project.getCompilerOptions().preserveSymlinks) { + this.ensureRealPath(); + } } return isNew; } diff --git a/src/server/session.ts b/src/server/session.ts index 693d1651c91..6b55b540577 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -255,6 +255,32 @@ namespace ts.server { }; } + type Projects = ReadonlyArray | { + projects: ReadonlyArray; + symLinkedProjects: MultiMap; + }; + + function isProjectsArray(projects: Projects): projects is ReadonlyArray { + return !!(>projects).length; + } + + /** + * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. + */ + function combineProjectOutput(defaultValue: T, getValue: (path: Path) => T, projects: Projects, action: (project: Project, value: T) => ReadonlyArray | U | undefined, comparer?: (a: U, b: U) => number, areEqual?: (a: U, b: U) => boolean) { + const outputs = flatMap(isProjectsArray(projects) ? projects : projects.projects, project => action(project, defaultValue)); + if (!isProjectsArray(projects) && projects.symLinkedProjects) { + projects.symLinkedProjects.forEach((projects, path) => { + const value = getValue(path as Path); + outputs.push(...flatMap(projects, project => action(project, value))); + }); + } + + return comparer + ? sortAndDeduplicate(outputs, comparer, areEqual) + : deduplicate(outputs, areEqual); + } + export interface SessionOptions { host: ServerHost; cancellationToken: ServerCancellationToken; @@ -789,8 +815,9 @@ namespace ts.server { return project.getLanguageService().getRenameInfo(file, position); } - private getProjects(args: protocol.FileRequestArgs) { - let projects: Project[]; + private getProjects(args: protocol.FileRequestArgs): Projects { + let projects: ReadonlyArray; + let symLinkedProjects: MultiMap | undefined; if (args.projectFileName) { const project = this.getProject(args.projectFileName); if (project) { @@ -800,13 +827,14 @@ namespace ts.server { else { const scriptInfo = this.projectService.getScriptInfo(args.file); projects = scriptInfo.containingProjects; + symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo); } // filter handles case when 'projects' is undefined projects = filter(projects, p => p.languageServiceEnabled); - if (!projects || !projects.length) { + if ((!projects || !projects.length) && !symLinkedProjects) { return Errors.ThrowNoProject(); } - return projects; + return symLinkedProjects ? { projects, symLinkedProjects } : projects; } private getDefaultProject(args: protocol.FileRequestArgs) { @@ -841,8 +869,10 @@ namespace ts.server { } const fileSpans = combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - (project: Project) => { + (project, file) => { const renameLocations = project.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments); if (!renameLocations) { return emptyArray; @@ -881,8 +911,10 @@ namespace ts.server { } else { return combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - p => p.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments), + (p, file) => p.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments), /*comparer*/ undefined, renameLocationIsEqualTo ); @@ -938,9 +970,11 @@ namespace ts.server { const nameSpan = nameInfo.textSpan; const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset; const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)); - const refs = combineProjectOutput( + const refs = combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - (project: Project) => { + (project, file) => { const references = project.getLanguageService().getReferencesAtPosition(file, position); if (!references) { return emptyArray; @@ -974,8 +1008,10 @@ namespace ts.server { } else { return combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - project => project.getLanguageService().findReferences(file, position), + (project, file) => project.getLanguageService().findReferences(file, position), /*comparer*/ undefined, equateValues ); @@ -1240,20 +1276,25 @@ namespace ts.server { return emptyArray; } - const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = []; - // if specified a project, we only return affected file list in this project - const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects; - for (const project of projectsToSearch) { - if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.getCompilationSettings().noEmit) { - result.push({ - projectFileName: project.getProjectName(), - fileNames: project.getCompileOnSaveAffectedFileList(info), - projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out - }); + const projects = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects; + const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info); + return combineProjectOutput( + info, + path => this.projectService.getScriptInfoForPath(path), + symLinkedProjects ? { projects, symLinkedProjects } : projects, + (project, info) => { + let result: protocol.CompileOnSaveAffectedFileListSingleProject; + if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.getCompilationSettings().noEmit) { + result = { + projectFileName: project.getProjectName(), + fileNames: project.getCompileOnSaveAffectedFileList(info), + projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out + }; + } + return result; } - } - return result; + ); } private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs) { @@ -1406,8 +1447,14 @@ namespace ts.server { const fileName = args.currentFileOnly ? args.file && normalizeSlashes(args.file) : undefined; if (simplifiedResult) { return combineProjectOutput( + fileName, + () => undefined, projects, - project => { + (project, file) => { + if (fileName && !file) { + return undefined; + } + const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()); if (!navItems) { return emptyArray; @@ -1443,8 +1490,15 @@ namespace ts.server { } else { return combineProjectOutput( + fileName, + () => undefined, projects, - project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()), + (project, file) => { + if (fileName && !file) { + return undefined; + } + return project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()); + }, /*comparer*/ undefined, navigateToItemIsEqualTo); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3eabea4435a..ef26cbc743b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7151,6 +7151,7 @@ declare namespace ts.server { open(newText: string): void; close(fileExists?: boolean): void; getSnapshot(): IScriptSnapshot; + private ensureRealPath(); getFormatCodeSettings(): FormatCodeSettings; attachToProject(project: Project): boolean; isAttached(project: Project): boolean; @@ -7523,10 +7524,6 @@ declare namespace ts.server { function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin; function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind; function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX; - /** - * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. - */ - function combineProjectOutput(projects: ReadonlyArray, action: (project: Project) => ReadonlyArray, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean): T[]; interface HostConfiguration { formatCodeOptions: FormatCodeSettings; hostInfo: string; @@ -7662,6 +7659,7 @@ declare namespace ts.server { */ private closeOpenFile(info); private deleteOrphanScriptInfoNotInAnyProject(); + private deleteScriptInfo(info); private configFileExists(configFileName, canonicalConfigFilePath, info); private setConfigFileExistenceByNewConfiguredProject(project); /** From 136c4d0dda5a381957f14b6c63456d2766053108 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 15:02:23 -0800 Subject: [PATCH 043/101] Fixes var declaration shadowing in async functions --- src/compiler/core.ts | 3 +- src/compiler/transformers/es2017.ts | 278 ++++++++++- .../reference/asyncWithVarShadowing_es6.js | 463 ++++++++++++++++++ .../asyncWithVarShadowing_es6.symbols | 406 +++++++++++++++ .../reference/asyncWithVarShadowing_es6.types | 410 ++++++++++++++++ .../async/es6/asyncWithVarShadowing_es6.ts | 207 ++++++++ 6 files changed, 1752 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/asyncWithVarShadowing_es6.js create mode 100644 tests/baselines/reference/asyncWithVarShadowing_es6.symbols create mode 100644 tests/baselines/reference/asyncWithVarShadowing_es6.types create mode 100644 tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0faabbda2a0..fe63b38ac1b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1341,7 +1341,8 @@ namespace ts { export function cloneMap(map: SymbolTable): SymbolTable; export function cloneMap(map: ReadonlyMap): Map; - export function cloneMap(map: ReadonlyMap | SymbolTable): Map | SymbolTable { + export function cloneMap(map: ReadonlyUnderscoreEscapedMap): UnderscoreEscapedMap; + export function cloneMap(map: ReadonlyMap | ReadonlyUnderscoreEscapedMap | SymbolTable): Map | UnderscoreEscapedMap | SymbolTable { const clone = createMap(); copyEntries(map as Map, clone); return clone; diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 9fa39da2d23..5733d089a7d 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -12,9 +12,9 @@ namespace ts { export function transformES2017(context: TransformationContext) { const { - startLexicalEnvironment, resumeLexicalEnvironment, - endLexicalEnvironment + endLexicalEnvironment, + hoistVariableDeclaration } = context; const resolver = context.getEmitResolver(); @@ -33,6 +33,8 @@ namespace ts { */ let enclosingSuperContainerFlags: NodeCheckFlags = 0; + let enclosingFunctionParameterNames: UnderscoreEscapedMap; + // Save the previous transformation hooks. const previousOnEmitNode = context.onEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; @@ -83,6 +85,103 @@ namespace ts { } } + function asyncBodyVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatementInAsyncBody(node); + case SyntaxKind.ForStatement: + return visitForStatementInAsyncBody(node); + case SyntaxKind.ForInStatement: + return visitForInStatementInAsyncBody(node); + case SyntaxKind.ForOfStatement: + return visitForOfStatementInAsyncBody(node); + case SyntaxKind.Block: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.WithStatement: + return visitEachChild(node, asyncBodyVisitor, context); + case SyntaxKind.CatchClause: + return visitCatchClauseInAsyncBody(node); + } + return visitor(node); + } + + function visitCatchClauseInAsyncBody(node: CatchClause) { + const catchClauseNames = createUnderscoreEscapedMap(); + recordDeclarationName(node.variableDeclaration, catchClauseNames); + + // names declared in a catch variable are block scoped + let catchClauseUnshadowedNames: UnderscoreEscapedMap; + catchClauseNames.forEach((_, escapedName) => { + if (enclosingFunctionParameterNames.has(escapedName)) { + if (!catchClauseUnshadowedNames) { + catchClauseUnshadowedNames = cloneMap(enclosingFunctionParameterNames); + } + catchClauseUnshadowedNames.delete(escapedName); + } + }); + + if (catchClauseUnshadowedNames) { + const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames; + enclosingFunctionParameterNames = catchClauseUnshadowedNames; + const result = visitEachChild(node, asyncBodyVisitor, context); + enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames; + return result; + } + else { + return visitEachChild(node, asyncBodyVisitor, context); + } + } + + function visitVariableStatementInAsyncBody(node: VariableStatement) { + if (isVariableDeclarationListWithCollidingName(node.declarationList)) { + const expression = visitVariableDeclarationListWithCollidingNames(node.declarationList, /*hasReceiver*/ false); + return expression ? createStatement(expression) : undefined; + } + return visitEachChild(node, visitor, context); + } + + function visitForInStatementInAsyncBody(node: ForInStatement) { + return updateForIn( + node, + isVariableDeclarationListWithCollidingName(node.initializer) + ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true) + : visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.expression, visitor, isExpression), + visitNode(node.statement, asyncBodyVisitor, isStatement, liftToBlock) + ); + } + + function visitForOfStatementInAsyncBody(node: ForOfStatement) { + return updateForOf( + node, + visitNode(node.awaitModifier, visitor, isToken), + isVariableDeclarationListWithCollidingName(node.initializer) + ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true) + : visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.expression, visitor, isExpression), + visitNode(node.statement, asyncBodyVisitor, isStatement, liftToBlock) + ); + } + + function visitForStatementInAsyncBody(node: ForStatement) { + return updateFor( + node, + isVariableDeclarationListWithCollidingName(node.initializer) + ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ false) + : visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.condition, visitor, isExpression), + visitNode(node.incrementor, visitor, isExpression), + visitNode((node).statement, asyncBodyVisitor, isStatement, liftToBlock) + ); + } + /** * Visits an AwaitExpression node. * @@ -197,6 +296,149 @@ namespace ts { ); } + function recordDeclarationName({ name }: ParameterDeclaration | VariableDeclaration | BindingElement, names: UnderscoreEscapedMap) { + if (isIdentifier(name)) { + names.set(name.escapedText, true); + } + else { + for (const element of name.elements) { + if (!isOmittedExpression(element)) { + recordDeclarationName(element, names); + } + } + } + } + + function isVariableDeclarationListWithCollidingName(node: ForInitializer): node is VariableDeclarationList { + return node + && isVariableDeclarationList(node) + && !(node.flags & NodeFlags.BlockScoped) + && forEach(node.declarations, collidesWithParameterName); + } + + function visitVariableDeclarationListWithCollidingNames(node: VariableDeclarationList, hasReceiver: boolean) { + hoistVariableDeclarationList(node); + + const variables = getInitializedVariables(node); + if (variables.length === 0) { + if (hasReceiver) { + return convertBindingNameToAssignmentTarget(node.declarations[0].name); + } + return undefined; + } + + return inlineExpressions(map(variables, transformInitializedVariable)); + } + + function hoistVariableDeclarationList(node: VariableDeclarationList) { + forEach(node.declarations, hoistVariable); + } + + function hoistVariable({ name }: VariableDeclaration | BindingElement) { + if (isIdentifier(name)) { + hoistVariableDeclaration(name); + } + else { + for (const element of name.elements) { + if (!isOmittedExpression(element)) { + hoistVariable(element); + } + } + } + } + + function transformInitializedVariable(node: VariableDeclaration) { + return setSourceMapRange( + createAssignment( + convertBindingNameToAssignmentTarget(node.name), + visitNode(node.initializer, visitor, isExpression) + ), + node + ); + } + + function convertBindingNameToAssignmentTarget(node: BindingName): Identifier | AssignmentPattern { + return isObjectBindingPattern(node) ? convertObjectBindingPatternToObjectAssignmentPattern(node) : + isArrayBindingPattern(node) ? convertArrayBindingPatternToArrayAssignmentPattern(node) : + setSourceMapRange(getSynthesizedClone(node), node); + } + + function convertObjectBindingPatternToObjectAssignmentPattern(node: ObjectBindingPattern): ObjectLiteralExpression { + return createObjectLiteral( + map(node.elements, convertObjectBindingElementToObjectAssignmentElement) + ); + } + + function convertObjectBindingElementToObjectAssignmentElement(node: BindingElement): ObjectLiteralElementLike { + if (node.propertyName) { + let expression: Expression = convertBindingNameToAssignmentTarget(node.name); + if (node.initializer) { + expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); + } + return setSourceMapRange( + createPropertyAssignment( + visitNode(node.propertyName, visitor, isPropertyName), + expression + ), + node + ); + } + else if (node.dotDotDotToken) { + return setSourceMapRange( + createSpreadAssignment( + visitNode(cast(node.name, isIdentifier), visitor, isIdentifier) + ), + node + ); + } + else { + return setSourceMapRange( + createShorthandPropertyAssignment( + visitNode(cast(node.name, isIdentifier), visitor, isIdentifier), + visitNode(node.initializer, visitor, isExpression) + ), + node + ); + } + } + + function convertArrayBindingPatternToArrayAssignmentPattern(node: ArrayBindingPattern): ArrayLiteralExpression { + return setSourceMapRange( + createArrayLiteral( + map(node.elements, convertArrayBindingElementToArrayAssignmentElement) + ), + node + ); + } + + function convertArrayBindingElementToArrayAssignmentElement(node: ArrayBindingElement): Expression { + if (isOmittedExpression(node)) return node; + let expression: Expression = convertBindingNameToAssignmentTarget(node.name); + if (node.initializer) { + expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); + setSourceMapRange(expression, node); + } + else if (node.dotDotDotToken) { + expression = createSpread(expression); + setSourceMapRange(expression, node); + } + return expression; + } + + function collidesWithParameterName({ name }: VariableDeclaration | BindingElement): boolean { + if (isIdentifier(name)) { + return enclosingFunctionParameterNames.has(name.escapedText); + } + else { + for (const element of name.elements) { + if (!isOmittedExpression(element) && collidesWithParameterName(element)) { + return true; + } + } + } + return false; + } + function transformAsyncFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody; function transformAsyncFunctionBody(node: ArrowFunction): ConciseBody; function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody { @@ -214,6 +456,13 @@ namespace ts { // passed to `__awaiter` is executed inside of the callback to the // promise constructor. + const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames; + enclosingFunctionParameterNames = createUnderscoreEscapedMap(); + for (const parameter of node.parameters) { + recordDeclarationName(parameter, enclosingFunctionParameterNames); + } + + let result: ConciseBody; if (!isArrowFunction) { const statements: Statement[] = []; const statementOffset = addPrologue(statements, (node.body).statements, /*ensureUseStrict*/ false, visitor); @@ -223,7 +472,7 @@ namespace ts { context, hasLexicalArguments, promiseConstructor, - transformFunctionBodyWorker(node.body, statementOffset) + transformAsyncFunctionBodyWorker(node.body, statementOffset) ) ) ); @@ -246,35 +495,36 @@ namespace ts { } } - return block; + result = block; } else { const expression = createAwaiterHelper( context, hasLexicalArguments, promiseConstructor, - transformFunctionBodyWorker(node.body) + transformAsyncFunctionBodyWorker(node.body) ); const declarations = endLexicalEnvironment(); if (some(declarations)) { const block = convertToFunctionBody(expression); - return updateBlock(block, setTextRange(createNodeArray(concatenate(block.statements, declarations)), block.statements)); + result = updateBlock(block, setTextRange(createNodeArray(concatenate(block.statements, declarations)), block.statements)); + } + else { + result = expression; } - - return expression; } + + enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames; + return result; } - function transformFunctionBodyWorker(body: ConciseBody, start?: number) { + function transformAsyncFunctionBodyWorker(body: ConciseBody, start?: number) { if (isBlock(body)) { - return updateBlock(body, visitLexicalEnvironment(body.statements, visitor, context, start)); + return updateBlock(body, visitNodes(body.statements, asyncBodyVisitor, isStatement, start)); } else { - startLexicalEnvironment(); - const visited = convertToFunctionBody(visitNode(body, visitor, isConciseBody)); - const declarations = endLexicalEnvironment(); - return updateBlock(visited, setTextRange(createNodeArray(concatenate(visited.statements, declarations)), visited.statements)); + return convertToFunctionBody(visitNode(body, asyncBodyVisitor, isConciseBody)); } } diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.js b/tests/baselines/reference/asyncWithVarShadowing_es6.js new file mode 100644 index 00000000000..726bf935cf6 --- /dev/null +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.js @@ -0,0 +1,463 @@ +//// [asyncWithVarShadowing_es6.ts] +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; + +async function fn1(x) { + var x; +} + +async function fn2(x) { + var x, z; +} + +async function fn3(x) { + var z; +} + +async function fn4(x) { + var x = y; +} + +async function fn5(x) { + var { x } = y; +} + +async function fn6(x) { + var { x, z } = y; +} + +async function fn7(x) { + var { x = y } = y; +} + +async function fn8(x) { + var { z: x } = y; +} + +async function fn9(x) { + var { z: { x } } = y; +} + +async function fn10(x) { + var { z: { x } = y } = y; +} + +async function fn11(x) { + var { ...x } = y; +} + +async function fn12(x) { + var [x] = y; +} + +async function fn13(x) { + var [x = y] = y; +} + +async function fn14(x) { + var [, x] = y; +} + +async function fn15(x) { + var [...x] = y; +} + +async function fn16(x) { + var [[x]] = y; +} + +async function fn17(x) { + var [[x] = y] = y; +} + +async function fn18({ x }) { + var x; +} + +async function fn19([x]) { + var x; +} + +async function fn20(x) { + { + var x; + } +} + +async function fn21(x) { + if (y) { + var x; + } +} + +async function fn22(x) { + if (y) { + } + else { + var x; + } +} + +async function fn23(x) { + try { + var x; + } + catch (e) { + } +} + +async function fn24(x) { + try { + + } + catch (e) { + var x; + } +} + +async function fn25(x) { + try { + + } + catch (x) { + var x; + } +} + +async function fn26(x) { + try { + + } + catch ({ x }) { + var x; + } +} + +async function fn27(x) { + try { + } + finally { + var x; + } +} + +async function fn28(x) { + while (y) { + var x; + } +} + +async function fn29(x) { + do { + var x; + } + while (y); +} + +async function fn30(x) { + for (var x = y;;) { + + } +} + +async function fn31(x) { + for (var { x } = y;;) { + } +} + +async function fn32(x) { + for (;;) { + var x; + } +} + +async function fn33(x: string) { + for (var x in y) { + } +} + +async function fn34(x) { + for (var z in y) { + var x; + } +} + +async function fn35(x) { + for (var x of y) { + } +} + +async function fn36(x) { + for (var { x } of y) { + } +} + +async function fn37(x) { + for (var z of y) { + var x; + } +} + +async function fn38(x) { + switch (y) { + case y: + var x; + } +} + +//// [asyncWithVarShadowing_es6.js] +function fn1(x) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x; +} +function fn2(x) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x, z; +} +function fn3(x) { + return __awaiter(this, void 0, void 0, function* () { + var z; + }); +} +function fn4(x) { + return __awaiter(this, void 0, void 0, function* () { + x = y; + }); + var x; +} +function fn5(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ x } = y); + }); + var x; +} +function fn6(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ x, z } = y); + }); + var x, z; +} +function fn7(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ x = y } = y); + }); + var x; +} +function fn8(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ z: x } = y); + }); + var x; +} +function fn9(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ z: { x } } = y); + }); + var x; +} +function fn10(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ z: { x } = y } = y); + }); + var x; +} +function fn11(x) { + return __awaiter(this, void 0, void 0, function* () { + x = __rest(y, []); + }); + var x; +} +function fn12(x) { + return __awaiter(this, void 0, void 0, function* () { + [x] = y; + }); + var x; +} +function fn13(x) { + return __awaiter(this, void 0, void 0, function* () { + [x = y] = y; + }); + var x; +} +function fn14(x) { + return __awaiter(this, void 0, void 0, function* () { + [, x] = y; + }); + var x; +} +function fn15(x) { + return __awaiter(this, void 0, void 0, function* () { + [...x] = y; + }); + var x; +} +function fn16(x) { + return __awaiter(this, void 0, void 0, function* () { + [[x]] = y; + }); + var x; +} +function fn17(x) { + return __awaiter(this, void 0, void 0, function* () { + [[x] = y] = y; + }); + var x; +} +function fn18({ x }) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x; +} +function fn19([x]) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x; +} +function fn20(x) { + return __awaiter(this, void 0, void 0, function* () { + { + } + }); + var x; +} +function fn21(x) { + return __awaiter(this, void 0, void 0, function* () { + if (y) { + } + }); + var x; +} +function fn22(x) { + return __awaiter(this, void 0, void 0, function* () { + if (y) { + } + else { + } + }); + var x; +} +function fn23(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (e) { + } + }); + var x; +} +function fn24(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (e) { + } + }); + var x; +} +function fn25(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (x) { + var x; + } + }); +} +function fn26(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch ({ x }) { + var x; + } + }); +} +function fn27(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + finally { + } + }); + var x; +} +function fn28(x) { + return __awaiter(this, void 0, void 0, function* () { + while (y) { + } + }); + var x; +} +function fn29(x) { + return __awaiter(this, void 0, void 0, function* () { + do { + } while (y); + }); + var x; +} +function fn30(x) { + return __awaiter(this, void 0, void 0, function* () { + for (x = y;;) { + } + }); + var x; +} +function fn31(x) { + return __awaiter(this, void 0, void 0, function* () { + for ({ x } = y;;) { + } + }); + var x; +} +function fn32(x) { + return __awaiter(this, void 0, void 0, function* () { + for (;;) { + } + }); + var x; +} +function fn33(x) { + return __awaiter(this, void 0, void 0, function* () { + for (x in y) { + } + }); + var x; +} +function fn34(x) { + return __awaiter(this, void 0, void 0, function* () { + for (var z in y) { + } + }); + var x; +} +function fn35(x) { + return __awaiter(this, void 0, void 0, function* () { + for (x of y) { + } + }); + var x; +} +function fn36(x) { + return __awaiter(this, void 0, void 0, function* () { + for ({ x } of y) { + } + }); + var x; +} +function fn37(x) { + return __awaiter(this, void 0, void 0, function* () { + for (var z of y) { + } + }); + var x; +} +function fn38(x) { + return __awaiter(this, void 0, void 0, function* () { + switch (y) { + case y: + } + }); + var x; +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols new file mode 100644 index 00000000000..aec43c553a5 --- /dev/null +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols @@ -0,0 +1,406 @@ +=== tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts === +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + +async function fn1(x) { +>fn1 : Symbol(fn1, Decl(asyncWithVarShadowing_es6.ts, 1, 21)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 3, 19), Decl(asyncWithVarShadowing_es6.ts, 4, 7)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 3, 19), Decl(asyncWithVarShadowing_es6.ts, 4, 7)) +} + +async function fn2(x) { +>fn2 : Symbol(fn2, Decl(asyncWithVarShadowing_es6.ts, 5, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 7, 19), Decl(asyncWithVarShadowing_es6.ts, 8, 7)) + + var x, z; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 7, 19), Decl(asyncWithVarShadowing_es6.ts, 8, 7)) +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 8, 10)) +} + +async function fn3(x) { +>fn3 : Symbol(fn3, Decl(asyncWithVarShadowing_es6.ts, 9, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 11, 19)) + + var z; +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 12, 7)) +} + +async function fn4(x) { +>fn4 : Symbol(fn4, Decl(asyncWithVarShadowing_es6.ts, 13, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 15, 19), Decl(asyncWithVarShadowing_es6.ts, 16, 7)) + + var x = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 15, 19), Decl(asyncWithVarShadowing_es6.ts, 16, 7)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn5(x) { +>fn5 : Symbol(fn5, Decl(asyncWithVarShadowing_es6.ts, 17, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 19, 19), Decl(asyncWithVarShadowing_es6.ts, 20, 9)) + + var { x } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 19, 19), Decl(asyncWithVarShadowing_es6.ts, 20, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn6(x) { +>fn6 : Symbol(fn6, Decl(asyncWithVarShadowing_es6.ts, 21, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 23, 19), Decl(asyncWithVarShadowing_es6.ts, 24, 9)) + + var { x, z } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 23, 19), Decl(asyncWithVarShadowing_es6.ts, 24, 9)) +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 24, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn7(x) { +>fn7 : Symbol(fn7, Decl(asyncWithVarShadowing_es6.ts, 25, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 27, 19), Decl(asyncWithVarShadowing_es6.ts, 28, 9)) + + var { x = y } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 27, 19), Decl(asyncWithVarShadowing_es6.ts, 28, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn8(x) { +>fn8 : Symbol(fn8, Decl(asyncWithVarShadowing_es6.ts, 29, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 31, 19), Decl(asyncWithVarShadowing_es6.ts, 32, 9)) + + var { z: x } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 31, 19), Decl(asyncWithVarShadowing_es6.ts, 32, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn9(x) { +>fn9 : Symbol(fn9, Decl(asyncWithVarShadowing_es6.ts, 33, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 35, 19), Decl(asyncWithVarShadowing_es6.ts, 36, 14)) + + var { z: { x } } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 35, 19), Decl(asyncWithVarShadowing_es6.ts, 36, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn10(x) { +>fn10 : Symbol(fn10, Decl(asyncWithVarShadowing_es6.ts, 37, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 39, 20), Decl(asyncWithVarShadowing_es6.ts, 40, 14)) + + var { z: { x } = y } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 39, 20), Decl(asyncWithVarShadowing_es6.ts, 40, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn11(x) { +>fn11 : Symbol(fn11, Decl(asyncWithVarShadowing_es6.ts, 41, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 43, 20), Decl(asyncWithVarShadowing_es6.ts, 44, 9)) + + var { ...x } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 43, 20), Decl(asyncWithVarShadowing_es6.ts, 44, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn12(x) { +>fn12 : Symbol(fn12, Decl(asyncWithVarShadowing_es6.ts, 45, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 47, 20), Decl(asyncWithVarShadowing_es6.ts, 48, 9)) + + var [x] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 47, 20), Decl(asyncWithVarShadowing_es6.ts, 48, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn13(x) { +>fn13 : Symbol(fn13, Decl(asyncWithVarShadowing_es6.ts, 49, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 51, 20), Decl(asyncWithVarShadowing_es6.ts, 52, 9)) + + var [x = y] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 51, 20), Decl(asyncWithVarShadowing_es6.ts, 52, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn14(x) { +>fn14 : Symbol(fn14, Decl(asyncWithVarShadowing_es6.ts, 53, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 55, 20), Decl(asyncWithVarShadowing_es6.ts, 56, 10)) + + var [, x] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 55, 20), Decl(asyncWithVarShadowing_es6.ts, 56, 10)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn15(x) { +>fn15 : Symbol(fn15, Decl(asyncWithVarShadowing_es6.ts, 57, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 59, 20), Decl(asyncWithVarShadowing_es6.ts, 60, 9)) + + var [...x] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 59, 20), Decl(asyncWithVarShadowing_es6.ts, 60, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn16(x) { +>fn16 : Symbol(fn16, Decl(asyncWithVarShadowing_es6.ts, 61, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 63, 20), Decl(asyncWithVarShadowing_es6.ts, 64, 10)) + + var [[x]] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 63, 20), Decl(asyncWithVarShadowing_es6.ts, 64, 10)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn17(x) { +>fn17 : Symbol(fn17, Decl(asyncWithVarShadowing_es6.ts, 65, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 67, 20), Decl(asyncWithVarShadowing_es6.ts, 68, 10)) + + var [[x] = y] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 67, 20), Decl(asyncWithVarShadowing_es6.ts, 68, 10)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn18({ x }) { +>fn18 : Symbol(fn18, Decl(asyncWithVarShadowing_es6.ts, 69, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 71, 21), Decl(asyncWithVarShadowing_es6.ts, 72, 7)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 71, 21), Decl(asyncWithVarShadowing_es6.ts, 72, 7)) +} + +async function fn19([x]) { +>fn19 : Symbol(fn19, Decl(asyncWithVarShadowing_es6.ts, 73, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 75, 21), Decl(asyncWithVarShadowing_es6.ts, 76, 7)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 75, 21), Decl(asyncWithVarShadowing_es6.ts, 76, 7)) +} + +async function fn20(x) { +>fn20 : Symbol(fn20, Decl(asyncWithVarShadowing_es6.ts, 77, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 79, 20), Decl(asyncWithVarShadowing_es6.ts, 81, 11)) + { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 79, 20), Decl(asyncWithVarShadowing_es6.ts, 81, 11)) + } +} + +async function fn21(x) { +>fn21 : Symbol(fn21, Decl(asyncWithVarShadowing_es6.ts, 83, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 85, 20), Decl(asyncWithVarShadowing_es6.ts, 87, 11)) + + if (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 85, 20), Decl(asyncWithVarShadowing_es6.ts, 87, 11)) + } +} + +async function fn22(x) { +>fn22 : Symbol(fn22, Decl(asyncWithVarShadowing_es6.ts, 89, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 91, 20), Decl(asyncWithVarShadowing_es6.ts, 95, 11)) + + if (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } + else { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 91, 20), Decl(asyncWithVarShadowing_es6.ts, 95, 11)) + } +} + +async function fn23(x) { +>fn23 : Symbol(fn23, Decl(asyncWithVarShadowing_es6.ts, 97, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 99, 20), Decl(asyncWithVarShadowing_es6.ts, 101, 11)) + + try { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 99, 20), Decl(asyncWithVarShadowing_es6.ts, 101, 11)) + } + catch (e) { +>e : Symbol(e, Decl(asyncWithVarShadowing_es6.ts, 103, 11)) + } +} + +async function fn24(x) { +>fn24 : Symbol(fn24, Decl(asyncWithVarShadowing_es6.ts, 105, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 107, 20), Decl(asyncWithVarShadowing_es6.ts, 112, 11)) + + try { + + } + catch (e) { +>e : Symbol(e, Decl(asyncWithVarShadowing_es6.ts, 111, 11)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 107, 20), Decl(asyncWithVarShadowing_es6.ts, 112, 11)) + } +} + +async function fn25(x) { +>fn25 : Symbol(fn25, Decl(asyncWithVarShadowing_es6.ts, 114, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 116, 20), Decl(asyncWithVarShadowing_es6.ts, 121, 11)) + + try { + + } + catch (x) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 120, 11)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 116, 20), Decl(asyncWithVarShadowing_es6.ts, 121, 11)) + } +} + +async function fn26(x) { +>fn26 : Symbol(fn26, Decl(asyncWithVarShadowing_es6.ts, 123, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 125, 20), Decl(asyncWithVarShadowing_es6.ts, 130, 11)) + + try { + + } + catch ({ x }) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 129, 12)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 125, 20), Decl(asyncWithVarShadowing_es6.ts, 130, 11)) + } +} + +async function fn27(x) { +>fn27 : Symbol(fn27, Decl(asyncWithVarShadowing_es6.ts, 132, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 134, 20), Decl(asyncWithVarShadowing_es6.ts, 138, 11)) + + try { + } + finally { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 134, 20), Decl(asyncWithVarShadowing_es6.ts, 138, 11)) + } +} + +async function fn28(x) { +>fn28 : Symbol(fn28, Decl(asyncWithVarShadowing_es6.ts, 140, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 142, 20), Decl(asyncWithVarShadowing_es6.ts, 144, 11)) + + while (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 142, 20), Decl(asyncWithVarShadowing_es6.ts, 144, 11)) + } +} + +async function fn29(x) { +>fn29 : Symbol(fn29, Decl(asyncWithVarShadowing_es6.ts, 146, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 148, 20), Decl(asyncWithVarShadowing_es6.ts, 150, 11)) + + do { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 148, 20), Decl(asyncWithVarShadowing_es6.ts, 150, 11)) + } + while (y); +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn30(x) { +>fn30 : Symbol(fn30, Decl(asyncWithVarShadowing_es6.ts, 153, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 155, 20), Decl(asyncWithVarShadowing_es6.ts, 156, 12)) + + for (var x = y;;) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 155, 20), Decl(asyncWithVarShadowing_es6.ts, 156, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + } +} + +async function fn31(x) { +>fn31 : Symbol(fn31, Decl(asyncWithVarShadowing_es6.ts, 159, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 161, 20), Decl(asyncWithVarShadowing_es6.ts, 162, 14)) + + for (var { x } = y;;) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 161, 20), Decl(asyncWithVarShadowing_es6.ts, 162, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn32(x) { +>fn32 : Symbol(fn32, Decl(asyncWithVarShadowing_es6.ts, 164, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 166, 20), Decl(asyncWithVarShadowing_es6.ts, 168, 11)) + + for (;;) { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 166, 20), Decl(asyncWithVarShadowing_es6.ts, 168, 11)) + } +} + +async function fn33(x: string) { +>fn33 : Symbol(fn33, Decl(asyncWithVarShadowing_es6.ts, 170, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 172, 20), Decl(asyncWithVarShadowing_es6.ts, 173, 12)) + + for (var x in y) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 172, 20), Decl(asyncWithVarShadowing_es6.ts, 173, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn34(x) { +>fn34 : Symbol(fn34, Decl(asyncWithVarShadowing_es6.ts, 175, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 177, 20), Decl(asyncWithVarShadowing_es6.ts, 179, 11)) + + for (var z in y) { +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 178, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 177, 20), Decl(asyncWithVarShadowing_es6.ts, 179, 11)) + } +} + +async function fn35(x) { +>fn35 : Symbol(fn35, Decl(asyncWithVarShadowing_es6.ts, 181, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 183, 20), Decl(asyncWithVarShadowing_es6.ts, 184, 12)) + + for (var x of y) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 183, 20), Decl(asyncWithVarShadowing_es6.ts, 184, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn36(x) { +>fn36 : Symbol(fn36, Decl(asyncWithVarShadowing_es6.ts, 186, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 188, 20), Decl(asyncWithVarShadowing_es6.ts, 189, 14)) + + for (var { x } of y) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 188, 20), Decl(asyncWithVarShadowing_es6.ts, 189, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn37(x) { +>fn37 : Symbol(fn37, Decl(asyncWithVarShadowing_es6.ts, 191, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 193, 20), Decl(asyncWithVarShadowing_es6.ts, 195, 11)) + + for (var z of y) { +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 194, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 193, 20), Decl(asyncWithVarShadowing_es6.ts, 195, 11)) + } +} + +async function fn38(x) { +>fn38 : Symbol(fn38, Decl(asyncWithVarShadowing_es6.ts, 197, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 199, 20), Decl(asyncWithVarShadowing_es6.ts, 202, 15)) + + switch (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + case y: +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 199, 20), Decl(asyncWithVarShadowing_es6.ts, 202, 15)) + } +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.types b/tests/baselines/reference/asyncWithVarShadowing_es6.types new file mode 100644 index 00000000000..42a81f168b0 --- /dev/null +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.types @@ -0,0 +1,410 @@ +=== tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts === +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; +>y : any + +async function fn1(x) { +>fn1 : (x: any) => Promise +>x : any + + var x; +>x : any +} + +async function fn2(x) { +>fn2 : (x: any) => Promise +>x : any + + var x, z; +>x : any +>z : any +} + +async function fn3(x) { +>fn3 : (x: any) => Promise +>x : any + + var z; +>z : any +} + +async function fn4(x) { +>fn4 : (x: any) => Promise +>x : any + + var x = y; +>x : any +>y : any +} + +async function fn5(x) { +>fn5 : (x: any) => Promise +>x : any + + var { x } = y; +>x : any +>y : any +} + +async function fn6(x) { +>fn6 : (x: any) => Promise +>x : any + + var { x, z } = y; +>x : any +>z : any +>y : any +} + +async function fn7(x) { +>fn7 : (x: any) => Promise +>x : any + + var { x = y } = y; +>x : any +>y : any +>y : any +} + +async function fn8(x) { +>fn8 : (x: any) => Promise +>x : any + + var { z: x } = y; +>z : any +>x : any +>y : any +} + +async function fn9(x) { +>fn9 : (x: any) => Promise +>x : any + + var { z: { x } } = y; +>z : any +>x : any +>y : any +} + +async function fn10(x) { +>fn10 : (x: any) => Promise +>x : any + + var { z: { x } = y } = y; +>z : any +>x : any +>y : any +>y : any +} + +async function fn11(x) { +>fn11 : (x: any) => Promise +>x : any + + var { ...x } = y; +>x : any +>y : any +} + +async function fn12(x) { +>fn12 : (x: any) => Promise +>x : any + + var [x] = y; +>x : any +>y : any +} + +async function fn13(x) { +>fn13 : (x: any) => Promise +>x : any + + var [x = y] = y; +>x : any +>y : any +>y : any +} + +async function fn14(x) { +>fn14 : (x: any) => Promise +>x : any + + var [, x] = y; +> : undefined +>x : any +>y : any +} + +async function fn15(x) { +>fn15 : (x: any) => Promise +>x : any + + var [...x] = y; +>x : any +>y : any +} + +async function fn16(x) { +>fn16 : (x: any) => Promise +>x : any + + var [[x]] = y; +>x : any +>y : any +} + +async function fn17(x) { +>fn17 : (x: any) => Promise +>x : any + + var [[x] = y] = y; +>x : any +>y : any +>y : any +} + +async function fn18({ x }) { +>fn18 : ({ x }: { x: any; }) => Promise +>x : any + + var x; +>x : any +} + +async function fn19([x]) { +>fn19 : ([x]: [any]) => Promise +>x : any + + var x; +>x : any +} + +async function fn20(x) { +>fn20 : (x: any) => Promise +>x : any + { + var x; +>x : any + } +} + +async function fn21(x) { +>fn21 : (x: any) => Promise +>x : any + + if (y) { +>y : any + + var x; +>x : any + } +} + +async function fn22(x) { +>fn22 : (x: any) => Promise +>x : any + + if (y) { +>y : any + } + else { + var x; +>x : any + } +} + +async function fn23(x) { +>fn23 : (x: any) => Promise +>x : any + + try { + var x; +>x : any + } + catch (e) { +>e : any + } +} + +async function fn24(x) { +>fn24 : (x: any) => Promise +>x : any + + try { + + } + catch (e) { +>e : any + + var x; +>x : any + } +} + +async function fn25(x) { +>fn25 : (x: any) => Promise +>x : any + + try { + + } + catch (x) { +>x : any + + var x; +>x : any + } +} + +async function fn26(x) { +>fn26 : (x: any) => Promise +>x : any + + try { + + } + catch ({ x }) { +>x : any + + var x; +>x : any + } +} + +async function fn27(x) { +>fn27 : (x: any) => Promise +>x : any + + try { + } + finally { + var x; +>x : any + } +} + +async function fn28(x) { +>fn28 : (x: any) => Promise +>x : any + + while (y) { +>y : any + + var x; +>x : any + } +} + +async function fn29(x) { +>fn29 : (x: any) => Promise +>x : any + + do { + var x; +>x : any + } + while (y); +>y : any +} + +async function fn30(x) { +>fn30 : (x: any) => Promise +>x : any + + for (var x = y;;) { +>x : any +>y : any + + } +} + +async function fn31(x) { +>fn31 : (x: any) => Promise +>x : any + + for (var { x } = y;;) { +>x : any +>y : any + } +} + +async function fn32(x) { +>fn32 : (x: any) => Promise +>x : any + + for (;;) { + var x; +>x : any + } +} + +async function fn33(x: string) { +>fn33 : (x: string) => Promise +>x : string + + for (var x in y) { +>x : string +>y : any + } +} + +async function fn34(x) { +>fn34 : (x: any) => Promise +>x : any + + for (var z in y) { +>z : string +>y : any + + var x; +>x : any + } +} + +async function fn35(x) { +>fn35 : (x: any) => Promise +>x : any + + for (var x of y) { +>x : any +>y : any + } +} + +async function fn36(x) { +>fn36 : (x: any) => Promise +>x : any + + for (var { x } of y) { +>x : any +>y : any + } +} + +async function fn37(x) { +>fn37 : (x: any) => Promise +>x : any + + for (var z of y) { +>z : any +>y : any + + var x; +>x : any + } +} + +async function fn38(x) { +>fn38 : (x: any) => Promise +>x : any + + switch (y) { +>y : any + + case y: +>y : any + + var x; +>x : any + } +} diff --git a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts new file mode 100644 index 00000000000..017be33edb4 --- /dev/null +++ b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts @@ -0,0 +1,207 @@ +// @target: es2015 +// @noEmitHelpers: true +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; + +async function fn1(x) { + var x; +} + +async function fn2(x) { + var x, z; +} + +async function fn3(x) { + var z; +} + +async function fn4(x) { + var x = y; +} + +async function fn5(x) { + var { x } = y; +} + +async function fn6(x) { + var { x, z } = y; +} + +async function fn7(x) { + var { x = y } = y; +} + +async function fn8(x) { + var { z: x } = y; +} + +async function fn9(x) { + var { z: { x } } = y; +} + +async function fn10(x) { + var { z: { x } = y } = y; +} + +async function fn11(x) { + var { ...x } = y; +} + +async function fn12(x) { + var [x] = y; +} + +async function fn13(x) { + var [x = y] = y; +} + +async function fn14(x) { + var [, x] = y; +} + +async function fn15(x) { + var [...x] = y; +} + +async function fn16(x) { + var [[x]] = y; +} + +async function fn17(x) { + var [[x] = y] = y; +} + +async function fn18({ x }) { + var x; +} + +async function fn19([x]) { + var x; +} + +async function fn20(x) { + { + var x; + } +} + +async function fn21(x) { + if (y) { + var x; + } +} + +async function fn22(x) { + if (y) { + } + else { + var x; + } +} + +async function fn23(x) { + try { + var x; + } + catch (e) { + } +} + +async function fn24(x) { + try { + + } + catch (e) { + var x; + } +} + +async function fn25(x) { + try { + + } + catch (x) { + var x; + } +} + +async function fn26(x) { + try { + + } + catch ({ x }) { + var x; + } +} + +async function fn27(x) { + try { + } + finally { + var x; + } +} + +async function fn28(x) { + while (y) { + var x; + } +} + +async function fn29(x) { + do { + var x; + } + while (y); +} + +async function fn30(x) { + for (var x = y;;) { + + } +} + +async function fn31(x) { + for (var { x } = y;;) { + } +} + +async function fn32(x) { + for (;;) { + var x; + } +} + +async function fn33(x: string) { + for (var x in y) { + } +} + +async function fn34(x) { + for (var z in y) { + var x; + } +} + +async function fn35(x) { + for (var x of y) { + } +} + +async function fn36(x) { + for (var { x } of y) { + } +} + +async function fn37(x) { + for (var z of y) { + var x; + } +} + +async function fn38(x) { + switch (y) { + case y: + var x; + } +} \ No newline at end of file From c4fddba0a9459c3ff0582ae787004368b9f063ef Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 17:11:32 -0800 Subject: [PATCH 044/101] Remove duplicate implementations --- src/compiler/transformers/es2017.ts | 77 ++--------------------------- 1 file changed, 5 insertions(+), 72 deletions(-) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 5733d089a7d..f1bd671d461 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -322,7 +322,7 @@ namespace ts { const variables = getInitializedVariables(node); if (variables.length === 0) { if (hasReceiver) { - return convertBindingNameToAssignmentTarget(node.declarations[0].name); + return visitNode(convertToAssignmentElementTarget(node.declarations[0].name), visitor, isExpression); } return undefined; } @@ -348,81 +348,14 @@ namespace ts { } function transformInitializedVariable(node: VariableDeclaration) { - return setSourceMapRange( + const converted = setSourceMapRange( createAssignment( - convertBindingNameToAssignmentTarget(node.name), - visitNode(node.initializer, visitor, isExpression) + convertToAssignmentElementTarget(node.name), + node.initializer ), node ); - } - - function convertBindingNameToAssignmentTarget(node: BindingName): Identifier | AssignmentPattern { - return isObjectBindingPattern(node) ? convertObjectBindingPatternToObjectAssignmentPattern(node) : - isArrayBindingPattern(node) ? convertArrayBindingPatternToArrayAssignmentPattern(node) : - setSourceMapRange(getSynthesizedClone(node), node); - } - - function convertObjectBindingPatternToObjectAssignmentPattern(node: ObjectBindingPattern): ObjectLiteralExpression { - return createObjectLiteral( - map(node.elements, convertObjectBindingElementToObjectAssignmentElement) - ); - } - - function convertObjectBindingElementToObjectAssignmentElement(node: BindingElement): ObjectLiteralElementLike { - if (node.propertyName) { - let expression: Expression = convertBindingNameToAssignmentTarget(node.name); - if (node.initializer) { - expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); - } - return setSourceMapRange( - createPropertyAssignment( - visitNode(node.propertyName, visitor, isPropertyName), - expression - ), - node - ); - } - else if (node.dotDotDotToken) { - return setSourceMapRange( - createSpreadAssignment( - visitNode(cast(node.name, isIdentifier), visitor, isIdentifier) - ), - node - ); - } - else { - return setSourceMapRange( - createShorthandPropertyAssignment( - visitNode(cast(node.name, isIdentifier), visitor, isIdentifier), - visitNode(node.initializer, visitor, isExpression) - ), - node - ); - } - } - - function convertArrayBindingPatternToArrayAssignmentPattern(node: ArrayBindingPattern): ArrayLiteralExpression { - return setSourceMapRange( - createArrayLiteral( - map(node.elements, convertArrayBindingElementToArrayAssignmentElement) - ), - node - ); - } - - function convertArrayBindingElementToArrayAssignmentElement(node: ArrayBindingElement): Expression { - if (isOmittedExpression(node)) return node; - let expression: Expression = convertBindingNameToAssignmentTarget(node.name); - if (node.initializer) { - expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); - setSourceMapRange(expression, node); - } - else if (node.dotDotDotToken) { - expression = createSpread(expression); - setSourceMapRange(expression, node); - } - return expression; + return visitNode(converted, visitor, isExpression); } function collidesWithParameterName({ name }: VariableDeclaration | BindingElement): boolean { From 2ba29d8d9d923d99b828a999b99c47db9cd1184f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:24:28 -0800 Subject: [PATCH 045/101] Support labeled statement --- src/compiler/transformers/es2017.ts | 6 ++-- src/compiler/utilities.ts | 29 +++++++++++++++++++ .../reference/asyncWithVarShadowing_es6.js | 15 ++++++++++ .../asyncWithVarShadowing_es6.symbols | 12 ++++++++ .../reference/asyncWithVarShadowing_es6.types | 15 ++++++++++ .../async/es6/asyncWithVarShadowing_es6.ts | 7 +++++ 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index f1bd671d461..79668dc0b8c 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -95,6 +95,8 @@ namespace ts { return visitForInStatementInAsyncBody(node); case SyntaxKind.ForOfStatement: return visitForOfStatementInAsyncBody(node); + case SyntaxKind.CatchClause: + return visitCatchClauseInAsyncBody(node); case SyntaxKind.Block: case SyntaxKind.SwitchStatement: case SyntaxKind.CaseBlock: @@ -105,10 +107,10 @@ namespace ts { case SyntaxKind.WhileStatement: case SyntaxKind.IfStatement: case SyntaxKind.WithStatement: + case SyntaxKind.LabeledStatement: return visitEachChild(node, asyncBodyVisitor, context); - case SyntaxKind.CatchClause: - return visitCatchClauseInAsyncBody(node); } + Debug.assert(!isNodeWithPossibleVarDeclaration(node), "Unhandled node."); return visitor(node); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 796ed35a861..5510353d1da 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1761,6 +1761,35 @@ namespace ts { return getAssignmentTargetKind(node) !== AssignmentKind.None; } + /** + * Indicates whether a node could contain embedded statements that share the same `var` + * declaration scope as its parent. + */ + export function isNodeWithPossibleVarDeclaration(node: Node) { + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.WithStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.LabeledStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ModuleBlock: + return true; + } + return false; + } + function walkUp(node: Node, kind: SyntaxKind) { while (node && node.kind === kind) { node = node.parent; diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.js b/tests/baselines/reference/asyncWithVarShadowing_es6.js index 726bf935cf6..e362a74072d 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.js +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.js @@ -203,6 +203,13 @@ async function fn38(x) { case y: var x; } +} + +async function fn39(x) { + foo: { + var x; + break foo; + } } //// [asyncWithVarShadowing_es6.js] @@ -461,3 +468,11 @@ function fn38(x) { }); var x; } +function fn39(x) { + return __awaiter(this, void 0, void 0, function* () { + foo: { + break foo; + } + }); + var x; +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols index aec43c553a5..f3fd27e8971 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols @@ -404,3 +404,15 @@ async function fn38(x) { >x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 199, 20), Decl(asyncWithVarShadowing_es6.ts, 202, 15)) } } + +async function fn39(x) { +>fn39 : Symbol(fn39, Decl(asyncWithVarShadowing_es6.ts, 204, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 206, 20), Decl(asyncWithVarShadowing_es6.ts, 208, 11)) + + foo: { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 206, 20), Decl(asyncWithVarShadowing_es6.ts, 208, 11)) + + break foo; + } +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.types b/tests/baselines/reference/asyncWithVarShadowing_es6.types index 42a81f168b0..5544c8be2c6 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.types +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.types @@ -408,3 +408,18 @@ async function fn38(x) { >x : any } } + +async function fn39(x) { +>fn39 : (x: any) => Promise +>x : any + + foo: { +>foo : any + + var x; +>x : any + + break foo; +>foo : any + } +} diff --git a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts index 017be33edb4..7c9a9846376 100644 --- a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts +++ b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts @@ -204,4 +204,11 @@ async function fn38(x) { case y: var x; } +} + +async function fn39(x) { + foo: { + var x; + break foo; + } } \ No newline at end of file From e655446318dbbdcf2dab1a232d607d5f1f89351c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:25:53 -0800 Subject: [PATCH 046/101] Add test for catch block without variable --- .../reference/asyncWithVarShadowing_es6.js | 21 ++++++++++++++++++- .../asyncWithVarShadowing_es6.symbols | 14 +++++++++++++ .../reference/asyncWithVarShadowing_es6.types | 14 +++++++++++++ .../async/es6/asyncWithVarShadowing_es6.ts | 11 +++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.js b/tests/baselines/reference/asyncWithVarShadowing_es6.js index e362a74072d..392bed9d294 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.js +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.js @@ -210,7 +210,17 @@ async function fn39(x) { var x; break foo; } -} +} + +async function fn40(x) { + try { + + } + catch { + var x; + } +} + //// [asyncWithVarShadowing_es6.js] function fn1(x) { @@ -476,3 +486,12 @@ function fn39(x) { }); var x; } +function fn40(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (_a) { + } + }); + var x; +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols index f3fd27e8971..ff95648722f 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols @@ -416,3 +416,17 @@ async function fn39(x) { break foo; } } + +async function fn40(x) { +>fn40 : Symbol(fn40, Decl(asyncWithVarShadowing_es6.ts, 211, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 213, 20), Decl(asyncWithVarShadowing_es6.ts, 218, 11)) + + try { + + } + catch { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 213, 20), Decl(asyncWithVarShadowing_es6.ts, 218, 11)) + } +} + diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.types b/tests/baselines/reference/asyncWithVarShadowing_es6.types index 5544c8be2c6..f87e452b2a1 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.types +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.types @@ -423,3 +423,17 @@ async function fn39(x) { >foo : any } } + +async function fn40(x) { +>fn40 : (x: any) => Promise +>x : any + + try { + + } + catch { + var x; +>x : any + } +} + diff --git a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts index 7c9a9846376..9083246dd18 100644 --- a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts +++ b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts @@ -211,4 +211,13 @@ async function fn39(x) { var x; break foo; } -} \ No newline at end of file +} + +async function fn40(x) { + try { + + } + catch { + var x; + } +} From afaa13947576c828b418d89fa67301df039d500d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:30:48 -0800 Subject: [PATCH 047/101] Namespaces do not have the same 'var' scope --- src/compiler/utilities.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5510353d1da..7b80da8f639 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1783,8 +1783,6 @@ namespace ts { case SyntaxKind.WhileStatement: case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ModuleBlock: return true; } return false; From 5b45db790729508553016572a5cddb8e806bc214 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:55:43 -0800 Subject: [PATCH 048/101] PR Feedback --- src/compiler/transformers/es2017.ts | 51 +++++++++++++++-------------- src/compiler/utilities.ts | 26 ++++++++++++--- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 79668dc0b8c..905686c8861 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -86,31 +86,34 @@ namespace ts { } function asyncBodyVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatementInAsyncBody(node); - case SyntaxKind.ForStatement: - return visitForStatementInAsyncBody(node); - case SyntaxKind.ForInStatement: - return visitForInStatementInAsyncBody(node); - case SyntaxKind.ForOfStatement: - return visitForOfStatementInAsyncBody(node); - case SyntaxKind.CatchClause: - return visitCatchClauseInAsyncBody(node); - case SyntaxKind.Block: - case SyntaxKind.SwitchStatement: - case SyntaxKind.CaseBlock: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - case SyntaxKind.TryStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.LabeledStatement: - return visitEachChild(node, asyncBodyVisitor, context); + if (isNodeWithPossibleHoistedDeclaration(node)) { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatementInAsyncBody(node); + case SyntaxKind.ForStatement: + return visitForStatementInAsyncBody(node); + case SyntaxKind.ForInStatement: + return visitForInStatementInAsyncBody(node); + case SyntaxKind.ForOfStatement: + return visitForOfStatementInAsyncBody(node); + case SyntaxKind.CatchClause: + return visitCatchClauseInAsyncBody(node); + case SyntaxKind.Block: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.LabeledStatement: + return visitEachChild(node, asyncBodyVisitor, context); + default: + return Debug.assertNever(node, "Unhandled node."); + } } - Debug.assert(!isNodeWithPossibleVarDeclaration(node), "Unhandled node."); return visitor(node); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7b80da8f639..eabc9921c1a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1761,14 +1761,32 @@ namespace ts { return getAssignmentTargetKind(node) !== AssignmentKind.None; } + export type NodeWithPossibleHoistedDeclaration = + | Block + | VariableStatement + | WithStatement + | IfStatement + | SwitchStatement + | CaseBlock + | CaseClause + | DefaultClause + | LabeledStatement + | ForStatement + | ForInStatement + | ForOfStatement + | DoStatement + | WhileStatement + | TryStatement + | CatchClause; + /** - * Indicates whether a node could contain embedded statements that share the same `var` - * declaration scope as its parent. + * Indicates whether a node could contain a `var` VariableDeclarationList that contributes to + * the same `var` declaration scope as the node's parent. */ - export function isNodeWithPossibleVarDeclaration(node: Node) { + export function isNodeWithPossibleHoistedDeclaration(node: Node): node is NodeWithPossibleHoistedDeclaration { switch (node.kind) { - case SyntaxKind.SourceFile: case SyntaxKind.Block: + case SyntaxKind.VariableStatement: case SyntaxKind.WithStatement: case SyntaxKind.IfStatement: case SyntaxKind.SwitchStatement: From ea6d7e91757dbaad991f9424b321cdd3cada0f37 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 12:44:03 -0800 Subject: [PATCH 049/101] Make issue template more enthusiastic --- issue_template.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/issue_template.md b/issue_template.md index 22acf3e9cc5..4fc8cd3ad95 100644 --- a/issue_template.md +++ b/issue_template.md @@ -1,10 +1,33 @@ - - - + + + + + + + + **TypeScript Version:** 2.7.0-dev.201xxxxx + +**Search Terms:** + **Code** ```ts @@ -16,4 +39,6 @@ **Actual behavior:** -**Related:** +**Playground Link:** + +**Related Issues:** From 7e1e038cf30450a554b886b48f730dd20ac0c30b Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 13:17:56 -0800 Subject: [PATCH 050/101] Update issue_template.md --- issue_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issue_template.md b/issue_template.md index 4fc8cd3ad95..5e3d612c3c3 100644 --- a/issue_template.md +++ b/issue_template.md @@ -14,7 +14,7 @@ Please help us by doing the following steps before logging an issue: --> From 5c889299f4ddb4ba53b704a423da01e2d21275b9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 17 Jan 2018 13:21:10 -0800 Subject: [PATCH 051/101] Indexed access relation check object+index types Previously, it only check the object types, and only if the index types were identical. Now both checks call `isRelatedTo` recursively. --- src/compiler/checker.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 489fff16e2c..0cfb6d1a381 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9632,7 +9632,7 @@ namespace ts { } else if (target.flags & TypeFlags.IndexedAccess) { // A type S is related to a type T[K] if S is related to A[K], where K is string-like and - // A is the apparent type of T. + // A is the constraint of T. const constraint = getConstraintOfIndexedAccess(target); if (constraint) { if (result = isRelatedTo(source, constraint, reportErrors)) { @@ -9668,7 +9668,7 @@ namespace ts { } else if (source.flags & TypeFlags.IndexedAccess) { // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and - // A is the apparent type of S. + // A is the constraint of S. const constraint = getConstraintOfIndexedAccess(source); if (constraint) { if (result = isRelatedTo(constraint, target, reportErrors)) { @@ -9676,10 +9676,11 @@ namespace ts { return result; } } - else if (target.flags & TypeFlags.IndexedAccess && (source).indexType === (target).indexType) { - // if we have indexed access types with identical index types, see if relationship holds for - // the two object types. + else if (target.flags & TypeFlags.IndexedAccess) { if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { + result &= isRelatedTo((source).indexType, (target).indexType, reportErrors); + } + if (result) { errorInfo = saveErrorInfo; return result; } From 485ec34e8ed7d3240acc567c2287dd822ca2aea8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 17 Jan 2018 13:22:31 -0800 Subject: [PATCH 052/101] Test assignability of indexed access types --- .../reference/keyofAndIndexedAccess.js | 24 +++++ .../reference/keyofAndIndexedAccess.symbols | 45 +++++++++ .../reference/keyofAndIndexedAccess.types | 48 ++++++++++ .../keyofAndIndexedAccessErrors.errors.txt | 53 +++++++++-- .../reference/keyofAndIndexedAccessErrors.js | 37 ++++++-- .../keyofAndIndexedAccessErrors.symbols | 90 ++++++++++++++---- .../keyofAndIndexedAccessErrors.types | 93 ++++++++++++++++--- .../types/keyof/keyofAndIndexedAccess.ts | 13 +++ .../keyof/keyofAndIndexedAccessErrors.ts | 21 ++++- 9 files changed, 377 insertions(+), 47 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess.js b/tests/baselines/reference/keyofAndIndexedAccess.js index 8ea17554b35..2f59ad03510 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.js +++ b/tests/baselines/reference/keyofAndIndexedAccess.js @@ -552,6 +552,19 @@ class AnotherSampleClass extends SampleClass { } } new AnotherSampleClass({}); + +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { + for (let key in t) { + key = k // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } +} + +// # 21185 +type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +} //// [keyofAndIndexedAccess.js] @@ -928,6 +941,13 @@ var AnotherSampleClass = /** @class */ (function (_super) { return AnotherSampleClass; }(SampleClass)); new AnotherSampleClass({}); +// Positive repro from #17166 +function f3(t, k, tk) { + for (var key in t) { + key = k; // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } +} //// [keyofAndIndexedAccess.d.ts] @@ -1188,3 +1208,7 @@ declare class AnotherSampleClass extends SampleClass { constructor(props: T); brokenMethod(): void; } +declare function f3(t: T, k: K, tk: T[K]): void; +declare type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T]; +}; diff --git a/tests/baselines/reference/keyofAndIndexedAccess.symbols b/tests/baselines/reference/keyofAndIndexedAccess.symbols index 461652fb725..c1a3449991f 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccess.symbols @@ -1962,3 +1962,48 @@ class AnotherSampleClass extends SampleClass { new AnotherSampleClass({}); >AnotherSampleClass : Symbol(AnotherSampleClass, Decl(keyofAndIndexedAccess.ts, 540, 54)) +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { +>f3 : Symbol(f3, Decl(keyofAndIndexedAccess.ts, 552, 27)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 555, 14)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 555, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 555, 39)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 555, 14)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccess.ts, 555, 45)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 555, 14)) + + for (let key in t) { +>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 556, 12)) +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 555, 34)) + + key = k // ok, K ==> keyof T +>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 556, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 555, 39)) + + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 555, 34)) +>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 556, 12)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccess.ts, 555, 45)) + } +} + +// # 21185 +type Predicates = { +>Predicates : Symbol(Predicates, Decl(keyofAndIndexedAccess.ts, 560, 1)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) + + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 564, 3)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>variant : Symbol(variant, Decl(keyofAndIndexedAccess.ts, 564, 30)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>variant : Symbol(variant, Decl(keyofAndIndexedAccess.ts, 564, 30)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 564, 3)) +} + diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index 0e7da19c3e2..d60245e183f 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -2294,3 +2294,51 @@ new AnotherSampleClass({}); >AnotherSampleClass : typeof AnotherSampleClass >{} : {} +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { +>f3 : (t: T, k: K, tk: T[K]) => void +>T : T +>K : K +>T : T +>t : T +>T : T +>k : K +>K : K +>tk : T[K] +>T : T +>K : K + + for (let key in t) { +>key : keyof T +>t : T + + key = k // ok, K ==> keyof T +>key = k : K +>key : keyof T +>k : K + + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t[key] = tk : T[K] +>t[key] : T[keyof T] +>t : T +>key : keyof T +>tk : T[K] + } +} + +// # 21185 +type Predicates = { +>Predicates : Predicates +>TaggedRecord : TaggedRecord + + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +>T : T +>TaggedRecord : TaggedRecord +>variant : TaggedRecord[keyof TaggedRecord] +>TaggedRecord : TaggedRecord +>TaggedRecord : TaggedRecord +>variant : any +>TaggedRecord : TaggedRecord +>T : T +} + diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt index 2981cb0366d..0b2da3f8e61 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt @@ -27,11 +27,21 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error Type 'T' is not assignable to type 'T & U'. Type 'T' is not assignable to type 'U'. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. -tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(84,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. -tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(86,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(88,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. + Type 'keyof T' is not assignable to type 'K'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(91,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(94,5): error TS2322: Type 'T[J]' is not assignable to type 'U[J]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(97,5): error TS2322: Type 'T[K]' is not assignable to type 'T[J]'. + Type 'K' is not assignable to type 'J'. + Type 'keyof T' is not assignable to type 'J'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(100,5): error TS2322: Type 'T[K]' is not assignable to type 'U[J]'. + Type 'T' is not assignable to type 'U'. -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (27 errors) ==== +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (31 errors) ==== class Shape { name: string; width: number; @@ -167,15 +177,42 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error } // Repro from #17166 - function f3(obj: T, k: K, value: T[K]): void { - for (let key in obj) { + function f3( + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { + for (let key in t) { + key = k // ok, K ==> keyof T k = key // error, keyof T =/=> K ~ !!! error TS2322: Type 'keyof T' is not assignable to type 'K'. - value = obj[key]; // error, T[keyof T] =/=> T[K] - ~~~~~ + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] + ~~ !!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'keyof T' is not assignable to type 'K'. } - } + tk = uk; + uk = tk; // error + ~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + tj = uj; + uj = tj; // error + ~~ +!!! error TS2322: Type 'T[J]' is not assignable to type 'U[J]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + + tk = tj; + tj = tk; // error + ~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'T[J]'. +!!! error TS2322: Type 'K' is not assignable to type 'J'. +!!! error TS2322: Type 'keyof T' is not assignable to type 'J'. + + tk = uj; + uj = tk; // error + ~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[J]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + } \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.js b/tests/baselines/reference/keyofAndIndexedAccessErrors.js index 2a1042cf4ee..d267699ffd6 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.js +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.js @@ -80,13 +80,26 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { - for (let key in obj) { +function f3( + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { + for (let key in t) { + key = k // ok, K ==> keyof T k = key // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] } -} + tk = uk; + uk = tk; // error + tj = uj; + uj = tj; // error + + tk = tj; + tj = tk; // error + + tk = uj; + uj = tk; // error +} //// [keyofAndIndexedAccessErrors.js] @@ -120,9 +133,19 @@ function f20(k1, k2, o1, o2) { k2 = k1; } // Repro from #17166 -function f3(obj, k, value) { - for (var key in obj) { +function f3(t, k, tk, u, j, uk, tj, uj) { + for (var key in t) { + key = k; // ok, K ==> keyof T k = key; // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] } + tk = uk; + uk = tk; // error + tj = uj; + uj = tj; // error + tk = tj; + tj = tk; // error + tk = uj; + uj = tk; // error } diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols b/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols index 0e97ef793fb..47b22af2541 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols @@ -270,32 +270,90 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { +function f3( >f3 : Symbol(f3, Decl(keyofAndIndexedAccessErrors.ts, 78, 1)) >T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) >K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) >T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) ->obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) ->T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) ->k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41)) ->K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) ->value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) >T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) >K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) - for (let key in obj) { ->key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) ->obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 82, 9)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>u : Symbol(u, Decl(keyofAndIndexedAccessErrors.ts, 82, 25)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) +>j : Symbol(j, Decl(keyofAndIndexedAccessErrors.ts, 82, 31)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) +>uk : Symbol(uk, Decl(keyofAndIndexedAccessErrors.ts, 82, 37)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) + + for (let key in t) { +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) + + key = k // ok, K ==> keyof T +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 82, 9)) k = key // error, keyof T =/=> K ->k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41)) ->key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 82, 9)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) - value = obj[key]; // error, T[keyof T] =/=> T[K] ->value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47)) ->obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) ->key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) + + tk = t[key]; // error, T[keyof T] =/=> T[K] +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) } + tk = uk; +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>uk : Symbol(uk, Decl(keyofAndIndexedAccessErrors.ts, 82, 37)) + + uk = tk; // error +>uk : Symbol(uk, Decl(keyofAndIndexedAccessErrors.ts, 82, 37)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) + + tj = uj; +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) + + uj = tj; // error +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) + + tk = tj; +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) + + tj = tk; // error +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) + + tk = uj; +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) + + uj = tk; // error +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) } - diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.types b/tests/baselines/reference/keyofAndIndexedAccessErrors.types index f5cc7c093d9..0c51d3a575c 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.types +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.types @@ -301,35 +301,104 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { ->f3 : (obj: T, k: K, value: T[K]) => void +function f3( +>f3 : (t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]) => void >T : T >K : K >T : T ->obj : T +>U : U +>T : T +>J : J +>K : K + + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { +>t : T >T : T >k : K >K : K ->value : T[K] +>tk : T[K] >T : T >K : K +>u : U +>U : U +>j : J +>J : J +>uk : U[K] +>U : U +>K : K +>tj : T[J] +>T : T +>J : J +>uj : U[J] +>U : U +>J : J - for (let key in obj) { + for (let key in t) { >key : keyof T ->obj : T +>t : T + + key = k // ok, K ==> keyof T +>key = k : K +>key : keyof T +>k : K k = key // error, keyof T =/=> K >k = key : keyof T >k : K >key : keyof T - value = obj[key]; // error, T[keyof T] =/=> T[K] ->value = obj[key] : T[keyof T] ->value : T[K] ->obj[key] : T[keyof T] ->obj : T + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t[key] = tk : T[K] +>t[key] : T[keyof T] +>t : T +>key : keyof T +>tk : T[K] + + tk = t[key]; // error, T[keyof T] =/=> T[K] +>tk = t[key] : T[keyof T] +>tk : T[K] +>t[key] : T[keyof T] +>t : T >key : keyof T } + tk = uk; +>tk = uk : U[K] +>tk : T[K] +>uk : U[K] + + uk = tk; // error +>uk = tk : T[K] +>uk : U[K] +>tk : T[K] + + tj = uj; +>tj = uj : U[J] +>tj : T[J] +>uj : U[J] + + uj = tj; // error +>uj = tj : T[J] +>uj : U[J] +>tj : T[J] + + tk = tj; +>tk = tj : T[J] +>tk : T[K] +>tj : T[J] + + tj = tk; // error +>tj = tk : T[K] +>tj : T[J] +>tk : T[K] + + tk = uj; +>tk = uj : U[J] +>tk : T[K] +>uj : U[J] + + uj = tk; // error +>uj = tk : T[K] +>uj : U[J] +>tk : T[K] } - diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts index 70b70dff6ea..9ec4e820f73 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts @@ -554,3 +554,16 @@ class AnotherSampleClass extends SampleClass { } } new AnotherSampleClass({}); + +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { + for (let key in t) { + key = k // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } +} + +// # 21185 +type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +} diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts index ffc5076831a..f96bcdb6234 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts @@ -79,10 +79,23 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { - for (let key in obj) { +function f3( + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { + for (let key in t) { + key = k // ok, K ==> keyof T k = key // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] } -} + tk = uk; + uk = tk; // error + tj = uj; + uj = tj; // error + + tk = tj; + tj = tk; // error + + tk = uj; + uj = tk; // error +} From b0d7d5a7ef15c22c07a517213bf0b8aa2265e90c Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 17 Jan 2018 14:16:11 -0800 Subject: [PATCH 053/101] Fix #21089: Do not infer from numeric index signature in Object.values and Object.entries (#21129) * Fix https://github.com/Microsoft/TypeScript/issues/21089: Do not infer from numeric index signature in Object.values and Object.entries * Update test --- src/lib/es2017.object.d.ts | 4 +- .../reference/useObjectValuesAndEntries1.js | 54 +++++-- .../useObjectValuesAndEntries1.symbols | 102 +++++++++++-- .../useObjectValuesAndEntries1.types | 138 +++++++++++++++--- .../useObjectValuesAndEntries4.types | 8 +- .../es2017/useObjectValuesAndEntries1.ts | 28 +++- 6 files changed, 289 insertions(+), 45 deletions(-) diff --git a/src/lib/es2017.object.d.ts b/src/lib/es2017.object.d.ts index 775aaece7f6..1790ce76894 100644 --- a/src/lib/es2017.object.d.ts +++ b/src/lib/es2017.object.d.ts @@ -3,7 +3,7 @@ interface ObjectConstructor { * Returns an array of values of the enumerable properties of an object * @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. */ - values(o: { [s: string]: T } | { [n: number]: T }): T[]; + values(o: { [s: string]: T } | ArrayLike): T[]; /** * Returns an array of values of the enumerable properties of an object @@ -15,7 +15,7 @@ interface ObjectConstructor { * Returns an array of key/values of the enumerable properties of an object * @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. */ - entries(o: { [s: string]: T } | { [n: number]: T }): [string, T][]; + entries(o: { [s: string]: T } | ArrayLike): [string, T][]; /** * Returns an array of key/values of the enumerable properties of an object diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.js b/tests/baselines/reference/useObjectValuesAndEntries1.js index a10fb9a2d32..7b18fb1fcd9 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.js +++ b/tests/baselines/reference/useObjectValuesAndEntries1.js @@ -5,11 +5,30 @@ for (var x of Object.values(o)) { let y = x; } -var entries = Object.entries(o); -var entries1 = Object.entries(1); -var entries2 = Object.entries({a: true, b: 2}) -var entries3 = Object.entries({}) - +var entries = Object.entries(o); // [string, number][] +var values = Object.values(o); // number[] + +var entries1 = Object.entries(1); // [string, any][] +var values1 = Object.values(1); // any[] + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] + +var entries3 = Object.entries({}); // [string, {}][] +var values3 = Object.values({}); // {}[] + +var a = ["a", "b", "c"]; +var entries4 = Object.entries(a); // [string, string][] +var values4 = Object.values(a); // string[] + +enum E { A, B } +var entries5 = Object.entries(E); // [string, any][] +var values5 = Object.values(E); // any[] + +interface I { } +var i: I = {}; +var entries6 = Object.entries(i); // [string, any][] +var values6 = Object.values(i); // any[] //// [useObjectValuesAndEntries1.js] var o = { a: 1, b: 2 }; @@ -17,7 +36,24 @@ for (var _i = 0, _a = Object.values(o); _i < _a.length; _i++) { var x = _a[_i]; var y = x; } -var entries = Object.entries(o); -var entries1 = Object.entries(1); -var entries2 = Object.entries({ a: true, b: 2 }); -var entries3 = Object.entries({}); +var entries = Object.entries(o); // [string, number][] +var values = Object.values(o); // number[] +var entries1 = Object.entries(1); // [string, any][] +var values1 = Object.values(1); // any[] +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] +var entries3 = Object.entries({}); // [string, {}][] +var values3 = Object.values({}); // {}[] +var a = ["a", "b", "c"]; +var entries4 = Object.entries(a); // [string, string][] +var values4 = Object.values(a); // string[] +var E; +(function (E) { + E[E["A"] = 0] = "A"; + E[E["B"] = 1] = "B"; +})(E || (E = {})); +var entries5 = Object.entries(E); // [string, any][] +var values5 = Object.values(E); // any[] +var i = {}; +var entries6 = Object.entries(i); // [string, any][] +var values6 = Object.values(i); // any[] diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.symbols b/tests/baselines/reference/useObjectValuesAndEntries1.symbols index 538e3f69d78..ee9adbdac8f 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.symbols +++ b/tests/baselines/reference/useObjectValuesAndEntries1.symbols @@ -16,30 +16,114 @@ for (var x of Object.values(o)) { >x : Symbol(x, Decl(useObjectValuesAndEntries1.ts, 2, 8)) } -var entries = Object.entries(o); +var entries = Object.entries(o); // [string, number][] >entries : Symbol(entries, Decl(useObjectValuesAndEntries1.ts, 6, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >o : Symbol(o, Decl(useObjectValuesAndEntries1.ts, 0, 3)) -var entries1 = Object.entries(1); ->entries1 : Symbol(entries1, Decl(useObjectValuesAndEntries1.ts, 7, 3)) +var values = Object.values(o); // number[] +>values : Symbol(values, Decl(useObjectValuesAndEntries1.ts, 7, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>o : Symbol(o, Decl(useObjectValuesAndEntries1.ts, 0, 3)) + +var entries1 = Object.entries(1); // [string, any][] +>entries1 : Symbol(entries1, Decl(useObjectValuesAndEntries1.ts, 9, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) -var entries2 = Object.entries({a: true, b: 2}) ->entries2 : Symbol(entries2, Decl(useObjectValuesAndEntries1.ts, 8, 3)) +var values1 = Object.values(1); // any[] +>values1 : Symbol(values1, Decl(useObjectValuesAndEntries1.ts, 10, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +>entries2 : Symbol(entries2, Decl(useObjectValuesAndEntries1.ts, 12, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) ->a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 8, 31)) ->b : Symbol(b, Decl(useObjectValuesAndEntries1.ts, 8, 39)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 12, 31)) +>b : Symbol(b, Decl(useObjectValuesAndEntries1.ts, 12, 40)) -var entries3 = Object.entries({}) ->entries3 : Symbol(entries3, Decl(useObjectValuesAndEntries1.ts, 9, 3)) +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] +>values2 : Symbol(values2, Decl(useObjectValuesAndEntries1.ts, 13, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 13, 29)) +>b : Symbol(b, Decl(useObjectValuesAndEntries1.ts, 13, 38)) + +var entries3 = Object.entries({}); // [string, {}][] +>entries3 : Symbol(entries3, Decl(useObjectValuesAndEntries1.ts, 15, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +var values3 = Object.values({}); // {}[] +>values3 : Symbol(values3, Decl(useObjectValuesAndEntries1.ts, 16, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) + +var a = ["a", "b", "c"]; +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 18, 3)) + +var entries4 = Object.entries(a); // [string, string][] +>entries4 : Symbol(entries4, Decl(useObjectValuesAndEntries1.ts, 19, 3)) +>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 18, 3)) + +var values4 = Object.values(a); // string[] +>values4 : Symbol(values4, Decl(useObjectValuesAndEntries1.ts, 20, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 18, 3)) + +enum E { A, B } +>E : Symbol(E, Decl(useObjectValuesAndEntries1.ts, 20, 31)) +>A : Symbol(E.A, Decl(useObjectValuesAndEntries1.ts, 22, 8)) +>B : Symbol(E.B, Decl(useObjectValuesAndEntries1.ts, 22, 11)) + +var entries5 = Object.entries(E); // [string, any][] +>entries5 : Symbol(entries5, Decl(useObjectValuesAndEntries1.ts, 23, 3)) +>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>E : Symbol(E, Decl(useObjectValuesAndEntries1.ts, 20, 31)) + +var values5 = Object.values(E); // any[] +>values5 : Symbol(values5, Decl(useObjectValuesAndEntries1.ts, 24, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>E : Symbol(E, Decl(useObjectValuesAndEntries1.ts, 20, 31)) + +interface I { } +>I : Symbol(I, Decl(useObjectValuesAndEntries1.ts, 24, 31)) + +var i: I = {}; +>i : Symbol(i, Decl(useObjectValuesAndEntries1.ts, 27, 3)) +>I : Symbol(I, Decl(useObjectValuesAndEntries1.ts, 24, 31)) + +var entries6 = Object.entries(i); // [string, any][] +>entries6 : Symbol(entries6, Decl(useObjectValuesAndEntries1.ts, 28, 3)) +>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>i : Symbol(i, Decl(useObjectValuesAndEntries1.ts, 27, 3)) + +var values6 = Object.values(i); // any[] +>values6 : Symbol(values6, Decl(useObjectValuesAndEntries1.ts, 29, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>i : Symbol(i, Decl(useObjectValuesAndEntries1.ts, 27, 3)) + diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.types b/tests/baselines/reference/useObjectValuesAndEntries1.types index c52d0ce872e..60ad86bcb43 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.types +++ b/tests/baselines/reference/useObjectValuesAndEntries1.types @@ -10,9 +10,9 @@ var o = { a: 1, b: 2 }; for (var x of Object.values(o)) { >x : number >Object.values(o) : number[] ->Object.values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor ->values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >o : { a: number; b: number; } let y = x; @@ -20,39 +20,143 @@ for (var x of Object.values(o)) { >x : number } -var entries = Object.entries(o); +var entries = Object.entries(o); // [string, number][] >entries : [string, number][] >Object.entries(o) : [string, number][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >o : { a: number; b: number; } -var entries1 = Object.entries(1); +var values = Object.values(o); // number[] +>values : number[] +>Object.values(o) : number[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>o : { a: number; b: number; } + +var entries1 = Object.entries(1); // [string, any][] >entries1 : [string, any][] >Object.entries(1) : [string, any][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >1 : 1 -var entries2 = Object.entries({a: true, b: 2}) ->entries2 : [string, number | boolean][] ->Object.entries({a: true, b: 2}) : [string, number | boolean][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +var values1 = Object.values(1); // any[] +>values1 : any[] +>Object.values(1) : any[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } ->{a: true, b: 2} : { a: boolean; b: number; } +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>1 : 1 + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +>entries2 : [string, number | boolean][] +>Object.entries({ a: true, b: 2 }) : [string, number | boolean][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>{ a: true, b: 2 } : { a: boolean; b: number; } >a : boolean >true : true >b : number >2 : 2 -var entries3 = Object.entries({}) +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] +>values2 : (number | boolean)[] +>Object.values({ a: true, b: 2 }) : (number | boolean)[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>{ a: true, b: 2 } : { a: boolean; b: number; } +>a : boolean +>true : true +>b : number +>2 : 2 + +var entries3 = Object.entries({}); // [string, {}][] >entries3 : [string, {}][] >Object.entries({}) : [string, {}][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >{} : {} +var values3 = Object.values({}); // {}[] +>values3 : {}[] +>Object.values({}) : {}[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>{} : {} + +var a = ["a", "b", "c"]; +>a : string[] +>["a", "b", "c"] : string[] +>"a" : "a" +>"b" : "b" +>"c" : "c" + +var entries4 = Object.entries(a); // [string, string][] +>entries4 : [string, string][] +>Object.entries(a) : [string, string][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>a : string[] + +var values4 = Object.values(a); // string[] +>values4 : string[] +>Object.values(a) : string[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>a : string[] + +enum E { A, B } +>E : E +>A : E.A +>B : E.B + +var entries5 = Object.entries(E); // [string, any][] +>entries5 : [string, any][] +>Object.entries(E) : [string, any][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>E : typeof E + +var values5 = Object.values(E); // any[] +>values5 : any[] +>Object.values(E) : any[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>E : typeof E + +interface I { } +>I : I + +var i: I = {}; +>i : I +>I : I +>{} : {} + +var entries6 = Object.entries(i); // [string, any][] +>entries6 : [string, any][] +>Object.entries(i) : [string, any][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>i : I + +var values6 = Object.values(i); // any[] +>values6 : any[] +>Object.values(i) : any[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>i : I + diff --git a/tests/baselines/reference/useObjectValuesAndEntries4.types b/tests/baselines/reference/useObjectValuesAndEntries4.types index 33a427760a8..58aac10e52b 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries4.types +++ b/tests/baselines/reference/useObjectValuesAndEntries4.types @@ -10,9 +10,9 @@ var o = { a: 1, b: 2 }; for (var x of Object.values(o)) { >x : number >Object.values(o) : number[] ->Object.values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor ->values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >o : { a: number; b: number; } let y = x; @@ -23,8 +23,8 @@ for (var x of Object.values(o)) { var entries = Object.entries(o); >entries : [string, number][] >Object.entries(o) : [string, number][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >o : { a: number; b: number; } diff --git a/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts b/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts index 719f10bbbcc..7422826be16 100644 --- a/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts +++ b/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts @@ -7,7 +7,27 @@ for (var x of Object.values(o)) { let y = x; } -var entries = Object.entries(o); -var entries1 = Object.entries(1); -var entries2 = Object.entries({a: true, b: 2}) -var entries3 = Object.entries({}) +var entries = Object.entries(o); // [string, number][] +var values = Object.values(o); // number[] + +var entries1 = Object.entries(1); // [string, any][] +var values1 = Object.values(1); // any[] + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] + +var entries3 = Object.entries({}); // [string, {}][] +var values3 = Object.values({}); // {}[] + +var a = ["a", "b", "c"]; +var entries4 = Object.entries(a); // [string, string][] +var values4 = Object.values(a); // string[] + +enum E { A, B } +var entries5 = Object.entries(E); // [string, any][] +var values5 = Object.values(E); // any[] + +interface I { } +var i: I = {}; +var entries6 = Object.entries(i); // [string, any][] +var values6 = Object.values(i); // any[] \ No newline at end of file From ec3765130812677e1be7b3d9909ee312e82ac663 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 14:22:58 -0800 Subject: [PATCH 054/101] Use packageId for suggestion to install `@types/packageName` (#21241) --- src/compiler/checker.ts | 4 ++-- .../untypedModuleImport_noImplicitAny.errors.txt | 5 ++++- .../untypedModuleImport_noImplicitAny.js | 5 ++++- ...eImport_noImplicitAny_relativePath.errors.txt | 16 ++++++++++++++++ ...pedModuleImport_noImplicitAny_relativePath.js | 15 +++++++++++++++ ...duleImport_noImplicitAny_relativePath.symbols | 4 ++++ ...ModuleImport_noImplicitAny_relativePath.types | 4 ++++ .../untypedModuleImport_noImplicitAny.ts | 4 +++- ...pedModuleImport_noImplicitAny_relativePath.ts | 11 +++++++++++ 9 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types create mode 100644 tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 39679675a31..0e4525c9f2d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2063,9 +2063,9 @@ namespace ts { error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName); } else if (noImplicitAny && moduleNotFoundError) { - let errorInfo = !resolvedModule.isExternalLibraryImport ? undefined : chainDiagnosticMessages(/*details*/ undefined, + let errorInfo = resolvedModule.packageId && chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, - moduleReference); + resolvedModule.packageId.name); errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, moduleReference, diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt b/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt index 0f451941bd7..5137ea4ae5f 100644 --- a/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt @@ -8,8 +8,11 @@ !!! error TS7016: Could not find a declaration file for module 'foo'. '/node_modules/foo/index.js' implicitly has an 'any' type. !!! error TS7016: Try `npm install @types/foo` if it exists or add a new declaration (.d.ts) file containing `declare module 'foo';` -==== /node_modules/foo/index.js (0 errors) ==== +==== /node_modules/foo/package.json (0 errors) ==== // This tests that `--noImplicitAny` disables untyped modules. + { "name": "foo", "version": "1.2.3" } + +==== /node_modules/foo/index.js (0 errors) ==== This file is not processed. \ No newline at end of file diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny.js b/tests/baselines/reference/untypedModuleImport_noImplicitAny.js index 86e2ae3c175..378ebee6d7a 100644 --- a/tests/baselines/reference/untypedModuleImport_noImplicitAny.js +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny.js @@ -1,8 +1,11 @@ //// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts] //// -//// [index.js] +//// [package.json] // This tests that `--noImplicitAny` disables untyped modules. +{ "name": "foo", "version": "1.2.3" } + +//// [index.js] This file is not processed. //// [a.ts] diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt new file mode 100644 index 00000000000..1aeb8489b8d --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt @@ -0,0 +1,16 @@ +/a.ts(1,22): error TS7016: Could not find a declaration file for module './node_modules/foo'. '/node_modules/foo/index.js' implicitly has an 'any' type. + Try `npm install @types/foo` if it exists or add a new declaration (.d.ts) file containing `declare module 'foo';` + + +==== /a.ts (1 errors) ==== + import * as foo from "./node_modules/foo"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS7016: Could not find a declaration file for module './node_modules/foo'. '/node_modules/foo/index.js' implicitly has an 'any' type. +!!! error TS7016: Try `npm install @types/foo` if it exists or add a new declaration (.d.ts) file containing `declare module 'foo';` + +==== /node_modules/foo/package.json (0 errors) ==== + { "name": "foo", "version": "1.2.3" } + +==== /node_modules/foo/index.js (0 errors) ==== + This file is not processed. + \ No newline at end of file diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js new file mode 100644 index 00000000000..7e8c52a3bfb --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js @@ -0,0 +1,15 @@ +//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts] //// + +//// [package.json] +{ "name": "foo", "version": "1.2.3" } + +//// [index.js] +This file is not processed. + +//// [a.ts] +import * as foo from "./node_modules/foo"; + + +//// [a.js] +"use strict"; +exports.__esModule = true; diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols new file mode 100644 index 00000000000..0b9aff6bbef --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols @@ -0,0 +1,4 @@ +=== /a.ts === +import * as foo from "./node_modules/foo"; +>foo : Symbol(foo, Decl(a.ts, 0, 6)) + diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types new file mode 100644 index 00000000000..d9b7333550d --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types @@ -0,0 +1,4 @@ +=== /a.ts === +import * as foo from "./node_modules/foo"; +>foo : any + diff --git a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts index 1aee7c069de..39208624a72 100644 --- a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts +++ b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts @@ -1,8 +1,10 @@ // @noImplicitReferences: true -// @currentDirectory: / // @noImplicitAny: true // This tests that `--noImplicitAny` disables untyped modules. +// @filename: /node_modules/foo/package.json +{ "name": "foo", "version": "1.2.3" } + // @filename: /node_modules/foo/index.js This file is not processed. diff --git a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts new file mode 100644 index 00000000000..ae38be5dfa0 --- /dev/null +++ b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts @@ -0,0 +1,11 @@ +// @noImplicitReferences: true +// @noImplicitAny: true + +// @filename: /node_modules/foo/package.json +{ "name": "foo", "version": "1.2.3" } + +// @filename: /node_modules/foo/index.js +This file is not processed. + +// @filename: /a.ts +import * as foo from "./node_modules/foo"; From 1dcc83e6d2dbc1c2f97ce5b0ce96a7c160528b3d Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 14:29:19 -0800 Subject: [PATCH 055/101] Minor cleanup in getDynamicIndentation (#21240) --- src/services/formatting/formatting.ts | 94 ++++++++++++--------------- 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index c020e638692..81bcd2c7137 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -537,70 +537,56 @@ namespace ts.formatting { case SyntaxKind.CloseBraceToken: case SyntaxKind.CloseBracketToken: case SyntaxKind.CloseParenToken: - return indentation + getEffectiveDelta(delta, container); + return indentation + getDelta(container); } return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation; }, - getIndentationForToken: (line, kind, container) => { - if (nodeStartLine !== line && node.decorators) { - if (kind === getFirstNonDecoratorTokenOfNode(node)) { - // if this token is the first token following the list of decorators, we do not need to indent - return indentation; - } - } - switch (kind) { - // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent - case SyntaxKind.OpenBraceToken: - case SyntaxKind.CloseBraceToken: - case SyntaxKind.OpenParenToken: - case SyntaxKind.CloseParenToken: - case SyntaxKind.ElseKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.AtToken: - return indentation; - case SyntaxKind.SlashToken: - case SyntaxKind.GreaterThanToken: { - if (container.kind === SyntaxKind.JsxOpeningElement || - container.kind === SyntaxKind.JsxClosingElement || - container.kind === SyntaxKind.JsxSelfClosingElement - ) { - return indentation; - } - break; - } - case SyntaxKind.OpenBracketToken: - case SyntaxKind.CloseBracketToken: { - if (container.kind !== SyntaxKind.MappedType) { - return indentation; - } - break; - } - } - // if token line equals to the line of containing node (this is a first token in the node) - use node indentation - return nodeStartLine !== line ? indentation + getEffectiveDelta(delta, container) : indentation; - }, + getIndentationForToken: (line, kind, container) => + shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation, getIndentation: () => indentation, - getDelta: child => getEffectiveDelta(delta, child), + getDelta, recomputeIndentation: lineAdded => { if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) { - if (lineAdded) { - indentation += options.indentSize; - } - else { - indentation -= options.indentSize; - } - - if (SmartIndenter.shouldIndentChildNode(node)) { - delta = options.indentSize; - } - else { - delta = 0; - } + indentation += lineAdded ? options.indentSize : -options.indentSize; + delta = SmartIndenter.shouldIndentChildNode(node) ? options.indentSize : 0; } } }; - function getEffectiveDelta(delta: number, child: TextRangeWithKind) { + function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean { + switch (kind) { + // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent + case SyntaxKind.OpenBraceToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.OpenParenToken: + case SyntaxKind.CloseParenToken: + case SyntaxKind.ElseKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.AtToken: + return false; + case SyntaxKind.SlashToken: + case SyntaxKind.GreaterThanToken: + switch (container.kind) { + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxSelfClosingElement: + return false; + } + break; + case SyntaxKind.OpenBracketToken: + case SyntaxKind.CloseBracketToken: + if (container.kind !== SyntaxKind.MappedType) { + return false; + } + break; + } + // if token line equals to the line of containing node (this is a first token in the node) - use node indentation + return nodeStartLine !== line + // if this token is the first token following the list of decorators, we do not need to indent + && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node)); + } + + function getDelta(child: TextRangeWithKind) { // Delta value should be zero when the node explicitly prevents indentation of the child node return SmartIndenter.nodeWillIndentChild(node, child, /*indentByDefault*/ true) ? delta : 0; } From 8281c7a137348f9e7392d35327283234ff4791a8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 17 Jan 2018 14:51:31 -0800 Subject: [PATCH 056/101] Fix the invalid file/directory location when getting file system entry for caching read directory results Fixes #20607 --- src/compiler/core.ts | 7 ++++- src/compiler/sys.ts | 2 +- src/compiler/utilities.ts | 1 - .../unittests/tsserverProjectSystem.ts | 26 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d92a02661e8..5fa9a544f8f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -20,6 +20,7 @@ namespace ts { /* @internal */ namespace ts { + export const emptyArray: never[] = [] as never[]; /** Create a MapLike with good performance. */ function createDictionaryObject(): MapLike { const map = Object.create(/*prototype*/ null); // tslint:disable-line:no-null-keyword @@ -3069,6 +3070,10 @@ namespace ts { readonly directories: string[]; } + export const emptyFileSystemEntries: FileSystemEntries = { + files: emptyArray, + directories: emptyArray + }; export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost { const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); @@ -3210,7 +3215,7 @@ namespace ts { if (path === rootDirPath) { return result; } - return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); + return tryReadDirectory(dir, path) || emptyFileSystemEntries; } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 841dba8a528..5293657a646 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -398,7 +398,7 @@ namespace ts { return { files, directories }; } catch (e) { - return { files: [], directories: [] }; + return emptyFileSystemEntries; } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ffcdf42c67e..75083e159c5 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2,7 +2,6 @@ /* @internal */ namespace ts { - export const emptyArray: never[] = [] as never[]; export const resolvingEmptyArray: never[] = [] as never[]; export const emptyMap: ReadonlyMap = createMap(); export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap = emptyMap as ReadonlyUnderscoreEscapedMap; diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 68fbc1ecba0..2ac0945587a 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4168,6 +4168,32 @@ namespace ts.projectSystem { // Since no file from the configured project is open, it would be closed immediately projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 }); }); + + it("should tolerate invalid include files that start in subDirectory", () => { + const projectFolder = "/user/username/projects/myproject"; + const f = { + path: `${projectFolder}/src/server/index.ts`, + content: "let x = 1" + }; + const config = { + path: `${projectFolder}/src/server/tsconfig.json`, + content: JSON.stringify({ + compiler: { + module: "commonjs", + outDir: "../../build" + }, + include: [ + "../src/**/*.ts" + ] + }) + }; + const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + + projectService.openClientFile(f.path); + // Since no file from the configured project is open, it would be closed immediately + projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 }); + }); }); describe("tsserverProjectSystem reload", () => { From e354754b2a30ab9ff558a029fb869a30668dd0ae Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 18:28:47 -0800 Subject: [PATCH 057/101] Special case arrow functions with only parameter unused Fixes GH #18274 --- src/services/codefixes/fixUnusedIdentifier.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index b7d948b3deb..bcfcd6d1e6d 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -121,9 +121,20 @@ namespace ts.codefix { break; case SyntaxKind.Parameter: - const functionDeclaration = parent.parent; - if (functionDeclaration.parameters.length === 1) { - changes.deleteNode(sourceFile, parent); + const oldFunction = parent.parent; + if (isArrowFunction(oldFunction) && oldFunction.parameters.length === 1) { + const newFunction = updateArrowFunction( + oldFunction, + oldFunction.modifiers, + oldFunction.typeParameters, + /*parameters*/ undefined, + oldFunction.type, + oldFunction.equalsGreaterThanToken, + oldFunction.body); + + suppressLeadingAndTrailingTrivia(newFunction); + + changes.replaceRange(sourceFile, { pos: oldFunction.getStart(), end: oldFunction.end }, newFunction); } else { changes.deleteNodeInList(sourceFile, parent); From 3b5689fa1fdf036f90c6e836681f312356b66aa5 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 11 Jan 2018 10:51:55 -0800 Subject: [PATCH 058/101] Add more test coverage for unusedParameterInLambda --- tests/cases/fourslash/unusedParameterInLambda1.ts | 5 +++-- tests/cases/fourslash/unusedParameterInLambda2.ts | 14 ++++++++++++++ tests/cases/fourslash/unusedParameterInLambda3.ts | 14 ++++++++++++++ tests/cases/fourslash/unusedParameterInLambda4.ts | 13 +++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/unusedParameterInLambda2.ts create mode 100644 tests/cases/fourslash/unusedParameterInLambda3.ts create mode 100644 tests/cases/fourslash/unusedParameterInLambda4.ts diff --git a/tests/cases/fourslash/unusedParameterInLambda1.ts b/tests/cases/fourslash/unusedParameterInLambda1.ts index ace79adae46..88a0e8c9272 100644 --- a/tests/cases/fourslash/unusedParameterInLambda1.ts +++ b/tests/cases/fourslash/unusedParameterInLambda1.ts @@ -3,11 +3,12 @@ // @noUnusedLocals: true // @noUnusedParameters: true //// function f1() { -//// [|return (x:number) => {}|] +//// [|return /*~a*/(/*~b*/x/*~c*/:/*~d*/number/*~e*/)/*~f*/ => /*~g*/{/*~h*/}/*~i*/|] //// } +// In a perfect world, /*~f*/ and /*~h*/ would probably be retained. verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return () => {}", + newRangeContent: "return /*~a*/() => /*~g*/ { }/*~i*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda2.ts b/tests/cases/fourslash/unusedParameterInLambda2.ts new file mode 100644 index 00000000000..cc46288fe3c --- /dev/null +++ b/tests/cases/fourslash/unusedParameterInLambda2.ts @@ -0,0 +1,14 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true +//// function f1() { +//// [|return /*~a*/x/*~b*/ /*~c*/=>/*~d*/ {/*~e*/}/*~f*/|] +//// } + +// In a perfect world, /*~c*/ and /*~e*/ would probably be retained. +verify.codeFix({ + description: "Remove declaration for: 'x'", + index: 0, + newRangeContent: "return /*~a*/() => /*~d*/ { }/*~f*/", +}); diff --git a/tests/cases/fourslash/unusedParameterInLambda3.ts b/tests/cases/fourslash/unusedParameterInLambda3.ts new file mode 100644 index 00000000000..e5f1930eb9d --- /dev/null +++ b/tests/cases/fourslash/unusedParameterInLambda3.ts @@ -0,0 +1,14 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true +//// function f1() { +//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/x/*~h*/|] +//// } + +// In a perfect world, /*~c*/ would probably be retained, rather than /*~e*/. +verify.codeFix({ + description: "Remove declaration for: 'y'", + index: 0, + newRangeContent: "return /*~a*/(/*~b*/x/*~e*/)/*~f*/ => /*~g*/x/*~h*/", +}); diff --git a/tests/cases/fourslash/unusedParameterInLambda4.ts b/tests/cases/fourslash/unusedParameterInLambda4.ts new file mode 100644 index 00000000000..c2273ebaad0 --- /dev/null +++ b/tests/cases/fourslash/unusedParameterInLambda4.ts @@ -0,0 +1,13 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true +//// function f1() { +//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/|] +//// } + +verify.codeFix({ + description: "Remove declaration for: 'x'", + index: 0, + newRangeContent: "return /*~a*/(/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/", +}); From 5de6ac1a2f81362578ad338d0d88e3d040d3f7bc Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 12 Jan 2018 11:11:01 -0800 Subject: [PATCH 059/101] Simplify test cases --- src/services/codefixes/fixUnusedIdentifier.ts | 3 +++ tests/cases/fourslash/unusedParameterInLambda1.ts | 6 ++---- tests/cases/fourslash/unusedParameterInLambda2.ts | 6 ++---- tests/cases/fourslash/unusedParameterInLambda3.ts | 6 ++---- tests/cases/fourslash/unusedParameterInLambda4.ts | 6 ++---- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index bcfcd6d1e6d..69778b543a0 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -123,6 +123,9 @@ namespace ts.codefix { case SyntaxKind.Parameter: const oldFunction = parent.parent; if (isArrowFunction(oldFunction) && oldFunction.parameters.length === 1) { + // Lambdas with exactly one parameter are special because, after removal, there + // must be an empty parameter list (i.e. `()`) and this won't necessarily be the + // case if the parameter is simply removed (e.g. in `x => 1`). const newFunction = updateArrowFunction( oldFunction, oldFunction.modifiers, diff --git a/tests/cases/fourslash/unusedParameterInLambda1.ts b/tests/cases/fourslash/unusedParameterInLambda1.ts index 88a0e8c9272..fdb53a8a05f 100644 --- a/tests/cases/fourslash/unusedParameterInLambda1.ts +++ b/tests/cases/fourslash/unusedParameterInLambda1.ts @@ -2,13 +2,11 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/(/*~b*/x/*~c*/:/*~d*/number/*~e*/)/*~f*/ => /*~g*/{/*~h*/}/*~i*/|] -//// } +////[|/*~a*/(/*~b*/x/*~c*/:/*~d*/number/*~e*/)/*~f*/ => /*~g*/{/*~h*/}/*~i*/|] // In a perfect world, /*~f*/ and /*~h*/ would probably be retained. verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return /*~a*/() => /*~g*/ { }/*~i*/", + newRangeContent: "/*~a*/() => /*~g*/ { }/*~i*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda2.ts b/tests/cases/fourslash/unusedParameterInLambda2.ts index cc46288fe3c..e2b1be346b8 100644 --- a/tests/cases/fourslash/unusedParameterInLambda2.ts +++ b/tests/cases/fourslash/unusedParameterInLambda2.ts @@ -2,13 +2,11 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/x/*~b*/ /*~c*/=>/*~d*/ {/*~e*/}/*~f*/|] -//// } +////[|/*~a*/x/*~b*/ /*~c*/=>/*~d*/ {/*~e*/}/*~f*/|] // In a perfect world, /*~c*/ and /*~e*/ would probably be retained. verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return /*~a*/() => /*~d*/ { }/*~f*/", + newRangeContent: "/*~a*/() => /*~d*/ { }/*~f*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda3.ts b/tests/cases/fourslash/unusedParameterInLambda3.ts index e5f1930eb9d..f95d142d436 100644 --- a/tests/cases/fourslash/unusedParameterInLambda3.ts +++ b/tests/cases/fourslash/unusedParameterInLambda3.ts @@ -2,13 +2,11 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/x/*~h*/|] -//// } +////[|/*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/x/*~h*/|] // In a perfect world, /*~c*/ would probably be retained, rather than /*~e*/. verify.codeFix({ description: "Remove declaration for: 'y'", index: 0, - newRangeContent: "return /*~a*/(/*~b*/x/*~e*/)/*~f*/ => /*~g*/x/*~h*/", + newRangeContent: "/*~a*/(/*~b*/x/*~e*/)/*~f*/ => /*~g*/x/*~h*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda4.ts b/tests/cases/fourslash/unusedParameterInLambda4.ts index c2273ebaad0..015f081891a 100644 --- a/tests/cases/fourslash/unusedParameterInLambda4.ts +++ b/tests/cases/fourslash/unusedParameterInLambda4.ts @@ -2,12 +2,10 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/|] -//// } +////[|/*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/|] verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return /*~a*/(/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/", + newRangeContent: "/*~a*/(/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/", }); From 9a83077d78cadc781284f001c45e0fcf808d6a39 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 17 Jan 2018 15:12:39 -0800 Subject: [PATCH 060/101] Add explanatory comment --- src/services/codefixes/fixUnusedIdentifier.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 69778b543a0..59d19a1bd9b 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -135,6 +135,9 @@ namespace ts.codefix { oldFunction.equalsGreaterThanToken, oldFunction.body); + // Drop leading and trailing trivia of the new function because we're only going + // to replace the span (vs the full span) of the old function - the old leading + // and trailing trivia will remain. suppressLeadingAndTrailingTrivia(newFunction); changes.replaceRange(sourceFile, { pos: oldFunction.getStart(), end: oldFunction.end }, newFunction); From b4a382bdd2a9cb25555e1f852b9e759372bc996f Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 11 Jan 2018 14:53:31 -0800 Subject: [PATCH 061/101] Stop explicitly storing newline in refactoring/code fix contexts It's already in the EditorSettings and the LanguageServiceHost. Fixes #18291 Fixes #18445 --- src/harness/unittests/extractTestHelpers.ts | 2 -- src/services/codeFixProvider.ts | 5 ++-- .../addMissingInvocationForDecorator.ts | 2 +- ...correctQualifiedNameToIndexedAccessType.ts | 2 +- .../codefixes/disableJsDiagnostics.ts | 6 +++-- src/services/codefixes/fixAddMissingMember.ts | 14 +++++----- .../codefixes/fixAwaitInSyncFunction.ts | 2 +- ...sDoesntImplementInheritedAbstractMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 2 +- .../fixClassSuperMustPrecedeThisAccess.ts | 2 +- .../fixConstructorForDerivedNeedSuperCall.ts | 2 +- .../fixExtendsInterfaceBecomesImplements.ts | 2 +- .../fixForgottenThisPropertyAccess.ts | 2 +- .../codefixes/fixInvalidImportSyntax.ts | 6 ++--- src/services/codefixes/fixSpelling.ts | 2 +- src/services/codefixes/fixUnusedIdentifier.ts | 4 +-- src/services/codefixes/importFixes.ts | 27 +++++++++---------- src/services/completions.ts | 1 - src/services/refactorProvider.ts | 20 ++++++++++++-- .../refactors/annotateWithTypeFromJSDoc.ts | 4 +-- .../refactors/convertFunctionToEs6Class.ts | 2 +- src/services/refactors/convertToEs6Module.ts | 2 +- src/services/refactors/extractSymbol.ts | 4 +-- src/services/refactors/useDefaultImport.ts | 2 +- src/services/services.ts | 7 ++--- 25 files changed, 67 insertions(+), 59 deletions(-) diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts index a04f443c3c9..f519555bd26 100644 --- a/src/harness/unittests/extractTestHelpers.ts +++ b/src/harness/unittests/extractTestHelpers.ts @@ -121,7 +121,6 @@ namespace ts { const sourceFile = program.getSourceFile(path); const context: RefactorContext = { cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - newLineCharacter, program, file: sourceFile, startPosition: selectionRange.start, @@ -185,7 +184,6 @@ namespace ts { const sourceFile = program.getSourceFile(f.path); const context: RefactorContext = { cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - newLineCharacter, program, file: sourceFile, startPosition: selectionRange.start, diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index aa1f2fb6885..619bcc8813c 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -7,10 +7,9 @@ namespace ts { getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; } - export interface CodeFixContextBase extends textChanges.TextChangesContext { + export interface CodeFixContextBase extends RefactorOrCodeFixContext { sourceFile: SourceFile; program: Program; - host: LanguageServiceHost; cancellationToken: CancellationToken; } @@ -84,7 +83,7 @@ namespace ts { export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push) => void): CombinedCodeActions { const commands: CodeActionCommand[] = []; - const changes = textChanges.ChangeTracker.with(context, t => + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); } diff --git a/src/services/codefixes/addMissingInvocationForDecorator.ts b/src/services/codefixes/addMissingInvocationForDecorator.ts index d063df87be4..25214f08218 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -5,7 +5,7 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions: (context) => { - const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => makeChange(t, context.sourceFile, context.span.start)); return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts index de4498035be..076ea6135ae 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -7,7 +7,7 @@ namespace ts.codefix { getCodeActions(context) { const qualifiedName = getQualifiedName(context.sourceFile, context.span.start); if (!qualifiedName) return undefined; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, context.sourceFile, qualifiedName)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]); return [{ description, changes, fixId }]; }, diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 6a59e61d52d..cf006a6066c 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -9,12 +9,14 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions(context) { - const { sourceFile, program, newLineCharacter, span } = context; + const { sourceFile, program, span } = context; if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) { return undefined; } + const newLineCharacter = getNewLineFromContext(context); + return [{ description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], @@ -36,7 +38,7 @@ namespace ts.codefix { fixIds: [fixId], // No point applying as a group, doing it once will fix all errors getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { if (err.start !== undefined) { - changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter)); + changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, getNewLineFromContext(context))); } }), }); diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 0fc6a430e19..8e45efa09ad 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -96,7 +96,7 @@ namespace ts.codefix { } function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined { - const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]); return { description, changes, fixId }; @@ -142,9 +142,9 @@ namespace ts.codefix { return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); } - function createAddPropertyDeclarationAction(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction { + function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]); - const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic)); return { description, changes, fixId }; } @@ -159,7 +159,7 @@ namespace ts.codefix { changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property); } - function createAddIndexSignatureAction(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction { + function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction { // Index signatures cannot have the static modifier. const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); const indexingParameter = createParameter( @@ -176,14 +176,14 @@ namespace ts.codefix { [indexingParameter], typeNode); - const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature)); // No fixId here because code-fix-all currently only works on adding individual named properties. return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined }; } - function getActionForMethodDeclaration(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined { + function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]); - const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs)); return { description, changes, fixId }; } diff --git a/src/services/codefixes/fixAwaitInSyncFunction.ts b/src/services/codefixes/fixAwaitInSyncFunction.ts index 883993e7b51..7d09e5f214f 100644 --- a/src/services/codefixes/fixAwaitInSyncFunction.ts +++ b/src/services/codefixes/fixAwaitInSyncFunction.ts @@ -11,7 +11,7 @@ namespace ts.codefix { const { sourceFile, span } = context; const nodes = getNodes(sourceFile, span.start); if (!nodes) return undefined; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, nodes)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, nodes)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_async_modifier_to_containing_function), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index da3685339ab..6e215666ddf 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -9,7 +9,7 @@ namespace ts.codefix { errorCodes, getCodeActions(context) { const { program, sourceFile, span } = context; - const changes = textChanges.ChangeTracker.with(context, t => + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t)); return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }]; }, diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 0236b0c79b7..1eb5bfac986 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -10,7 +10,7 @@ namespace ts.codefix { const classDeclaration = getClass(sourceFile, span.start); const checker = program.getTypeChecker(); return mapDefined(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => { - const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); return { description, changes, fixId }; diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 1595bbf3c13..76c1c588097 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -9,7 +9,7 @@ namespace ts.codefix { const nodes = getNodes(sourceFile, span.start); if (!nodes) return undefined; const { constructor, superCall } = nodes; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, constructor, superCall)); return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 8fe2e8458a8..0b747344e3b 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -7,7 +7,7 @@ namespace ts.codefix { getCodeActions(context) { const { sourceFile, span } = context; const ctr = getNode(sourceFile, span.start); - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, ctr)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index d98ca556f47..a4e91410db5 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -9,7 +9,7 @@ namespace ts.codefix { const nodes = getNodes(sourceFile, context.span.start); if (!nodes) return undefined; const { extendsToken, heritageClauses } = nodes; - const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChanges(t, sourceFile, extendsToken, heritageClauses)); return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 19610da0b15..8c337c16c4f 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -7,7 +7,7 @@ namespace ts.codefix { getCodeActions(context) { const { sourceFile } = context; const token = getNode(sourceFile, context.span.start); - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, token)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixInvalidImportSyntax.ts b/src/services/codefixes/fixInvalidImportSyntax.ts index f98f1eaea7c..0bfc6a6090d 100644 --- a/src/services/codefixes/fixInvalidImportSyntax.ts +++ b/src/services/codefixes/fixInvalidImportSyntax.ts @@ -32,7 +32,7 @@ namespace ts.codefix { createImportClause(namespace.name, /*namedBindings*/ undefined), node.moduleSpecifier ); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true }); const changes = changeTracker.getChanges(); variations.push({ @@ -48,7 +48,7 @@ namespace ts.codefix { namespace.name, createExternalModuleReference(node.moduleSpecifier) ); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true }); const changes = changeTracker.getChanges(); variations.push({ @@ -86,7 +86,7 @@ namespace ts.codefix { addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport)); } const propertyAccess = createPropertyAccess(expr, "default"); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); changeTracker.replaceNode(sourceFile, expr, propertyAccess, {}); const changes = changeTracker.getChanges(); fixes.push({ diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 729f14e9ef9..95159012f48 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -12,7 +12,7 @@ namespace ts.codefix { const info = getInfo(sourceFile, context.span.start, context.program.getTypeChecker()); if (!info) return undefined; const { node, suggestion } = info; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, node, suggestion)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]); return [{ description, changes, fixId }]; }, diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index b7d948b3deb..93c8ae80ac9 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -13,13 +13,13 @@ namespace ts.codefix { const token = getToken(sourceFile, context.span.start); const result: CodeFixAction[] = []; - const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token)); + const deletion = textChanges.ChangeTracker.with(toTextChangesContext(context), t => tryDeleteDeclaration(t, sourceFile, token)); if (deletion.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]); result.push({ description, changes: deletion, fixId: fixIdDelete }); } - const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token)); + const prefix = textChanges.ChangeTracker.with(toTextChangesContext(context), t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token)); if (prefix.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]); result.push({ description, changes: prefix, fixId: fixIdPrefix }); diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 8f69cd95c11..204030e3104 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -24,16 +24,14 @@ namespace ts.codefix { moduleSpecifier?: string; } - interface SymbolContext extends textChanges.TextChangesContext { + interface SymbolContext { sourceFile: SourceFile; symbolName: string; + formatContext: ts.formatting.FormatContext; } - interface SymbolAndTokenContext extends SymbolContext { + interface ImportCodeFixContext extends SymbolContext { symbolToken: Identifier | undefined; - } - - interface ImportCodeFixContext extends SymbolAndTokenContext { host: LanguageServiceHost; program: Program; checker: TypeChecker; @@ -173,7 +171,6 @@ namespace ts.codefix { const symbolToken = cast(getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false), isIdentifier); return { host: context.host, - newLineCharacter: context.newLineCharacter, formatContext: context.formatContext, sourceFile: context.sourceFile, program, @@ -260,7 +257,7 @@ namespace ts.codefix { } } - function getCodeActionForNewImport(context: SymbolContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction { + function getCodeActionForNewImport(context: SymbolContext & RefactorOrCodeFixContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction { const { kind, sourceFile, symbolName } = context; const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax); @@ -278,7 +275,7 @@ namespace ts.codefix { createIdentifier(symbolName), createExternalModuleReference(quotedModuleSpecifier)); - const changes = ChangeTracker.with(context, changeTracker => { + const changes = ChangeTracker.with(toTextChangesContext(context), changeTracker => { if (lastImportDeclaration) { changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl); } @@ -672,33 +669,33 @@ namespace ts.codefix { return expression && isStringLiteral(expression) ? expression.text : undefined; } - function tryUpdateExistingImport(context: SymbolContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined { + function tryUpdateExistingImport(context: SymbolContext & RefactorOrCodeFixContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined { const { symbolName, sourceFile, kind } = context; const { name } = importClause; const { namedBindings } = importClause.kind !== SyntaxKind.ImportEqualsDeclaration && importClause; switch (kind) { case ImportKind.Default: - return name ? undefined : ChangeTracker.with(context, t => + return name ? undefined : ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(sourceFile, importClause, createImportClause(createIdentifier(symbolName), namedBindings))); case ImportKind.Named: { const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(symbolName)); if (namedBindings && namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length !== 0) { // There are already named imports; add another. - return ChangeTracker.with(context, t => t.insertNodeInListAfter( + return ChangeTracker.with(toTextChangesContext(context), t => t.insertNodeInListAfter( sourceFile, namedBindings.elements[namedBindings.elements.length - 1], newImportSpecifier)); } if (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length === 0) { - return ChangeTracker.with(context, t => + return ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(sourceFile, importClause, createImportClause(name, createNamedImports([newImportSpecifier])))); } return undefined; } case ImportKind.Namespace: - return namedBindings ? undefined : ChangeTracker.with(context, t => + return namedBindings ? undefined : ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(sourceFile, importClause, createImportClause(name, createNamespaceImport(createIdentifier(symbolName))))); case ImportKind.Equals: @@ -709,7 +706,7 @@ namespace ts.codefix { } } - function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext, symbolToken: Identifier): ImportCodeAction { + function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext & RefactorOrCodeFixContext, symbolToken: Identifier): ImportCodeAction { const { symbolName, sourceFile } = context; /** @@ -723,7 +720,7 @@ namespace ts.codefix { * become "ns.foo" */ // Prefix the node instead of it replacing it, because this may be used for import completions and we don't want the text changes to overlap with the identifier being completed. - const changes = ChangeTracker.with(context, tracker => + const changes = ChangeTracker.with(toTextChangesContext(context), tracker => tracker.changeIdentifierToPropertyAccess(sourceFile, namespacePrefix, symbolToken)); return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes, "CodeChange", /*moduleSpecifier*/ undefined); } diff --git a/src/services/completions.ts b/src/services/completions.ts index 36a10a1b578..82e7065228a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -627,7 +627,6 @@ namespace ts.Completions { host, program, checker, - newLineCharacter: host.getNewLine(), compilerOptions, sourceFile, formatContext, diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 85ef9113bda..3d2a60e63e9 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -14,12 +14,28 @@ namespace ts { getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined; } - export interface RefactorContext extends textChanges.TextChangesContext { + export interface RefactorOrCodeFixContext { + host: LanguageServiceHost; + formatContext: ts.formatting.FormatContext; + } + + export function getNewLineFromContext(context: RefactorOrCodeFixContext) { + const formatSettings = context.formatContext.options; + return formatSettings ? formatSettings.newLineCharacter : context.host.getNewLine(); + } + + export function toTextChangesContext(context: RefactorOrCodeFixContext): textChanges.TextChangesContext { + return { + newLineCharacter: getNewLineFromContext(context), + formatContext: context.formatContext, + }; + } + + export interface RefactorContext extends RefactorOrCodeFixContext { file: SourceFile; startPosition: number; endPosition?: number; program: Program; - host: LanguageServiceHost; cancellationToken?: CancellationToken; } diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index d3bf59638b2..a39cdc20c42 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -78,7 +78,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { return Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); } - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); const declarationWithType = addType(decl, transformJSDocType(jsdocType) as TypeNode); suppressLeadingAndTrailingTrivia(declarationWithType); changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, declarationWithType); @@ -93,7 +93,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const sourceFile = context.file; const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(token, isFunctionLikeDeclaration); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); const functionWithType = addTypesToFunctionLike(decl); suppressLeadingAndTrailingTrivia(functionWithType); changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, functionWithType); diff --git a/src/services/refactors/convertFunctionToEs6Class.ts b/src/services/refactors/convertFunctionToEs6Class.ts index cddf40ae017..93e39bc683c 100644 --- a/src/services/refactors/convertFunctionToEs6Class.ts +++ b/src/services/refactors/convertFunctionToEs6Class.ts @@ -59,7 +59,7 @@ namespace ts.refactor.convertFunctionToES6Class { } const ctorDeclaration = ctorSymbol.valueDeclaration; - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); let precedingNode: Node; let newClassDeclaration: ClassDeclaration; diff --git a/src/services/refactors/convertToEs6Module.ts b/src/services/refactors/convertToEs6Module.ts index 1046bf90aa6..933797554dd 100644 --- a/src/services/refactors/convertToEs6Module.ts +++ b/src/services/refactors/convertToEs6Module.ts @@ -74,7 +74,7 @@ namespace ts.refactor { Debug.assertEqual(actionName, _actionName); const { file, program } = context; Debug.assert(isSourceFileJavaScript(file)); - const edits = textChanges.ChangeTracker.with(context, changes => { + const edits = textChanges.ChangeTracker.with(toTextChangesContext(context), changes => { const moduleExportsChangedToDefault = convertFileToEs6Module(file, program.getTypeChecker(), changes, program.getCompilerOptions().target); if (moduleExportsChangedToDefault) { for (const importingFile of program.getSourceFiles()) { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index b3110a0ca36..9a9c30756a4 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -806,7 +806,7 @@ namespace ts.refactor.extractSymbol { ); } - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); if (nodeToInsertBefore) { @@ -1011,7 +1011,7 @@ namespace ts.refactor.extractSymbol { const initializer = transformConstantInitializer(node, substitutions); suppressLeadingAndTrailingTrivia(initializer); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); if (isClassLike(scope)) { Debug.assert(!isJS); // See CannotExtractToJSClass diff --git a/src/services/refactors/useDefaultImport.ts b/src/services/refactors/useDefaultImport.ts index a103168f67b..e64eaf0bfcb 100644 --- a/src/services/refactors/useDefaultImport.ts +++ b/src/services/refactors/useDefaultImport.ts @@ -54,7 +54,7 @@ namespace ts.refactor.installTypesForPackage { const newImportClause = createImportClause(name, /*namedBindings*/ undefined); const newImportStatement = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newImportClause, moduleSpecifier); return { - edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, importStatement, newImportStatement)), + edits: textChanges.ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(file, importStatement, newImportStatement)), renameFilename: undefined, renameLocation: undefined, }; diff --git a/src/services/services.ts b/src/services/services.ts index 4236416fbb3..1692da9872b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1887,12 +1887,11 @@ namespace ts { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); - const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { cancellationToken.throwIfCancellationRequested(); - return codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, formatContext }); + return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext }); }); } @@ -1900,10 +1899,9 @@ namespace ts { synchronizeHostData(); Debug.assert(scope.type === "file"); const sourceFile = getValidSourceFile(scope.fileName); - const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); - return codefix.getAllFixes({ fixId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext }); } function applyCodeActionCommand(action: CodeActionCommand): Promise; @@ -2134,7 +2132,6 @@ namespace ts { startPosition, endPosition, program: getProgram(), - newLineCharacter: formatOptions ? formatOptions.newLineCharacter : host.getNewLine(), host, formatContext: formatting.getFormatContext(formatOptions), cancellationToken, From f92e6a26c9c52c59865276f9c90d3a97dc4882b6 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 15:14:27 -0800 Subject: [PATCH 062/101] Update issue_template.md --- issue_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issue_template.md b/issue_template.md index 5e3d612c3c3..d460d66cbac 100644 --- a/issue_template.md +++ b/issue_template.md @@ -1,4 +1,4 @@ - +