From a06f0c3d9f97a99fb84cd7ee15433c2fe37655b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Oct 2017 17:15:02 -0700 Subject: [PATCH 001/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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/163] 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 68aad1b85e57f48b34ab2add6d5424dc17fa355a Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 16 Jan 2018 09:50:14 -0800 Subject: [PATCH 043/163] Normalize triple slash reference paths at resolve time --- src/compiler/program.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0d1bbf233a3..8f3cadcddc5 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -15,7 +15,8 @@ namespace ts { export function resolveTripleslashReference(moduleName: string, containingFile: string): string { const basePath = getDirectoryPath(containingFile); - const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); + const normalizedModuleName = normalizePath(moduleName); + const referencedFileName = isRootedDiskPath(normalizedModuleName) ? normalizedModuleName : combinePaths(basePath, normalizedModuleName); return normalizePath(referencedFileName); } From 5ea43db6ecee7db00586e4438131200e18cea2b8 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 16 Jan 2018 11:25:54 -0800 Subject: [PATCH 044/163] Add test --- .../tripleSlashReferenceAbsoluteWindowsPath.js | 14 ++++++++++++++ ...tripleSlashReferenceAbsoluteWindowsPath.symbols | 10 ++++++++++ .../tripleSlashReferenceAbsoluteWindowsPath.types | 13 +++++++++++++ .../tripleSlashReferenceAbsoluteWindowsPath.ts | 6 ++++++ 4 files changed, 43 insertions(+) create mode 100644 tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js create mode 100644 tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols create mode 100644 tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types create mode 100644 tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts diff --git a/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js new file mode 100644 index 00000000000..3e719abde5f --- /dev/null +++ b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts] //// + +//// [c.ts] +const x = 5; + +//// [d.ts] +/// +const y = x + 3; + +//// [c.js] +var x = 5; +//// [d.js] +/// +var y = x + 3; diff --git a/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols new file mode 100644 index 00000000000..0a3b6aedc6b --- /dev/null +++ b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols @@ -0,0 +1,10 @@ +=== C:/a/b/d.ts === +/// +const y = x + 3; +>y : Symbol(y, Decl(d.ts, 1, 5)) +>x : Symbol(x, Decl(c.ts, 0, 5)) + +=== C:/a/b/c.ts === +const x = 5; +>x : Symbol(x, Decl(c.ts, 0, 5)) + diff --git a/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types new file mode 100644 index 00000000000..fdc37ae2faf --- /dev/null +++ b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types @@ -0,0 +1,13 @@ +=== C:/a/b/d.ts === +/// +const y = x + 3; +>y : number +>x + 3 : number +>x : 5 +>3 : 3 + +=== C:/a/b/c.ts === +const x = 5; +>x : 5 +>5 : 5 + diff --git a/tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts b/tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts new file mode 100644 index 00000000000..b45e2a52b65 --- /dev/null +++ b/tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts @@ -0,0 +1,6 @@ +//@Filename: C:\a\b\c.ts +const x = 5; + +//@Filename: C:\a\b\d.ts +/// +const y = x + 3; \ No newline at end of file From 136c4d0dda5a381957f14b6c63456d2766053108 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 15:02:23 -0800 Subject: [PATCH 045/163] 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 5320ce25528e27612116d0063c11ed702964bef8 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 16 Jan 2018 17:04:14 -0800 Subject: [PATCH 046/163] Revert path normalization in favor of checking for backslash --- src/compiler/core.ts | 2 +- src/compiler/program.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0faabbda2a0..d92a02661e8 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1909,7 +1909,7 @@ namespace ts { return p2 + 1; } if (path.charCodeAt(1) === CharacterCodes.colon) { - if (path.charCodeAt(2) === CharacterCodes.slash) return 3; + if (path.charCodeAt(2) === CharacterCodes.slash || path.charCodeAt(2) === CharacterCodes.backslash) return 3; } // Per RFC 1738 'file' URI schema has the shape file:/// // if is omitted then it is assumed that host value is 'localhost', diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8f3cadcddc5..0d1bbf233a3 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -15,8 +15,7 @@ namespace ts { export function resolveTripleslashReference(moduleName: string, containingFile: string): string { const basePath = getDirectoryPath(containingFile); - const normalizedModuleName = normalizePath(moduleName); - const referencedFileName = isRootedDiskPath(normalizedModuleName) ? normalizedModuleName : combinePaths(basePath, normalizedModuleName); + const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); return normalizePath(referencedFileName); } From c4fddba0a9459c3ff0582ae787004368b9f063ef Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 17:11:32 -0800 Subject: [PATCH 047/163] 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 543b48d031f0127e0b57197dcf66e4a02a3c690f Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Wed, 17 Jan 2018 10:58:25 -0800 Subject: [PATCH 048/163] Add test for file path in tsconfig --- .../unittests/tsserverProjectSystem.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 565c20ac9cd..68fbc1ecba0 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2821,6 +2821,31 @@ namespace ts.projectSystem { checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); }); + it("Properly handle Windows-style outDir", () => { + const configFile: FileOrFolder = { + path: "C:\\a\\tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + outDir: `C:\\a\\b` + }, + include: ["*.ts"] + }) + }; + const file1: FileOrFolder = { + path: "C:\\a\\f1.ts", + content: "let x = 1;" + }; + + const host = createServerHost([file1, configFile], { useWindowsStylePaths: true }); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [normalizePath(file1.path), normalizePath(configFile.path)]); + const options = project.getCompilerOptions(); + assert.equal(options.outDir, "C:/a/b", ""); + }); }); describe("tsserverProjectSystem Proper errors", () => { From 2ba29d8d9d923d99b828a999b99c47db9cd1184f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:24:28 -0800 Subject: [PATCH 049/163] 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 050/163] 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 051/163] 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 052/163] 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 33fd30250d19ca087f191164c424e99d653f88cf Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 17 Jan 2018 11:58:38 -0800 Subject: [PATCH 053/163] update authors --- .mailmap | 3 ++- AUTHORS.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 80487998c95..b246515e467 100644 --- a/.mailmap +++ b/.mailmap @@ -311,4 +311,5 @@ Sharon Rolel Stanislav Iliev Wenlu Wang <805037171@163.com> wenlu.wang <805037171@163.com> kingwl <805037171@163.com> Wilson Hobbs -Yuval Greenfield \ No newline at end of file +Yuval Greenfield +Daniel # @nieltg \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 0dfaddb7a25..7662a5642e7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -205,6 +205,7 @@ TypeScript is authored by: * Nathan Shively-Sanders * Nathan Yee * Nicolas Henry +* @nieltg * Nima Zahedi * Noah Chen * Noel Varanda From 48ac3019b47185b57468384d18e8f73a19fc594d Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 17 Jan 2018 11:59:01 -0800 Subject: [PATCH 054/163] Add example to command description --- scripts/authors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/authors.ts b/scripts/authors.ts index fd9d27acd00..b616ceb8601 100644 --- a/scripts/authors.ts +++ b/scripts/authors.ts @@ -164,7 +164,7 @@ namespace Commands { } }); }; - listAuthors.description = "List known and unknown authors for a given spec"; + listAuthors.description = "List known and unknown authors for a given spec, e.g. 'node authors.js listAuthors origin/release-2.6..origin/release-2.7'"; } var args = process.argv.slice(2); From 8ed885db3e462ec5ddeebfd136945e9a9cc0ac8a Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 12:05:31 -0800 Subject: [PATCH 055/163] Add completions from the 'this' type (#21231) * Add completions from the 'this' type * Code review --- src/compiler/checker.ts | 20 +++++--- src/compiler/types.ts | 2 + src/harness/fourslash.ts | 4 +- src/services/completions.ts | 51 +++++++++++++------ .../cases/fourslash/completionListInScope.ts | 4 +- tests/cases/fourslash/completionsThisType.ts | 29 +++++++++++ tests/cases/fourslash/fourslash.ts | 8 ++- 7 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 tests/cases/fourslash/completionsThisType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b3f06788131..39679675a31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -295,6 +295,10 @@ namespace ts { getAccessibleSymbolChain, getTypePredicateOfSignature, resolveExternalModuleSymbol, + tryGetThisTypeAt: node => { + node = getParseTreeNode(node); + return node && tryGetThisTypeAt(node); + }, }; const tupleTypes: GenericType[] = []; @@ -13268,6 +13272,16 @@ namespace ts { if (needToCaptureLexicalThis) { captureLexicalThis(node, container); } + + const type = tryGetThisTypeAt(node, container); + if (!type && noImplicitThis) { + // With noImplicitThis, functions may not reference 'this' if it has type 'any' + error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); + } + return type || anyType; + } + + function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { if (isFunctionLike(container) && (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. @@ -13306,12 +13320,6 @@ namespace ts { return type; } } - - if (noImplicitThis) { - // With noImplicitThis, functions may not reference 'this' if it has type 'any' - error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); - } - return anyType; } function getTypeForThisExpressionFromJSDoc(node: Node) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9d970b88197..703b04699b8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2921,6 +2921,8 @@ namespace ts { /* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined; /* @internal */ getTypePredicateOfSignature(signature: Signature): TypePredicate; /* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol; + /** @param node A location where we might consider accessing `this`. Not necessarily a ThisExpression. */ + /* @internal */ tryGetThisTypeAt(node: Node): Type | undefined; } /* @internal */ diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4c0c72a200f..84bf3bb1752 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3152,8 +3152,9 @@ Actual: ${stringify(fullActual)}`); assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + entryId)); } - assert.equal(item.hasAction, hasAction); + assert.equal(item.hasAction, hasAction, "hasAction"); assert.equal(item.isRecommended, options && options.isRecommended, "isRecommended"); + assert.equal(item.insertText, options && options.insertText, "insertText"); } private findFile(indexOrName: string | number) { @@ -4615,6 +4616,7 @@ namespace FourSlashInterface { export interface VerifyCompletionListContainsOptions extends ts.GetCompletionsAtPositionOptions { sourceDisplay: string; isRecommended?: true; + insertText?: string; } export interface NewContentOptions { diff --git a/src/services/completions.ts b/src/services/completions.ts index 2afac10a944..36a10a1b578 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -4,7 +4,9 @@ namespace ts.Completions { export type Log = (message: string) => void; - interface SymbolOriginInfo { + type SymbolOriginInfo = { type: "this-type" } | SymbolOriginInfoExport; + interface SymbolOriginInfoExport { + type: "export"; moduleSymbol: Symbol; isDefaultExport: boolean; } @@ -170,11 +172,21 @@ namespace ts.Completions { return undefined; } const { name, needsConvertPropertyAccess } = info; - Debug.assert(!(needsConvertPropertyAccess && !propertyAccessToConvert)); if (needsConvertPropertyAccess && !includeInsertTextCompletions) { return undefined; } + let insertText: string | undefined; + let replacementSpan: TextSpan | undefined; + if (kind === CompletionKind.Global && origin && origin.type === "this-type") { + insertText = needsConvertPropertyAccess ? `this["${name}"]` : `this.${name}`; + } + else if (needsConvertPropertyAccess) { + // TODO: GH#20619 Use configured quote style + insertText = `["${name}"]`; + replacementSpan = createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert!, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert!.name.end); + } + // TODO(drosen): Right now we just permit *all* semantic meanings when calling // 'getSymbolKind' which is permissible given that it is backwards compatible; but // really we should consider passing the meaning for the node so that we don't report @@ -189,13 +201,10 @@ namespace ts.Completions { kindModifiers: SymbolDisplay.getSymbolModifiers(symbol), sortText: "0", source: getSourceFromOrigin(origin), - // TODO: GH#20619 Use configured quote style - insertText: needsConvertPropertyAccess ? `["${name}"]` : undefined, - replacementSpan: needsConvertPropertyAccess - ? createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert.name.end) - : undefined, - hasAction: trueOrUndefined(needsConvertPropertyAccess || origin !== undefined), + hasAction: trueOrUndefined(!!origin && origin.type === "export"), isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)), + insertText, + replacementSpan, }; } @@ -210,7 +219,7 @@ namespace ts.Completions { } function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { - return origin && stripQuotes(origin.moduleSymbol.name); + return origin && origin.type === "export" ? stripQuotes(origin.moduleSymbol.name) : undefined; } function getCompletionEntriesFromSymbols( @@ -504,7 +513,7 @@ namespace ts.Completions { } function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string { - return origin && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default + return origin && origin.type === "export" && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. ? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined) || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) @@ -590,13 +599,13 @@ namespace ts.Completions { allSourceFiles: ReadonlyArray, ): CodeActionsAndSourceDisplay { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; - return symbolOriginInfo + return symbolOriginInfo && symbolOriginInfo.type === "export" ? getCodeActionsAndSourceDisplayForImport(symbolOriginInfo, symbol, program, checker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles) : { codeActions: undefined, sourceDisplay: undefined }; } function getCodeActionsAndSourceDisplayForImport( - symbolOriginInfo: SymbolOriginInfo, + symbolOriginInfo: SymbolOriginInfoExport, symbol: Symbol, program: Program, checker: TypeChecker, @@ -1117,6 +1126,18 @@ namespace ts.Completions { const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias; symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings); + + // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` + if (options.includeInsertTextCompletions && scopeNode.kind !== SyntaxKind.SourceFile) { + const thisType = typeChecker.tryGetThisTypeAt(scopeNode); + if (thisType) { + for (const symbol of getPropertiesForCompletion(thisType, typeChecker, /*isForAccess*/ true)) { + symbolToOriginInfoMap[getSymbolId(symbol)] = { type: "this-type" }; + symbols.push(symbol); + } + } + } + if (options.includeExternalModuleExports) { getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", target); } @@ -1230,10 +1251,10 @@ namespace ts.Completions { symbol = getLocalSymbolForExportDefault(symbol) || symbol; } - const origin: SymbolOriginInfo = { moduleSymbol, isDefaultExport }; + const origin: SymbolOriginInfo = { type: "export", moduleSymbol, isDefaultExport }; if (stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) { symbols.push(symbol); - symbolToOriginInfoMap[getSymbolId(symbol)] = { moduleSymbol, isDefaultExport }; + symbolToOriginInfoMap[getSymbolId(symbol)] = origin; } } }); @@ -2072,13 +2093,13 @@ namespace ts.Completions { if (isIdentifierText(name, target)) return validIdentiferResult; switch (kind) { case CompletionKind.None: - case CompletionKind.Global: case CompletionKind.MemberLike: return undefined; case CompletionKind.ObjectPropertyDeclaration: // TODO: GH#18169 return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; case CompletionKind.PropertyAccess: + case CompletionKind.Global: // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; case CompletionKind.String: diff --git a/tests/cases/fourslash/completionListInScope.ts b/tests/cases/fourslash/completionListInScope.ts index 8773f6d4588..70f82b80f5f 100644 --- a/tests/cases/fourslash/completionListInScope.ts +++ b/tests/cases/fourslash/completionListInScope.ts @@ -13,7 +13,7 @@ //// interface localInterface {} //// export interface exportedInterface {} //// -//// module localModule { +//// module localModule { //// export var x = 0; //// } //// export module exportedModule { @@ -38,7 +38,7 @@ //// interface localInterface2 {} //// export interface exportedInterface2 {} //// -//// module localModule2 { +//// module localModule2 { //// export var x = 0; //// } //// export module exportedModule2 { diff --git a/tests/cases/fourslash/completionsThisType.ts b/tests/cases/fourslash/completionsThisType.ts new file mode 100644 index 00000000000..58325182a22 --- /dev/null +++ b/tests/cases/fourslash/completionsThisType.ts @@ -0,0 +1,29 @@ +/// + +////class C { +//// "foo bar": number; +//// xyz() { +//// /**/ +//// } +////} +//// +////function f(this: { x: number }) { /*f*/ } + +goTo.marker(""); + +verify.completionListContains("xyz", "(method) C.xyz(): void", "", "method", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "this.xyz", +}); + +verify.completionListContains("foo bar", '(property) C["foo bar"]: number', "", "property", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: 'this["foo bar"]', +}); + +goTo.marker("f"); + +verify.completionListContains("x", "(property) x: number", "", "property", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "this.x", +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 97379957cbf..0bf62eaef92 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -151,7 +151,13 @@ declare namespace FourSlashInterface { kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, - options?: { includeExternalModuleExports?: boolean, sourceDisplay?: string, isRecommended?: true }, + options?: { + includeExternalModuleExports?: boolean, + includeInsertTextCompletions?: boolean, + sourceDisplay?: string, + isRecommended?: true, + insertText?: string, + }, ): void; completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; From f96dc84a708937df865f84658fde4c011cd3bacf Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 12:42:31 -0800 Subject: [PATCH 056/163] Make getCombinedCodeFix API public (#21234) --- src/server/client.ts | 3 +- src/server/protocol.ts | 16 +------ src/services/types.ts | 11 +---- .../reference/api/tsserverlibrary.d.ts | 47 ++++++++++++++++++- tests/baselines/reference/api/typescript.d.ts | 18 ++++++- 5 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index 7a64d9b528c..8203475b06d 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -558,8 +558,7 @@ namespace ts.server { const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - // TODO: GH#20538 shouldn't need cast - return (response.body as ReadonlyArray).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); + return response.body.map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } getCombinedCodeFix = notImplemented; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b3cbba27a20..b4ff0aa4037 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -102,8 +102,6 @@ namespace ts.server.protocol { GetCodeFixes = "getCodeFixes", /* @internal */ GetCodeFixesFull = "getCodeFixes-full", - // TODO: GH#20538 - /* @internal */ GetCombinedCodeFix = "getCombinedCodeFix", /* @internal */ GetCombinedCodeFixFull = "getCombinedCodeFix-full", @@ -557,15 +555,11 @@ namespace ts.server.protocol { arguments: CodeFixRequestArgs; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixRequest extends Request { command: CommandTypes.GetCombinedCodeFix; arguments: GetCombinedCodeFixRequestArgs; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixResponse extends Response { body: CombinedCodeActions; } @@ -622,15 +616,11 @@ namespace ts.server.protocol { errorCodes?: ReadonlyArray; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixRequestArgs { scope: GetCombinedCodeFixScope; fixId: {}; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixScope { type: "file"; args: FileRequestArgs; @@ -1619,7 +1609,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; // TODO: GH#20538 CodeFixAction[] + body?: CodeFixAction[]; } export interface CodeAction { @@ -1631,15 +1621,11 @@ namespace ts.server.protocol { commands?: {}[]; } - // TODO: GH#20538 - /* @internal */ export interface CombinedCodeActions { changes: ReadonlyArray; commands?: ReadonlyArray<{}>; } - // TODO: GH#20538 - /* @internal */ export interface CodeFixAction extends CodeAction { /** * If present, one may call 'getCombinedCodeFix' with this fixId. diff --git a/src/services/types.ts b/src/services/types.ts index 597709a7c79..4ebd4a3e76a 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,10 +295,7 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - // TODO: GH#20538 return `ReadonlyArray` - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; - // TODO: GH#20538 - /* @internal */ + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; @@ -327,8 +324,6 @@ namespace ts { dispose(): void; } - // TODO: GH#20538 - /* @internal */ export interface CombinedCodeFixScope { type: "file"; fileName: string; } export interface GetCompletionsAtPositionOptions { @@ -419,8 +414,6 @@ namespace ts { commands?: CodeActionCommand[]; } - // TODO: GH#20538 - /* @internal */ export interface CodeFixAction extends CodeAction { /** * If present, one may call 'getCombinedCodeFix' with this fixId. @@ -429,8 +422,6 @@ namespace ts { fixId?: {}; } - // TODO: GH#20538 - /* @internal */ export interface CombinedCodeActions { changes: ReadonlyArray; commands: ReadonlyArray | undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a0dfa5f5d17..942ca44a782 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4091,7 +4091,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4107,6 +4108,10 @@ declare namespace ts { getProgram(): Program; dispose(): void; } + interface CombinedCodeFixScope { + type: "file"; + fileName: string; + } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; includeInsertTextCompletions: boolean; @@ -4184,6 +4189,17 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } + interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } + interface CombinedCodeActions { + changes: ReadonlyArray; + commands: ReadonlyArray | undefined; + } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } @@ -5027,6 +5043,7 @@ declare namespace ts.server.protocol { DocCommentTemplate = "docCommentTemplate", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", + GetCombinedCodeFix = "getCombinedCodeFix", ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -5389,6 +5406,13 @@ declare namespace ts.server.protocol { command: CommandTypes.GetCodeFixes; arguments: CodeFixRequestArgs; } + interface GetCombinedCodeFixRequest extends Request { + command: CommandTypes.GetCombinedCodeFix; + arguments: GetCombinedCodeFixRequestArgs; + } + interface GetCombinedCodeFixResponse extends Response { + body: CombinedCodeActions; + } interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -5422,6 +5446,14 @@ declare namespace ts.server.protocol { */ errorCodes?: ReadonlyArray; } + interface GetCombinedCodeFixRequestArgs { + scope: GetCombinedCodeFixScope; + fixId: {}; + } + interface GetCombinedCodeFixScope { + type: "file"; + args: FileRequestArgs; + } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ command: {}; @@ -6164,7 +6196,7 @@ declare namespace ts.server.protocol { } interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; + body?: CodeFixAction[]; } interface CodeAction { /** Description of the code action to display in the UI of the editor */ @@ -6174,6 +6206,17 @@ declare namespace ts.server.protocol { /** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */ commands?: {}[]; } + interface CombinedCodeActions { + changes: ReadonlyArray; + commands?: ReadonlyArray<{}>; + } + interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } /** * Format and format on key response message. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ce90fef044e..d5259f39f7f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4091,7 +4091,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4107,6 +4108,10 @@ declare namespace ts { getProgram(): Program; dispose(): void; } + interface CombinedCodeFixScope { + type: "file"; + fileName: string; + } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; includeInsertTextCompletions: boolean; @@ -4184,6 +4189,17 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } + interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } + interface CombinedCodeActions { + changes: ReadonlyArray; + commands: ReadonlyArray | undefined; + } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } From e248d08e4c7b4e8a87d0844dd6068bfafd1f9d7b Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 12:43:41 -0800 Subject: [PATCH 057/163] Combine `repeatString` helper functions (#21235) --- src/harness/fourslash.ts | 16 ++++------------ src/services/formatting/formatting.ts | 17 ++++------------- src/services/utilities.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 84bf3bb1752..df870034270 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1535,8 +1535,8 @@ Actual: ${stringify(fullActual)}`); const addSpanInfoString = () => { if (previousSpanInfo) { resultString += currentLine; - let thisLineMarker = repeatString(startColumn, " ") + repeatString(length, "~"); - thisLineMarker += repeatString(this.alignmentForExtraInfo - thisLineMarker.length - prefixString.length + 1, " "); + let thisLineMarker = ts.repeatString(" ", startColumn) + ts.repeatString("~", length); + thisLineMarker += ts.repeatString(" ", this.alignmentForExtraInfo - thisLineMarker.length - prefixString.length + 1); resultString += thisLineMarker; resultString += "=> Pos: (" + (pos - length) + " to " + (pos - 1) + ") "; resultString += " " + previousSpanInfo; @@ -1551,7 +1551,7 @@ Actual: ${stringify(fullActual)}`); if (resultString.length) { resultString += "\n--------------------------------"; } - currentLine = "\n" + nextLine.toString() + repeatString(3 - nextLine.toString().length, " ") + ">" + this.activeFile.content.substring(pos, fileLineMap[nextLine]) + "\n "; + currentLine = "\n" + nextLine.toString() + ts.repeatString(" ", 3 - nextLine.toString().length) + ">" + this.activeFile.content.substring(pos, fileLineMap[nextLine]) + "\n "; startColumn = 0; length = 0; } @@ -2787,7 +2787,7 @@ Actual: ${stringify(fullActual)}`); const items = this.languageService.getNavigationBarItems(this.activeFile.fileName); Harness.IO.log(`Navigation bar (${items.length} items)`); for (const item of items) { - Harness.IO.log(`${repeatString(item.indent, " ")}name: ${item.text}, kind: ${item.kind}, childItems: ${item.childItems.map(child => child.text)}`); + Harness.IO.log(`${ts.repeatString(" ", item.indent)}name: ${item.text}, kind: ${item.kind}, childItems: ${item.childItems.map(child => child.text)}`); } } @@ -3689,14 +3689,6 @@ ${code} }; } - function repeatString(count: number, char: string) { - let result = ""; - for (let i = 0; i < count; i++) { - result += char; - } - return result; - } - function stringify(data: any, replacer?: (key: string, value: any) => any): string { return JSON.stringify(data, replacer, 2); } diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 49fb1420db8..c020e638692 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1277,13 +1277,13 @@ namespace ts.formatting { } if (internedTabsIndentation[tabs] === undefined) { - internedTabsIndentation[tabs] = tabString = repeat("\t", tabs); + internedTabsIndentation[tabs] = tabString = repeatString("\t", tabs); } else { tabString = internedTabsIndentation[tabs]; } - return spaces ? tabString + repeat(" ", spaces) : tabString; + return spaces ? tabString + repeatString(" ", spaces) : tabString; } else { let spacesString: string; @@ -1294,23 +1294,14 @@ namespace ts.formatting { } if (internedSpacesIndentation[quotient] === undefined) { - spacesString = repeat(" ", options.indentSize * quotient); + spacesString = repeatString(" ", options.indentSize * quotient); internedSpacesIndentation[quotient] = spacesString; } else { spacesString = internedSpacesIndentation[quotient]; } - return remainder ? spacesString + repeat(" ", remainder) : spacesString; - } - - function repeat(value: string, count: number): string { - let s = ""; - for (let i = 0; i < count; i++) { - s += value; - } - - return s; + return remainder ? spacesString + repeatString(" ", remainder) : spacesString; } } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index abfe5a7a55d..344a44a9f18 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1110,6 +1110,14 @@ namespace ts { export function getSnapshotText(snap: IScriptSnapshot): string { return snap.getText(0, snap.getLength()); } + + export function repeatString(str: string, count: number): string { + let result = ""; + for (let i = 0; i < count; i++) { + result += str; + } + return result; + } } // Display-part writer helpers From ea6d7e91757dbaad991f9424b321cdd3cada0f37 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 12:44:03 -0800 Subject: [PATCH 058/163] 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 059/163] 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 060/163] 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 061/163] 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 062/163] 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 063/163] 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 064/163] 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 065/163] 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 066/163] 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 067/163] 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 068/163] 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 069/163] 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 070/163] 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 071/163] 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 @@ - + Fixes # +#17080 diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 405fd11f138..74c60bcdb62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13046,7 +13046,7 @@ namespace ts { node.parent.kind === SyntaxKind.NonNullExpression || declaration.kind === SyntaxKind.VariableDeclaration && (declaration).exclamationToken || declaration.flags & NodeFlags.Ambient; - const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, getRootDeclaration(declaration) as VariableLikeDeclaration) : type) : + const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : type === autoType || type === autoArrayType ? undefinedType : getOptionalType(type); const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized); diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt new file mode 100644 index 00000000000..14bc3ea8e6c --- /dev/null +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt @@ -0,0 +1,62 @@ +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(6,8): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. + Type 'undefined' is not assignable to type 'number'. +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(16,7): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. + Type 'undefined' is not assignable to type 'number'. +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(21,7): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. + Type 'undefined' is not assignable to type 'number'. +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(31,8): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. + Type 'undefined' is not assignable to type 'number'. + + +==== tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts (4 errors) ==== + // https://github.com/Microsoft/TypeScript/issues/17080 + function f(a:number,b:number) { + } + + function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { + f(a, b) + ~ +!!! error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. +!!! error TS2345: Type 'undefined' is not assignable to type 'number'. + // error + } + + function func2( {a, b = 3}: {a: number, b?:number} = {a: 1,b: 2} ) { + f(a, b) + // no error + } + + function func3( {a, b}: {a: number, b?: number} = {a: 1} ) { + f(a,b) + ~ +!!! error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. +!!! error TS2345: Type 'undefined' is not assignable to type 'number'. + // error + } + + function func4( {a: {b, c}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { + f(b,c) + ~ +!!! error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. +!!! error TS2345: Type 'undefined' is not assignable to type 'number'. + // error + } + + function func5({a: {b, c = 4}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { + f(b, c) + // no error + } + + function func6( {a: {b, c} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1,c: 2}, d: 3} ) { + f(b, c) + ~ +!!! error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. +!!! error TS2345: Type 'undefined' is not assignable to type 'number'. + // error + } + + function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1, c: 2}, d: 3} ) { + f(b, c) + // no error + } + \ No newline at end of file diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js new file mode 100644 index 00000000000..8c297088f14 --- /dev/null +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js @@ -0,0 +1,80 @@ +//// [optionalParameterInDestructuringWithInitializer.ts] +// https://github.com/Microsoft/TypeScript/issues/17080 +function f(a:number,b:number) { +} + +function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { + f(a, b) + // error +} + +function func2( {a, b = 3}: {a: number, b?:number} = {a: 1,b: 2} ) { + f(a, b) + // no error +} + +function func3( {a, b}: {a: number, b?: number} = {a: 1} ) { + f(a,b) + // error +} + +function func4( {a: {b, c}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { + f(b,c) + // error +} + +function func5({a: {b, c = 4}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { + f(b, c) + // no error +} + +function func6( {a: {b, c} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1,c: 2}, d: 3} ) { + f(b, c) + // error +} + +function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1, c: 2}, d: 3} ) { + f(b, c) + // no error +} + + +//// [optionalParameterInDestructuringWithInitializer.js] +// https://github.com/Microsoft/TypeScript/issues/17080 +function f(a, b) { +} +function func1(_a) { + var _b = _a === void 0 ? { a: 1, b: 2 } : _a, a = _b.a, b = _b.b; + f(a, b); + // error +} +function func2(_a) { + var _b = _a === void 0 ? { a: 1, b: 2 } : _a, a = _b.a, _c = _b.b, b = _c === void 0 ? 3 : _c; + f(a, b); + // no error +} +function func3(_a) { + var _b = _a === void 0 ? { a: 1 } : _a, a = _b.a, b = _b.b; + f(a, b); + // error +} +function func4(_a) { + var _b = _a === void 0 ? { a: { b: 1, c: 2 }, d: 3 } : _a, _c = _b.a, b = _c.b, c = _c.c, d = _b.d; + f(b, c); + // error +} +function func5(_a) { + var _b = _a === void 0 ? { a: { b: 1, c: 2 }, d: 3 } : _a, _c = _b.a, b = _c.b, _d = _c.c, c = _d === void 0 ? 4 : _d, d = _b.d; + f(b, c); + // no error +} +function func6(_a) { + var _b = _a === void 0 ? { a: { b: 1, c: 2 }, d: 3 } : _a, _c = _b.a, _d = _c === void 0 ? { b: 4, c: 5 } : _c, b = _d.b, c = _d.c, d = _b.d; + f(b, c); + // error +} +function func7(_a) { + var _b = _a === void 0 ? { a: { b: 1, c: 2 }, d: 3 } : _a, _c = _b.a, _d = _c === void 0 ? { b: 4, c: 5 } : _c, b = _d.b, _e = _d.c, c = _e === void 0 ? 6 : _e, d = _b.d; + f(b, c); + // no error +} diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols new file mode 100644 index 00000000000..fc068a0babb --- /dev/null +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols @@ -0,0 +1,154 @@ +=== tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts === +// https://github.com/Microsoft/TypeScript/issues/17080 +function f(a:number,b:number) { +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 1, 11)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 1, 20)) +} + +function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { +>func1 : Symbol(func1, Decl(optionalParameterInDestructuringWithInitializer.ts, 2, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 17)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 19)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 25)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 35)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 51)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 56)) + + f(a, b) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 17)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 19)) + + // error +} + +function func2( {a, b = 3}: {a: number, b?:number} = {a: 1,b: 2} ) { +>func2 : Symbol(func2, Decl(optionalParameterInDestructuringWithInitializer.ts, 7, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 17)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 19)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 29)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 39)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 54)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 59)) + + f(a, b) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 17)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 9, 19)) + + // no error +} + +function func3( {a, b}: {a: number, b?: number} = {a: 1} ) { +>func3 : Symbol(func3, Decl(optionalParameterInDestructuringWithInitializer.ts, 12, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 17)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 19)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 25)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 35)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 51)) + + f(a,b) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 17)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 14, 19)) + + // error +} + +function func4( {a: {b, c}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { +>func4 : Symbol(func4, Decl(optionalParameterInDestructuringWithInitializer.ts, 17, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 33)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 21)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 23)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 27)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 33)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 37)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 47)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 59)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 73)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 77)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 82)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 88)) + + f(b,c) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 21)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 19, 23)) + + // error +} + +function func5({a: {b, c = 4}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { +>func5 : Symbol(func5, Decl(optionalParameterInDestructuringWithInitializer.ts, 22, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 36)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 20)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 22)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 30)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 36)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 40)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 50)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 62)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 76)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 80)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 85)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 91)) + + f(b, c) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 20)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 24, 22)) + + // no error +} + +function func6( {a: {b, c} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1,c: 2}, d: 3} ) { +>func6 : Symbol(func6, Decl(optionalParameterInDestructuringWithInitializer.ts, 27, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 48)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 21)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 23)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 30)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 35)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 42)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 48)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 52)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 62)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 75)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 90)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 94)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 99)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 105)) + + f(b, c) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 21)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 29, 23)) + + // error +} + +function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1, c: 2}, d: 3} ) { +>func7 : Symbol(func7, Decl(optionalParameterInDestructuringWithInitializer.ts, 32, 1)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 52)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 21)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 23)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 34)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 39)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 46)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 52)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 56)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 66)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 79)) +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 94)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 98)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 103)) +>d : Symbol(d, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 110)) + + f(b, c) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 21)) +>c : Symbol(c, Decl(optionalParameterInDestructuringWithInitializer.ts, 34, 23)) + + // no error +} + diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types new file mode 100644 index 00000000000..8def19dde20 --- /dev/null +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types @@ -0,0 +1,198 @@ +=== tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts === +// https://github.com/Microsoft/TypeScript/issues/17080 +function f(a:number,b:number) { +>f : (a: number, b: number) => void +>a : number +>b : number +} + +function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { +>func1 : ({ a, b }?: { a: number; b?: number | undefined; }) => void +>a : number +>b : number | undefined +>a : number +>b : number | undefined +>{a: 1, b: 2} : { a: number; b: number; } +>a : number +>1 : 1 +>b : number +>2 : 2 + + f(a, b) +>f(a, b) : void +>f : (a: number, b: number) => void +>a : number +>b : number | undefined + + // error +} + +function func2( {a, b = 3}: {a: number, b?:number} = {a: 1,b: 2} ) { +>func2 : ({ a, b }?: { a: number; b?: number | undefined; }) => void +>a : number +>b : number +>3 : 3 +>a : number +>b : number | undefined +>{a: 1,b: 2} : { a: number; b: number; } +>a : number +>1 : 1 +>b : number +>2 : 2 + + f(a, b) +>f(a, b) : void +>f : (a: number, b: number) => void +>a : number +>b : number + + // no error +} + +function func3( {a, b}: {a: number, b?: number} = {a: 1} ) { +>func3 : ({ a, b }?: { a: number; b?: number | undefined; }) => void +>a : number +>b : number | undefined +>a : number +>b : number | undefined +>{a: 1} : { a: number; } +>a : number +>1 : 1 + + f(a,b) +>f(a,b) : void +>f : (a: number, b: number) => void +>a : number +>b : number | undefined + + // error +} + +function func4( {a: {b, c}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { +>func4 : ({ a: { b, c }, d }?: { a: { b: number; c?: number | undefined; }; d: number; }) => void +>a : any +>b : number +>c : number | undefined +>d : number +>a : { b: number; c?: number | undefined; } +>b : number +>c : number | undefined +>d : number +>{a: {b: 1,c: 2},d: 3} : { a: { b: number; c: number; }; d: number; } +>a : { b: number; c: number; } +>{b: 1,c: 2} : { b: number; c: number; } +>b : number +>1 : 1 +>c : number +>2 : 2 +>d : number +>3 : 3 + + f(b,c) +>f(b,c) : void +>f : (a: number, b: number) => void +>b : number +>c : number | undefined + + // error +} + +function func5({a: {b, c = 4}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { +>func5 : ({ a: { b, c }, d }?: { a: { b: number; c?: number | undefined; }; d: number; }) => void +>a : any +>b : number +>c : number +>4 : 4 +>d : number +>a : { b: number; c?: number | undefined; } +>b : number +>c : number | undefined +>d : number +>{a: {b: 1,c: 2},d: 3} : { a: { b: number; c: number; }; d: number; } +>a : { b: number; c: number; } +>{b: 1,c: 2} : { b: number; c: number; } +>b : number +>1 : 1 +>c : number +>2 : 2 +>d : number +>3 : 3 + + f(b, c) +>f(b, c) : void +>f : (a: number, b: number) => void +>b : number +>c : number + + // no error +} + +function func6( {a: {b, c} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1,c: 2}, d: 3} ) { +>func6 : ({ a: { b, c }, d }?: { a: { b: number; c?: number | undefined; }; d: number; }) => void +>a : any +>b : number +>c : number | undefined +>{b: 4, c: 5} : { b: number; c: number; } +>b : number +>4 : 4 +>c : number +>5 : 5 +>d : number +>a : { b: number; c?: number | undefined; } +>b : number +>c : number | undefined +>d : number +>{a: {b: 1,c: 2}, d: 3} : { a: { b: number; c: number; }; d: number; } +>a : { b: number; c: number; } +>{b: 1,c: 2} : { b: number; c: number; } +>b : number +>1 : 1 +>c : number +>2 : 2 +>d : number +>3 : 3 + + f(b, c) +>f(b, c) : void +>f : (a: number, b: number) => void +>b : number +>c : number | undefined + + // error +} + +function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1, c: 2}, d: 3} ) { +>func7 : ({ a: { b, c }, d }?: { a: { b: number; c?: number | undefined; }; d: number; }) => void +>a : any +>b : number +>c : number +>6 : 6 +>{b: 4, c: 5} : { b: number; c?: number; } +>b : number +>4 : 4 +>c : number +>5 : 5 +>d : number +>a : { b: number; c?: number | undefined; } +>b : number +>c : number | undefined +>d : number +>{a: {b: 1, c: 2}, d: 3} : { a: { b: number; c: number; }; d: number; } +>a : { b: number; c: number; } +>{b: 1, c: 2} : { b: number; c: number; } +>b : number +>1 : 1 +>c : number +>2 : 2 +>d : number +>3 : 3 + + f(b, c) +>f(b, c) : void +>f : (a: number, b: number) => void +>b : number +>c : number + + // no error +} + diff --git a/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts b/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts new file mode 100644 index 00000000000..3666ee2d954 --- /dev/null +++ b/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts @@ -0,0 +1,39 @@ +// @strictNullChecks: true +// https://github.com/Microsoft/TypeScript/issues/17080 +function f(a:number,b:number) { +} + +function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { + f(a, b) + // error +} + +function func2( {a, b = 3}: {a: number, b?:number} = {a: 1,b: 2} ) { + f(a, b) + // no error +} + +function func3( {a, b}: {a: number, b?: number} = {a: 1} ) { + f(a,b) + // error +} + +function func4( {a: {b, c}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { + f(b,c) + // error +} + +function func5({a: {b, c = 4}, d}: {a: {b: number,c?: number},d: number} = {a: {b: 1,c: 2},d: 3} ) { + f(b, c) + // no error +} + +function func6( {a: {b, c} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1,c: 2}, d: 3} ) { + f(b, c) + // error +} + +function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, d: number} = {a: {b: 1, c: 2}, d: 3} ) { + f(b, c) + // no error +} From 5a87a94c59a7ea137113a873b163c98e7322fa81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ginth=C3=B6r?= <26004708+Lazarus535@users.noreply.github.com> Date: Mon, 22 Jan 2018 13:40:57 +0100 Subject: [PATCH 121/163] Fixes #17080 https://github.com/Microsoft/TypeScript/issues/17080 Added testcases from the Github bugreport (all working as intended now). Signed CLA. --- pull_request_template.md | 2 +- ...rInDestructuringWithInitializer.errors.txt | 38 ++++++++++- ...ParameterInDestructuringWithInitializer.js | 44 +++++++++++++ ...eterInDestructuringWithInitializer.symbols | 55 ++++++++++++++++ ...ameterInDestructuringWithInitializer.types | 66 +++++++++++++++++++ ...ParameterInDestructuringWithInitializer.ts | 26 ++++++++ 6 files changed, 229 insertions(+), 2 deletions(-) diff --git a/pull_request_template.md b/pull_request_template.md index faa484054be..4243c2f671f 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -6,7 +6,7 @@ Here's a checklist you might find useful. 'Bug' or 'help wanted' or is in the Community milestone [X] Code is up-to-date with the `master` branch [X] You've successfully run `jake runtests` locally -[ ] You've signed the CLA +[X] You've signed the CLA [X] There are new or updated unit tests validating the change Refer to CONTRIBUTING.MD for more details. diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt index 14bc3ea8e6c..8965d7172e8 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt @@ -6,9 +6,13 @@ tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(21,7): e Type 'undefined' is not assignable to type 'number'. tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(31,8): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. Type 'undefined' is not assignable to type 'number'. +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(45,10): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. + Type 'undefined' is not assignable to type 'number'. +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(55,11): error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number | undefined'. + Type 'null' is not assignable to type 'number | undefined'. -==== tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts (4 errors) ==== +==== tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts (6 errors) ==== // https://github.com/Microsoft/TypeScript/issues/17080 function f(a:number,b:number) { } @@ -59,4 +63,36 @@ tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(31,8): e f(b, c) // no error } + + interface Foo { + readonly bar?: number; + } + + function performFoo({ bar }: Foo = {}) { + useBar(bar); + ~~~ +!!! error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. +!!! error TS2345: Type 'undefined' is not assignable to type 'number'. + } + + function useBar(bar: number) { + f(bar, 1) + } + + performFoo(); + + function performFoo2({ bar = null }: Foo = {}) { + useBar2(bar); + ~~~ +!!! error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number | undefined'. +!!! error TS2345: Type 'null' is not assignable to type 'number | undefined'. + } + + function useBar2(bar: number | undefined) { + if (bar) { + f(bar, 1) + } + } + + performFoo2(); \ No newline at end of file diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js index 8c297088f14..cb929d89112 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js @@ -37,6 +37,32 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, f(b, c) // no error } + +interface Foo { + readonly bar?: number; +} + +function performFoo({ bar }: Foo = {}) { + useBar(bar); +} + +function useBar(bar: number) { + f(bar, 1) +} + +performFoo(); + +function performFoo2({ bar = null }: Foo = {}) { + useBar2(bar); +} + +function useBar2(bar: number | undefined) { + if (bar) { + f(bar, 1) + } +} + +performFoo2(); //// [optionalParameterInDestructuringWithInitializer.js] @@ -78,3 +104,21 @@ function func7(_a) { f(b, c); // no error } +function performFoo(_a) { + var bar = (_a === void 0 ? {} : _a).bar; + useBar(bar); +} +function useBar(bar) { + f(bar, 1); +} +performFoo(); +function performFoo2(_a) { + var _b = (_a === void 0 ? {} : _a).bar, bar = _b === void 0 ? null : _b; + useBar2(bar); +} +function useBar2(bar) { + if (bar) { + f(bar, 1); + } +} +performFoo2(); diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols index fc068a0babb..399956476c8 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols @@ -152,3 +152,58 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, // no error } +interface Foo { +>Foo : Symbol(Foo, Decl(optionalParameterInDestructuringWithInitializer.ts, 37, 1)) + + readonly bar?: number; +>bar : Symbol(Foo.bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 39, 15)) +} + +function performFoo({ bar }: Foo = {}) { +>performFoo : Symbol(performFoo, Decl(optionalParameterInDestructuringWithInitializer.ts, 41, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 43, 21)) +>Foo : Symbol(Foo, Decl(optionalParameterInDestructuringWithInitializer.ts, 37, 1)) + + useBar(bar); +>useBar : Symbol(useBar, Decl(optionalParameterInDestructuringWithInitializer.ts, 45, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 43, 21)) +} + +function useBar(bar: number) { +>useBar : Symbol(useBar, Decl(optionalParameterInDestructuringWithInitializer.ts, 45, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 47, 16)) + + f(bar, 1) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 47, 16)) +} + +performFoo(); +>performFoo : Symbol(performFoo, Decl(optionalParameterInDestructuringWithInitializer.ts, 41, 1)) + +function performFoo2({ bar = null }: Foo = {}) { +>performFoo2 : Symbol(performFoo2, Decl(optionalParameterInDestructuringWithInitializer.ts, 51, 13)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 53, 22)) +>Foo : Symbol(Foo, Decl(optionalParameterInDestructuringWithInitializer.ts, 37, 1)) + + useBar2(bar); +>useBar2 : Symbol(useBar2, Decl(optionalParameterInDestructuringWithInitializer.ts, 55, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 53, 22)) +} + +function useBar2(bar: number | undefined) { +>useBar2 : Symbol(useBar2, Decl(optionalParameterInDestructuringWithInitializer.ts, 55, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 57, 17)) + + if (bar) { +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 57, 17)) + + f(bar, 1) +>f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 57, 17)) + } +} + +performFoo2(); +>performFoo2 : Symbol(performFoo2, Decl(optionalParameterInDestructuringWithInitializer.ts, 51, 13)) + diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types index 8def19dde20..1d1618e12e3 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types @@ -196,3 +196,69 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, // no error } +interface Foo { +>Foo : Foo + + readonly bar?: number; +>bar : number | undefined +} + +function performFoo({ bar }: Foo = {}) { +>performFoo : ({ bar }?: Foo) => void +>bar : number | undefined +>Foo : Foo +>{} : {} + + useBar(bar); +>useBar(bar) : void +>useBar : (bar: number) => void +>bar : number | undefined +} + +function useBar(bar: number) { +>useBar : (bar: number) => void +>bar : number + + f(bar, 1) +>f(bar, 1) : void +>f : (a: number, b: number) => void +>bar : number +>1 : 1 +} + +performFoo(); +>performFoo() : void +>performFoo : ({ bar }?: Foo) => void + +function performFoo2({ bar = null }: Foo = {}) { +>performFoo2 : ({ bar }?: Foo) => void +>bar : number | null +>null : null +>Foo : Foo +>{} : {} + + useBar2(bar); +>useBar2(bar) : void +>useBar2 : (bar: number | undefined) => void +>bar : number | null +} + +function useBar2(bar: number | undefined) { +>useBar2 : (bar: number | undefined) => void +>bar : number | undefined + + if (bar) { +>bar : number | undefined + + f(bar, 1) +>f(bar, 1) : void +>f : (a: number, b: number) => void +>bar : number +>1 : 1 + } +} + +performFoo2(); +>performFoo2() : void +>performFoo2 : ({ bar }?: Foo) => void + diff --git a/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts b/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts index 3666ee2d954..1f1847a19a7 100644 --- a/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts +++ b/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts @@ -37,3 +37,29 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, f(b, c) // no error } + +interface Foo { + readonly bar?: number; +} + +function performFoo({ bar }: Foo = {}) { + useBar(bar); +} + +function useBar(bar: number) { + f(bar, 1) +} + +performFoo(); + +function performFoo2({ bar = null }: Foo = {}) { + useBar2(bar); +} + +function useBar2(bar: number | undefined) { + if (bar) { + f(bar, 1) + } +} + +performFoo2(); From 588716926dcaa063aecb4d64232e2f3cfd8e23b2 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 22 Jan 2018 10:15:57 -0800 Subject: [PATCH 122/163] Fix bug: result of createUnionOrIntersectionProperty may be undefined (#21332) --- src/compiler/checker.ts | 6 ++++-- tests/cases/fourslash/completionsUnion.ts | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 405fd11f138..14631fa69cb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5995,7 +5995,9 @@ namespace ts { for (const memberType of types) { for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { if (!props.has(escapedName)) { - props.set(escapedName, createUnionOrIntersectionProperty(unionType as UnionType, escapedName)); + const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); + // May be undefined if the property is private + if (prop) props.set(escapedName, prop); } } } @@ -6177,7 +6179,7 @@ namespace ts { t; } - function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol { + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { let props: Symbol[]; const isUnion = containingType.flags & TypeFlags.Union; const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; diff --git a/tests/cases/fourslash/completionsUnion.ts b/tests/cases/fourslash/completionsUnion.ts index ed449936680..d9d1d3c792f 100644 --- a/tests/cases/fourslash/completionsUnion.ts +++ b/tests/cases/fourslash/completionsUnion.ts @@ -2,7 +2,9 @@ ////interface I { x: number; } ////interface Many extends ReadonlyArray { extra: number; } -////const x: I | I[] | Many = { /**/ }; +////class C { private priv: number; } +////const x: I | I[] | Many | C = { /**/ }; // We specifically filter out any array-like types. +// Private members will be excluded by `createUnionOrIntersectionProperty`. verify.completionsAt("", ["x"]); From 146256b7dc2590f067ea8ccd916d1192ef169429 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 17 Jan 2018 15:35:17 -0800 Subject: [PATCH 123/163] Allow dynamic files without external project and also use file names starting with ^ as dynamic file Fixes #21204 --- .../unittests/tsserverProjectSystem.ts | 39 +++++++++++++++++++ src/server/editorServices.ts | 4 +- src/server/scriptInfo.ts | 2 +- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index af69e35ec18..fee21fcdb2d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2846,6 +2846,45 @@ namespace ts.projectSystem { const options = project.getCompilerOptions(); assert.equal(options.outDir, "C:/a/b", ""); }); + + it("dynamic file without external project", () => { + const file: FileOrFolder = { + path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", + content: "var x = 10;" + }; + const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ + module: ModuleKind.CommonJS, + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true + }); + projectService.openClientFile(file.path, "var x = 10;"); + + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + checkProjectRootFiles(project, [file.path]); + checkProjectActualFiles(project, [file.path, libFile.path]); + + assert.strictEqual(projectService.getDefaultProjectForFile(server.toNormalizedPath(file.path), /*ensureProject*/ true), project); + const indexOfX = file.content.indexOf("x"); + assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { + kind: ScriptElementKind.variableElement, + kindModifiers: "", + textSpan: { start: indexOfX, length: 1 }, + displayParts: [ + { text: "var", kind: "keyword" }, + { text: " ", kind: "space" }, + { text: "x", kind: "localName" }, + { text: ":", kind: "punctuation" }, + { text: " ", kind: "space" }, + { text: "number", kind: "keyword" } + ], + documentation: [], + tags: [] + }); + }); }); describe("tsserverProjectSystem Proper errors", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 575e00c5b0e..5d71ff4c2d3 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -898,7 +898,7 @@ namespace ts.server { const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) || this.getOrCreateSingleInferredProjectIfEnabled() || - this.createInferredProject(getDirectoryPath(info.path)); + this.createInferredProject(info.isDynamic ? this.currentDirectory : getDirectoryPath(info.path)); project.addRoot(info); project.updateGraph(); @@ -1655,7 +1655,7 @@ namespace ts.server { } private getOrCreateInferredProjectForProjectRootPathIfEnabled(info: ScriptInfo, projectRootPath: NormalizedPath | undefined): InferredProject | undefined { - if (!this.useInferredProjectPerProjectRoot) { + if (info.isDynamic || !this.useInferredProjectPerProjectRoot) { return undefined; } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index dbadf5c88e7..6fc4f241f65 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -196,7 +196,7 @@ namespace ts.server { /*@internal*/ export function isDynamicFileName(fileName: NormalizedPath) { - return getBaseFileName(fileName)[0] === "^"; + return fileName[0] === "^" || getBaseFileName(fileName)[0] === "^"; } export class ScriptInfo { From d11341820e3aa3d99c918700a113f2819d8c381e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ginth=C3=B6r?= <26004708+Lazarus535@users.noreply.github.com> Date: Mon, 22 Jan 2018 21:45:37 +0100 Subject: [PATCH 124/163] Fixes #17080 Fixed the two requested changes. 1) Deleting the file "pull_request_template.md" 2) Declaring functions in tests, instead of defining --- pull_request_template.md | 17 ------- ...rInDestructuringWithInitializer.errors.txt | 16 +++---- ...ParameterInDestructuringWithInitializer.js | 24 ++-------- ...eterInDestructuringWithInitializer.symbols | 44 +++++++------------ ...ameterInDestructuringWithInitializer.types | 26 ++--------- ...ParameterInDestructuringWithInitializer.ts | 14 ++---- 6 files changed, 32 insertions(+), 109 deletions(-) delete mode 100644 pull_request_template.md diff --git a/pull_request_template.md b/pull_request_template.md deleted file mode 100644 index 4243c2f671f..00000000000 --- a/pull_request_template.md +++ /dev/null @@ -1,17 +0,0 @@ - - -Fixes # -#17080 diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt index 8965d7172e8..b2fea589cb1 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.errors.txt @@ -8,14 +8,14 @@ tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(31,8): e Type 'undefined' is not assignable to type 'number'. tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(45,10): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. Type 'undefined' is not assignable to type 'number'. -tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(55,11): error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number | undefined'. +tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(53,11): error TS2345: Argument of type 'number | null' is not assignable to parameter of type 'number | undefined'. Type 'null' is not assignable to type 'number | undefined'. ==== tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts (6 errors) ==== // https://github.com/Microsoft/TypeScript/issues/17080 - function f(a:number,b:number) { - } + + declare function f(a:number,b:number): void; function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { f(a, b) @@ -75,9 +75,7 @@ tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(55,11): !!! error TS2345: Type 'undefined' is not assignable to type 'number'. } - function useBar(bar: number) { - f(bar, 1) - } + declare function useBar(bar: number): void; performFoo(); @@ -88,11 +86,7 @@ tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts(55,11): !!! error TS2345: Type 'null' is not assignable to type 'number | undefined'. } - function useBar2(bar: number | undefined) { - if (bar) { - f(bar, 1) - } - } + declare function useBar2(bar: number | undefined): void; performFoo2(); \ No newline at end of file diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js index cb929d89112..642f00a4f50 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.js @@ -1,7 +1,7 @@ //// [optionalParameterInDestructuringWithInitializer.ts] // https://github.com/Microsoft/TypeScript/issues/17080 -function f(a:number,b:number) { -} + +declare function f(a:number,b:number): void; function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { f(a, b) @@ -46,9 +46,7 @@ function performFoo({ bar }: Foo = {}) { useBar(bar); } -function useBar(bar: number) { - f(bar, 1) -} +declare function useBar(bar: number): void; performFoo(); @@ -56,19 +54,13 @@ function performFoo2({ bar = null }: Foo = {}) { useBar2(bar); } -function useBar2(bar: number | undefined) { - if (bar) { - f(bar, 1) - } -} +declare function useBar2(bar: number | undefined): void; performFoo2(); //// [optionalParameterInDestructuringWithInitializer.js] // https://github.com/Microsoft/TypeScript/issues/17080 -function f(a, b) { -} function func1(_a) { var _b = _a === void 0 ? { a: 1, b: 2 } : _a, a = _b.a, b = _b.b; f(a, b); @@ -108,17 +100,9 @@ function performFoo(_a) { var bar = (_a === void 0 ? {} : _a).bar; useBar(bar); } -function useBar(bar) { - f(bar, 1); -} performFoo(); function performFoo2(_a) { var _b = (_a === void 0 ? {} : _a).bar, bar = _b === void 0 ? null : _b; useBar2(bar); } -function useBar2(bar) { - if (bar) { - f(bar, 1); - } -} performFoo2(); diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols index 399956476c8..6d4cf484d92 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.symbols @@ -1,13 +1,13 @@ === tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts === // https://github.com/Microsoft/TypeScript/issues/17080 -function f(a:number,b:number) { + +declare function f(a:number,b:number): void; >f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) ->a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 1, 11)) ->b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 1, 20)) -} +>a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 2, 19)) +>b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 2, 28)) function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { ->func1 : Symbol(func1, Decl(optionalParameterInDestructuringWithInitializer.ts, 2, 1)) +>func1 : Symbol(func1, Decl(optionalParameterInDestructuringWithInitializer.ts, 2, 44)) >a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 17)) >b : Symbol(b, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 19)) >a : Symbol(a, Decl(optionalParameterInDestructuringWithInitializer.ts, 4, 25)) @@ -169,41 +169,27 @@ function performFoo({ bar }: Foo = {}) { >bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 43, 21)) } -function useBar(bar: number) { +declare function useBar(bar: number): void; >useBar : Symbol(useBar, Decl(optionalParameterInDestructuringWithInitializer.ts, 45, 1)) ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 47, 16)) - - f(bar, 1) ->f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 47, 16)) -} +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 47, 24)) performFoo(); >performFoo : Symbol(performFoo, Decl(optionalParameterInDestructuringWithInitializer.ts, 41, 1)) function performFoo2({ bar = null }: Foo = {}) { ->performFoo2 : Symbol(performFoo2, Decl(optionalParameterInDestructuringWithInitializer.ts, 51, 13)) ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 53, 22)) +>performFoo2 : Symbol(performFoo2, Decl(optionalParameterInDestructuringWithInitializer.ts, 49, 13)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 51, 22)) >Foo : Symbol(Foo, Decl(optionalParameterInDestructuringWithInitializer.ts, 37, 1)) useBar2(bar); ->useBar2 : Symbol(useBar2, Decl(optionalParameterInDestructuringWithInitializer.ts, 55, 1)) ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 53, 22)) +>useBar2 : Symbol(useBar2, Decl(optionalParameterInDestructuringWithInitializer.ts, 53, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 51, 22)) } -function useBar2(bar: number | undefined) { ->useBar2 : Symbol(useBar2, Decl(optionalParameterInDestructuringWithInitializer.ts, 55, 1)) ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 57, 17)) - - if (bar) { ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 57, 17)) - - f(bar, 1) ->f : Symbol(f, Decl(optionalParameterInDestructuringWithInitializer.ts, 0, 0)) ->bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 57, 17)) - } -} +declare function useBar2(bar: number | undefined): void; +>useBar2 : Symbol(useBar2, Decl(optionalParameterInDestructuringWithInitializer.ts, 53, 1)) +>bar : Symbol(bar, Decl(optionalParameterInDestructuringWithInitializer.ts, 55, 25)) performFoo2(); ->performFoo2 : Symbol(performFoo2, Decl(optionalParameterInDestructuringWithInitializer.ts, 51, 13)) +>performFoo2 : Symbol(performFoo2, Decl(optionalParameterInDestructuringWithInitializer.ts, 49, 13)) diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types index 1d1618e12e3..26110e71051 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types @@ -1,10 +1,10 @@ === tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts === // https://github.com/Microsoft/TypeScript/issues/17080 -function f(a:number,b:number) { + +declare function f(a:number,b:number): void; >f : (a: number, b: number) => void >a : number >b : number -} function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { >func1 : ({ a, b }?: { a: number; b?: number | undefined; }) => void @@ -215,17 +215,10 @@ function performFoo({ bar }: Foo = {}) { >bar : number | undefined } -function useBar(bar: number) { +declare function useBar(bar: number): void; >useBar : (bar: number) => void >bar : number - f(bar, 1) ->f(bar, 1) : void ->f : (a: number, b: number) => void ->bar : number ->1 : 1 -} - performFoo(); >performFoo() : void >performFoo : ({ bar }?: Foo) => void @@ -243,21 +236,10 @@ function performFoo2({ bar = null }: Foo = {}) { >bar : number | null } -function useBar2(bar: number | undefined) { +declare function useBar2(bar: number | undefined): void; >useBar2 : (bar: number | undefined) => void >bar : number | undefined - if (bar) { ->bar : number | undefined - - f(bar, 1) ->f(bar, 1) : void ->f : (a: number, b: number) => void ->bar : number ->1 : 1 - } -} - performFoo2(); >performFoo2() : void >performFoo2 : ({ bar }?: Foo) => void diff --git a/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts b/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts index 1f1847a19a7..d3ebd99efa1 100644 --- a/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts +++ b/tests/cases/compiler/optionalParameterInDestructuringWithInitializer.ts @@ -1,7 +1,7 @@ // @strictNullChecks: true // https://github.com/Microsoft/TypeScript/issues/17080 -function f(a:number,b:number) { -} + +declare function f(a:number,b:number): void; function func1( {a, b}: {a: number, b?: number} = {a: 1, b: 2} ) { f(a, b) @@ -46,9 +46,7 @@ function performFoo({ bar }: Foo = {}) { useBar(bar); } -function useBar(bar: number) { - f(bar, 1) -} +declare function useBar(bar: number): void; performFoo(); @@ -56,10 +54,6 @@ function performFoo2({ bar = null }: Foo = {}) { useBar2(bar); } -function useBar2(bar: number | undefined) { - if (bar) { - f(bar, 1) - } -} +declare function useBar2(bar: number | undefined): void; performFoo2(); From 97fb0fd55f28d9c599f14ad8487b85367bdf7da2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 22 Jan 2018 12:59:53 -0800 Subject: [PATCH 125/163] Add semicolon to unused class member list Turns out SemicolonClassElement is a specific kind for semicolons inside a class. Having one of them with --noUnusedLocals on would crash the compiler after the assert added in #21013. --- src/compiler/checker.ts | 1 + tests/baselines/reference/unusedSemicolonInClass.js | 13 +++++++++++++ .../reference/unusedSemicolonInClass.symbols | 7 +++++++ .../reference/unusedSemicolonInClass.types | 7 +++++++ tests/cases/compiler/unusedSemicolonInClass.ts | 4 ++++ 5 files changed, 32 insertions(+) create mode 100644 tests/baselines/reference/unusedSemicolonInClass.js create mode 100644 tests/baselines/reference/unusedSemicolonInClass.symbols create mode 100644 tests/baselines/reference/unusedSemicolonInClass.types create mode 100644 tests/cases/compiler/unusedSemicolonInClass.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 14631fa69cb..f418dbabcb8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21030,6 +21030,7 @@ namespace ts { } break; case SyntaxKind.IndexSignature: + case SyntaxKind.SemicolonClassElement: // Can't be private break; default: diff --git a/tests/baselines/reference/unusedSemicolonInClass.js b/tests/baselines/reference/unusedSemicolonInClass.js new file mode 100644 index 00000000000..c33f799fec4 --- /dev/null +++ b/tests/baselines/reference/unusedSemicolonInClass.js @@ -0,0 +1,13 @@ +//// [unusedSemicolonInClass.ts] +class Unused { + ; +} + + +//// [unusedSemicolonInClass.js] +var Unused = /** @class */ (function () { + function Unused() { + } + ; + return Unused; +}()); diff --git a/tests/baselines/reference/unusedSemicolonInClass.symbols b/tests/baselines/reference/unusedSemicolonInClass.symbols new file mode 100644 index 00000000000..0e1832d1f3a --- /dev/null +++ b/tests/baselines/reference/unusedSemicolonInClass.symbols @@ -0,0 +1,7 @@ +=== tests/cases/compiler/unusedSemicolonInClass.ts === +class Unused { +>Unused : Symbol(Unused, Decl(unusedSemicolonInClass.ts, 0, 0)) + + ; +} + diff --git a/tests/baselines/reference/unusedSemicolonInClass.types b/tests/baselines/reference/unusedSemicolonInClass.types new file mode 100644 index 00000000000..118259662f4 --- /dev/null +++ b/tests/baselines/reference/unusedSemicolonInClass.types @@ -0,0 +1,7 @@ +=== tests/cases/compiler/unusedSemicolonInClass.ts === +class Unused { +>Unused : Unused + + ; +} + diff --git a/tests/cases/compiler/unusedSemicolonInClass.ts b/tests/cases/compiler/unusedSemicolonInClass.ts new file mode 100644 index 00000000000..5cd62690ba9 --- /dev/null +++ b/tests/cases/compiler/unusedSemicolonInClass.ts @@ -0,0 +1,4 @@ +// @noUnusedLocals: true +class Unused { + ; +} From 7b449a5e6230b5ec9445902a87148a698719baa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Ginth=C3=B6r?= <26004708+Lazarus535@users.noreply.github.com> Date: Mon, 22 Jan 2018 22:09:35 +0100 Subject: [PATCH 126/163] Fixes #17080 Readded untouched pull_request_template.md --- pull_request_template.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pull_request_template.md diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 00000000000..2c49c84641b --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,16 @@ + + +Fixes # From ae652404cdcf9821e7a397d7c16995ef2111baed Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 22 Jan 2018 13:34:12 -0800 Subject: [PATCH 127/163] Fix JSX attribute checking when spreading unions Previously, the code didn't account for the fact that spreading a union creates a union. In fact, before Decemeber, spreading a union in JSX didn't create a union. Now the check for properties of the spread type uses `getPropertiesOfType`, which works with unions, instead of accessing the `properties` property directly. --- src/compiler/checker.ts | 2 +- .../tsxSpreadAttributesResolution17.js | 47 +++++++++++++++++++ .../tsxSpreadAttributesResolution17.symbols | 41 ++++++++++++++++ .../tsxSpreadAttributesResolution17.types | 45 ++++++++++++++++++ .../jsx/tsxSpreadAttributesResolution17.tsx | 25 ++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/tsxSpreadAttributesResolution17.js create mode 100644 tests/baselines/reference/tsxSpreadAttributesResolution17.symbols create mode 100644 tests/baselines/reference/tsxSpreadAttributesResolution17.types create mode 100644 tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f418dbabcb8..f4d8c99c471 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15310,7 +15310,7 @@ namespace ts { // If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type. // but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass. - if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || (sourceAttributesType).properties.length > 0)) { + if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || getPropertiesOfType(sourceAttributesType).length > 0)) { error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(getJsxElementPropertiesName())); } else { diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.js b/tests/baselines/reference/tsxSpreadAttributesResolution17.js new file mode 100644 index 00000000000..81b1f073514 --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.js @@ -0,0 +1,47 @@ +//// [file.tsx] +declare global { + namespace JSX { + interface Element {} + interface ElementAttributesProperty { props: {} } + } +} +declare var React: any; + +export class Empty extends React.Component<{}, {}> { + render() { + return
Hello
; + } +} + +declare const obj: { a: number | undefined } | undefined; + +// OK +let unionedSpread = ; + + +//// [file.jsx] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var Empty = /** @class */ (function (_super) { + __extends(Empty, _super); + function Empty() { + return _super !== null && _super.apply(this, arguments) || this; + } + Empty.prototype.render = function () { + return
Hello
; + }; + return Empty; +}(React.Component)); +exports.Empty = Empty; +// OK +var unionedSpread = ; diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.symbols b/tests/baselines/reference/tsxSpreadAttributesResolution17.symbols new file mode 100644 index 00000000000..6f346ccdcb1 --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.symbols @@ -0,0 +1,41 @@ +=== tests/cases/conformance/jsx/file.tsx === +declare global { +>global : Symbol(global, Decl(file.tsx, 0, 0)) + + namespace JSX { +>JSX : Symbol(JSX, Decl(file.tsx, 0, 16)) + + interface Element {} +>Element : Symbol(Element, Decl(file.tsx, 1, 19)) + + interface ElementAttributesProperty { props: {} } +>ElementAttributesProperty : Symbol(ElementAttributesProperty, Decl(file.tsx, 2, 28)) +>props : Symbol(ElementAttributesProperty.props, Decl(file.tsx, 3, 45)) + } +} +declare var React: any; +>React : Symbol(React, Decl(file.tsx, 6, 11)) + +export class Empty extends React.Component<{}, {}> { +>Empty : Symbol(Empty, Decl(file.tsx, 6, 23)) +>React : Symbol(React, Decl(file.tsx, 6, 11)) + + render() { +>render : Symbol(Empty.render, Decl(file.tsx, 8, 52)) + + return
Hello
; +>div : Symbol(unknown) +>div : Symbol(unknown) + } +} + +declare const obj: { a: number | undefined } | undefined; +>obj : Symbol(obj, Decl(file.tsx, 14, 13)) +>a : Symbol(a, Decl(file.tsx, 14, 20)) + +// OK +let unionedSpread = ; +>unionedSpread : Symbol(unionedSpread, Decl(file.tsx, 17, 3)) +>Empty : Symbol(Empty, Decl(file.tsx, 6, 23)) +>obj : Symbol(obj, Decl(file.tsx, 14, 13)) + diff --git a/tests/baselines/reference/tsxSpreadAttributesResolution17.types b/tests/baselines/reference/tsxSpreadAttributesResolution17.types new file mode 100644 index 00000000000..9477e7366ee --- /dev/null +++ b/tests/baselines/reference/tsxSpreadAttributesResolution17.types @@ -0,0 +1,45 @@ +=== tests/cases/conformance/jsx/file.tsx === +declare global { +>global : any + + namespace JSX { +>JSX : any + + interface Element {} +>Element : Element + + interface ElementAttributesProperty { props: {} } +>ElementAttributesProperty : ElementAttributesProperty +>props : {} + } +} +declare var React: any; +>React : any + +export class Empty extends React.Component<{}, {}> { +>Empty : Empty +>React.Component : any +>React : any +>Component : any + + render() { +>render : () => JSX.Element + + return
Hello
; +>
Hello
: JSX.Element +>div : any +>div : any + } +} + +declare const obj: { a: number | undefined } | undefined; +>obj : { a: number | undefined; } | undefined +>a : number | undefined + +// OK +let unionedSpread = ; +>unionedSpread : JSX.Element +> : JSX.Element +>Empty : typeof Empty +>obj : { a: number | undefined; } | undefined + diff --git a/tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx b/tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx new file mode 100644 index 00000000000..da5638d3e2a --- /dev/null +++ b/tests/cases/conformance/jsx/tsxSpreadAttributesResolution17.tsx @@ -0,0 +1,25 @@ +// @strictNullChecks: true +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @skipLibCheck: true +// @libFiles: lib.d.ts + +declare global { + namespace JSX { + interface Element {} + interface ElementAttributesProperty { props: {} } + } +} +declare var React: any; + +export class Empty extends React.Component<{}, {}> { + render() { + return
Hello
; + } +} + +declare const obj: { a: number | undefined } | undefined; + +// OK +let unionedSpread = ; From a05f669f43d4e52b2fe4e349d9968b61d3090ae4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 22 Jan 2018 16:40:29 -0800 Subject: [PATCH 128/163] Expose sort and deduplicate diagnostics in Public API Fixes #20876 --- src/compiler/core.ts | 8 ++++---- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ae506fd616a..e927bd35382 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -16,6 +16,10 @@ namespace ts { // Update: We also consider a path like `C:\foo.ts` "relative" because we do not search for it in `node_modules` or treat it as an ambient module. return pathIsRelative(moduleName) || isRootedDiskPath(moduleName); } + + export function sortAndDeduplicateDiagnostics(diagnostics: ReadonlyArray): Diagnostic[] { + return sortAndDeduplicate(diagnostics, compareDiagnostics); + } } /* @internal */ @@ -1897,10 +1901,6 @@ namespace ts { return text1 ? Comparison.GreaterThan : Comparison.LessThan; } - export function sortAndDeduplicateDiagnostics(diagnostics: ReadonlyArray): Diagnostic[] { - return sortAndDeduplicate(diagnostics, compareDiagnostics); - } - export function normalizeSlashes(path: string): string { return path.replace(/\\/g, "/"); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index cd1b577b711..8c244f3d4e0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2814,6 +2814,7 @@ declare namespace ts { } declare namespace ts { function isExternalModuleNameRelative(moduleName: string): boolean; + function sortAndDeduplicateDiagnostics(diagnostics: ReadonlyArray): Diagnostic[]; } declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; declare function clearTimeout(handle: any): void; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3cbca96f8fa..2933d11e5cb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2814,6 +2814,7 @@ declare namespace ts { } declare namespace ts { function isExternalModuleNameRelative(moduleName: string): boolean; + function sortAndDeduplicateDiagnostics(diagnostics: ReadonlyArray): Diagnostic[]; } declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; declare function clearTimeout(handle: any): void; From 6cc17f1cd9169ec02eb1d5a0573f3691ab561465 Mon Sep 17 00:00:00 2001 From: csigs Date: Tue, 23 Jan 2018 10:10:51 +0000 Subject: [PATCH 129/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl index b6b35ec094b..7ef4d27d021 100644 --- a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -582,6 +582,15 @@ + + + + + + + + + @@ -3378,15 +3387,6 @@ - - - - - - - - - @@ -3573,6 +3573,15 @@ + + + + + + + + + @@ -6603,11 +6612,11 @@ - + - + - + From 0e46086e7e7323461306bbb1ff4261e2800a283a Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 23 Jan 2018 08:09:15 -0800 Subject: [PATCH 130/163] In getSpecialPropertyExport, add debug failure when symbol parent is not a module (#21347) * In getSpecialPropertyExport, add debug failure when symbol parent is not a module * Fix lint --- src/services/importTracker.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index d2ce802f177..a8f724cb4be 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -510,11 +510,32 @@ namespace ts.FindAllReferences { return undefined; } - const sym = useLhsSymbol ? checker.getSymbolAtLocation((node.left as ts.PropertyAccessExpression).name) : symbol; + const sym = useLhsSymbol ? checker.getSymbolAtLocation(cast(node.left, isPropertyAccessExpression).name) : symbol; + // Better detection for GH#20803 + if (sym && !(checker.getMergedSymbol(sym.parent).flags & SymbolFlags.Module)) { + Debug.fail(`Special property assignment kind does not have a module as its parent. Assignment is ${showSymbol(sym)}, parent is ${showSymbol(sym.parent)}`); + } return sym && exportInfo(sym, kind); } } + function showSymbol(s: Symbol): string { + const decls = s.declarations.map(d => (ts as any).SyntaxKind[d.kind]).join(","); + const flags = showFlags(s.flags, (ts as any).SymbolFlags); + return `{ declarations: ${decls}, flags: ${flags} }`; + } + + function showFlags(f: number, flags: any) { + const out = []; + for (let pow = 0; pow <= 30; pow++) { + const n = 1 << pow; + if (f & n) { + out.push(flags[n]); + } + } + return out.join("|"); + } + function getImport(): ImportedSymbol | undefined { const isImport = isNodeImport(node); if (!isImport) return undefined; From 9dc01bf8bdd4a5c98c14fd1d21c302bc0df47173 Mon Sep 17 00:00:00 2001 From: csigs Date: Tue, 23 Jan 2018 17:10:12 +0000 Subject: [PATCH 131/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- 5 files changed, 105 insertions(+), 60 deletions(-) diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl index a678f69e80e..7ae7fab1b3b 100644 --- a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -573,6 +573,15 @@ + + + + + + + + + @@ -3369,15 +3378,6 @@ - - - - - - - - - @@ -3564,6 +3564,15 @@ + + + + + + + + + @@ -6594,11 +6603,11 @@ - + - + - + diff --git a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl index a58de23e3ff..7cc0cf16633 100644 --- a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -573,6 +573,15 @@ + + + + + + + + + @@ -3369,15 +3378,6 @@ - - - - - - - - - @@ -3564,6 +3564,15 @@ + + + + + + + + + @@ -6594,11 +6603,11 @@ - + - + - + diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index d111d8f84b5..1262a3743cf 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -582,6 +582,15 @@ + + + + + + + + + @@ -3378,15 +3387,6 @@ - - - - - - - - - @@ -3573,6 +3573,15 @@ + + + + + + + + + @@ -6603,11 +6612,11 @@ - + - + - + diff --git a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 1f62aefc8ab..551a0e28806 100644 --- a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -573,6 +573,15 @@ + + + + + + + + + @@ -3369,15 +3378,6 @@ - - - - - - - - - @@ -3564,6 +3564,15 @@ + + + + + + + + + @@ -6594,11 +6603,11 @@ - + - + - + diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl index 2481a1b8752..d9b0532182c 100644 --- a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -573,6 +573,15 @@ + + + + + + + + + @@ -3369,15 +3378,6 @@ - - - - - - - - - @@ -3564,6 +3564,15 @@ + + + + + + + + + @@ -6594,11 +6603,11 @@ - + - + - + From d4b3bd16c4deaec427fbfdc304cdd1b23a36de2a Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 23 Jan 2018 10:57:35 -0800 Subject: [PATCH 132/163] Add KeywordCompletionFilters.TypeKeywords (#21364) --- src/services/completions.ts | 86 ++++++++----------- src/services/utilities.ts | 4 + .../fourslash/completionsTypeKeywords.ts | 7 ++ 3 files changed, 46 insertions(+), 51 deletions(-) create mode 100644 tests/cases/fourslash/completionsTypeKeywords.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 80fdf6c280e..8b7f564ff08 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -20,7 +20,8 @@ namespace ts.Completions { None, ClassElementKeywords, // Keywords at class keyword ConstructorParameterKeywords, // Keywords at constructor parameter - FunctionLikeBodyKeywords // Keywords at function like body + FunctionLikeBodyKeywords, // Keywords at function like body + TypeKeywords, } export function getCompletionsAtPosition( @@ -565,7 +566,7 @@ namespace ts.Completions { } case "none": { // Didn't find a symbol with this name. See if we can find a keyword instead. - if (some(getKeywordCompletions(KeywordCompletionFilters.None), c => c.name === name)) { + if (allKeywordsCompletions().some(c => c.name === name)) { return { name, kind: ScriptElementKind.keyword, @@ -1163,6 +1164,9 @@ namespace ts.Completions { } function filterGlobalCompletion(symbols: Symbol[]): void { + const isTypeCompletion = insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)); + if (isTypeCompletion) keywordFilters = KeywordCompletionFilters.TypeKeywords; + filterMutate(symbols, symbol => { if (!isSourceFile(location)) { // export = /**/ here we want to get all meanings, so any symbol is ok @@ -1170,19 +1174,14 @@ namespace ts.Completions { return true; } - // This is an alias, follow what it aliases - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } + symbol = skipAlias(symbol, typeChecker); // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) if (isInRightSideOfInternalImportEqualsDeclaration(location)) { return !!(symbol.flags & SymbolFlags.Namespace); } - if (insideJsDocTagTypeExpression || - (!isContextTokenValueLocation(contextToken) && - (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) { + if (isTypeCompletion) { // Its a type, but you can reach it by namespace.type as well return symbolCanBeReferencedAtTypeLocation(symbol); } @@ -1199,7 +1198,7 @@ namespace ts.Completions { contextToken.parent.kind === SyntaxKind.TypeQuery; } - function isContextTokenTypeLocation(contextToken: Node) { + function isContextTokenTypeLocation(contextToken: Node): boolean { if (contextToken) { const parentKind = contextToken.parent.kind; switch (contextToken.kind) { @@ -1217,6 +1216,7 @@ namespace ts.Completions { return parentKind === SyntaxKind.AsExpression; } } + return false; } function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { @@ -2130,51 +2130,38 @@ namespace ts.Completions { } // A cache of completion entries for keywords, these do not change between sessions - const _keywordCompletions: CompletionEntry[][] = []; - function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): CompletionEntry[] { - const completions = _keywordCompletions[keywordFilter]; - if (completions) { - return completions; + const _keywordCompletions: ReadonlyArray[] = []; + const allKeywordsCompletions: () => ReadonlyArray = ts.memoize(() => { + const res: CompletionEntry[] = []; + for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { + res.push({ + name: tokenToString(i), + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: "0" + }); } - return _keywordCompletions[keywordFilter] = generateKeywordCompletions(keywordFilter); - - type FilterKeywordCompletions = (entryName: string) => boolean; - function generateKeywordCompletions(keywordFilter: KeywordCompletionFilters): CompletionEntry[] { + return res; + }); + function getKeywordCompletions(keywordFilter: KeywordCompletionFilters): ReadonlyArray { + return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { + const kind = stringToToken(entry.name); switch (keywordFilter) { case KeywordCompletionFilters.None: - return getAllKeywordCompletions(); + // "undefined" is a global variable, so don't need a keyword completion for it. + return kind !== SyntaxKind.UndefinedKeyword; case KeywordCompletionFilters.ClassElementKeywords: - return getFilteredKeywordCompletions(isClassMemberCompletionKeywordText); + return isClassMemberCompletionKeyword(kind); case KeywordCompletionFilters.ConstructorParameterKeywords: - return getFilteredKeywordCompletions(isConstructorParameterCompletionKeywordText); + return isConstructorParameterCompletionKeyword(kind); case KeywordCompletionFilters.FunctionLikeBodyKeywords: - return getFilteredKeywordCompletions(isFunctionLikeBodyCompletionKeywordText); + return isFunctionLikeBodyCompletionKeyword(kind); + case KeywordCompletionFilters.TypeKeywords: + return isTypeKeyword(kind); default: - Debug.assertNever(keywordFilter); + return Debug.assertNever(keywordFilter); } - } - - function getAllKeywordCompletions() { - const allKeywordsCompletions: CompletionEntry[] = []; - for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { - // "undefined" is a global variable, so don't need a keyword completion for it. - if (i === SyntaxKind.UndefinedKeyword) continue; - allKeywordsCompletions.push({ - name: tokenToString(i), - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: "0" - }); - } - return allKeywordsCompletions; - } - - function getFilteredKeywordCompletions(filterFn: FilterKeywordCompletions) { - return filter( - getKeywordCompletions(KeywordCompletionFilters.None), - entry => filterFn(entry.name) - ); - } + })); } function isClassMemberCompletionKeyword(kind: SyntaxKind) { @@ -2222,15 +2209,12 @@ namespace ts.Completions { case SyntaxKind.AbstractKeyword: case SyntaxKind.GetKeyword: case SyntaxKind.SetKeyword: + case SyntaxKind.UndefinedKeyword: return false; } return true; } - function isFunctionLikeBodyCompletionKeywordText(text: string) { - return isFunctionLikeBodyCompletionKeyword(stringToToken(text)); - } - function isEqualityOperatorKind(kind: ts.SyntaxKind): kind is EqualityOperator { switch (kind) { case ts.SyntaxKind.EqualsEqualsEqualsToken: diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9bb35359523..c724fa5643d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1070,12 +1070,16 @@ namespace ts { export const typeKeywords: ReadonlyArray = [ SyntaxKind.AnyKeyword, SyntaxKind.BooleanKeyword, + SyntaxKind.KeyOfKeyword, SyntaxKind.NeverKeyword, + SyntaxKind.NullKeyword, SyntaxKind.NumberKeyword, SyntaxKind.ObjectKeyword, SyntaxKind.StringKeyword, SyntaxKind.SymbolKeyword, SyntaxKind.VoidKeyword, + SyntaxKind.UndefinedKeyword, + SyntaxKind.UniqueKeyword, ]; export function isTypeKeyword(kind: SyntaxKind): boolean { diff --git a/tests/cases/fourslash/completionsTypeKeywords.ts b/tests/cases/fourslash/completionsTypeKeywords.ts new file mode 100644 index 00000000000..198d6dddab4 --- /dev/null +++ b/tests/cases/fourslash/completionsTypeKeywords.ts @@ -0,0 +1,7 @@ +/// + +////type T = /**/ + +goTo.marker(); +verify.completionListContains("undefined", "undefined", undefined, "keyword"); +verify.not.completionListContains("await"); From 4f11dd68abf969b6ad0f7f08185e68e291cd54d5 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 23 Jan 2018 10:39:26 -0800 Subject: [PATCH 133/163] Handle extracting case clause expression as constant --- src/harness/unittests/extractConstants.ts | 7 +++++++ src/services/refactors/extractSymbol.ts | 7 +++++++ .../extractConstant_CaseClauseExpression.js | 13 +++++++++++++ .../extractConstant_CaseClauseExpression.ts | 13 +++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.ts diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts index 71b550cba8f..9dfb1264a70 100644 --- a/src/harness/unittests/extractConstants.ts +++ b/src/harness/unittests/extractConstants.ts @@ -273,6 +273,13 @@ let i: I = [#|{ a: 1 }|]; const myObj: { member(x: number, y: string): void } = { member: [#|(x, y) => x + y|], } +`); + + testExtractConstant("extractConstant_CaseClauseExpression", ` +switch (1) { + case [#|1|]: + break; +} `); }); diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 479c387fc65..f9a46f04914 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1321,6 +1321,13 @@ namespace ts.refactor.extractSymbol { } prevStatement = statement; } + + if (!prevStatement && isCaseClause(curr)) { + // We must have been in the expression of the case clause. + Debug.assert(isSwitchStatement(curr.parent.parent)); + return curr.parent.parent; + } + // There must be at least one statement since we started in one. Debug.assert(prevStatement !== undefined); return prevStatement; diff --git a/tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.js b/tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.js new file mode 100644 index 00000000000..0031ecdf0f9 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.js @@ -0,0 +1,13 @@ +// ==ORIGINAL== + +switch (1) { + case /*[#|*/1/*|]*/: + break; +} + +// ==SCOPE::Extract to constant in enclosing scope== +const newLocal = 1; +switch (1) { + case /*RENAME*/newLocal: + break; +} diff --git a/tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.ts b/tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.ts new file mode 100644 index 00000000000..0031ecdf0f9 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_CaseClauseExpression.ts @@ -0,0 +1,13 @@ +// ==ORIGINAL== + +switch (1) { + case /*[#|*/1/*|]*/: + break; +} + +// ==SCOPE::Extract to constant in enclosing scope== +const newLocal = 1; +switch (1) { + case /*RENAME*/newLocal: + break; +} From 2f3b06a3cd5ca8c394828fee9c0c96f85f54f903 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 23 Jan 2018 11:07:59 -0800 Subject: [PATCH 134/163] Handle extraction ranges including case clause expressions (mostly by rejecting them) Fixes #20559 --- src/harness/unittests/extractRanges.ts | 46 +++++++++++++++++++++++++ src/services/refactors/extractSymbol.ts | 11 ++++++ 2 files changed, 57 insertions(+) diff --git a/src/harness/unittests/extractRanges.ts b/src/harness/unittests/extractRanges.ts index 493c9639c3d..2810f323abf 100644 --- a/src/harness/unittests/extractRanges.ts +++ b/src/harness/unittests/extractRanges.ts @@ -365,6 +365,52 @@ switch (x) { refactor.extractSymbol.Messages.cannotExtractRange.message ]); + testExtractRangeFailed("extractRangeFailed14", + ` + switch(1) { + case [#|1: + break;|] + } + `, + [ + refactor.extractSymbol.Messages.cannotExtractRange.message + ]); + + testExtractRangeFailed("extractRangeFailed15", + ` + switch(1) { + case [#|1: + break|]; + } + `, + [ + refactor.extractSymbol.Messages.cannotExtractRange.message + ]); + + // Documentation only - it would be nice if the result were [$|1|] + testExtractRangeFailed("extractRangeFailed16", + ` + switch(1) { + [#|case 1|]: + break; + } + `, + [ + refactor.extractSymbol.Messages.cannotExtractRange.message + ]); + + // Documentation only - it would be nice if the result were [$|1|] + testExtractRangeFailed("extractRangeFailed17", + ` + switch(1) { + [#|case 1:|] + break; + } + `, + [ + refactor.extractSymbol.Messages.cannotExtractRange.message + ]); + testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); }); } \ No newline at end of file diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index f9a46f04914..e8a2186aa8b 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -235,6 +235,17 @@ namespace ts.refactor.extractSymbol { break; } } + + if (!statements.length) { + // https://github.com/Microsoft/TypeScript/issues/20559 + // Ranges like [|case 1: break;|] will fail to populate `statements` because + // they will never find `start` in `start.parent.statements`. + // Consider: We could support ranges like [|case 1:|] by refining them to just + // the expression. + Debug.assert(isCaseClause(start.parent) && span.start < start.parent.expression.end); + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } + return { targetRange: { range: statements, facts: rangeFacts, declarations } }; } From cd833890778c01260accd0198ea8e7240e00e4df Mon Sep 17 00:00:00 2001 From: csigs Date: Tue, 23 Jan 2018 23:11:17 +0000 Subject: [PATCH 135/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- 4 files changed, 84 insertions(+), 48 deletions(-) diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl index 63f0270005a..7579d5ce794 100644 --- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -573,6 +573,15 @@ + + + + + + + + + @@ -3369,15 +3378,6 @@ - - - - - - - - - @@ -3564,6 +3564,15 @@ + + + + + + + + + @@ -6594,11 +6603,11 @@ - + - + - + diff --git a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 74cf056e95a..400187f844a 100644 --- a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -566,6 +566,15 @@ + + + + + + + + + @@ -3359,15 +3368,6 @@ - - - - - - - - - @@ -3554,6 +3554,15 @@ + + + + + + + + + @@ -6578,11 +6587,11 @@ - + - + - + diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl index ced875a6581..46dce16bc93 100644 --- a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -572,6 +572,15 @@ + + + + + + + + + @@ -3368,15 +3377,6 @@ - - - - - - - - - @@ -3563,6 +3563,15 @@ + + + + + + + + + @@ -6593,11 +6602,11 @@ - + - + - + diff --git a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 3b9a3824b9f..c1894d0db09 100644 --- a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -566,6 +566,15 @@ + + + + + + + + + @@ -3362,15 +3371,6 @@ - - - - - - - - - @@ -3557,6 +3557,15 @@ + + + + + + + + + @@ -6587,11 +6596,11 @@ - + - + - + From d7ed6402a53c1aa432119273e6bb69a5a896c512 Mon Sep 17 00:00:00 2001 From: csigs Date: Wed, 24 Jan 2018 05:10:14 +0000 Subject: [PATCH 136/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 5d1bc276ee8..3ad2bbf63b2 100644 --- a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -582,6 +582,15 @@ + + + + + + + + + @@ -3378,15 +3387,6 @@ - - - - - - - - - @@ -3573,6 +3573,15 @@ + + + + + + + + + @@ -6603,11 +6612,11 @@ - + - + - + diff --git a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl index 4be92ea0534..b6290bed3fb 100644 --- a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -566,6 +566,15 @@ + + + + + + + + + @@ -3359,15 +3368,6 @@ - - - - - - - - - @@ -3554,6 +3554,15 @@ + + + + + + + + + @@ -6578,11 +6587,11 @@ - + - + - + From 5df27c1cd69b54dc3218d0c9e681940966bebc70 Mon Sep 17 00:00:00 2001 From: csigs Date: Wed, 24 Jan 2018 11:10:38 +0000 Subject: [PATCH 137/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl index afee7120010..7fe309d50f8 100644 --- a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -573,6 +573,15 @@ + + + + + + + + + @@ -3366,15 +3375,6 @@ - - - - - - - - - @@ -3561,6 +3561,15 @@ + + + + + + + + + @@ -6585,11 +6594,11 @@ - + - + - + From 77c5529e93ae1d5e4d21bc2bc3e8d573c7d69fa1 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 24 Jan 2018 10:58:41 -0800 Subject: [PATCH 138/163] Make error span for wrong type arguments be just `<...>`,d not `f<...>(...)` (#21390) --- src/compiler/checker.ts | 4 +- src/compiler/utilities.ts | 5 ++ ...signingFromObjectToAnythingElse.errors.txt | 8 +-- ...hIncorrectNumberOfTypeArguments.errors.txt | 56 +++++++++---------- ...enericFunctionWithTypeArguments.errors.txt | 28 +++++----- ...lWithWrongNumberOfTypeArguments.errors.txt | 8 +-- ...torInvocationWithTooFewTypeArgs.errors.txt | 4 +- .../emptyTypeArgumentList.errors.txt | 8 +-- .../emptyTypeArgumentListWithNew.errors.txt | 8 +-- ...kedInsideItsContainingFunction1.errors.txt | 24 ++++---- .../genericDefaultsErrors.errors.txt | 8 +-- .../genericWithOpenTypeParameters1.errors.txt | 8 +-- ...sWithWrongNumberOfTypeArguments.errors.txt | 8 +-- ...NonGenericTypeWithTypeArguments.errors.txt | 12 ++-- ...citTypeParameterAndArgumentType.errors.txt | 4 +- .../reference/overloadResolution.errors.txt | 4 +- ...loadResolutionClassConstructors.errors.txt | 12 ++-- .../overloadResolutionConstructors.errors.txt | 4 +- ...loadsAndTypeArgumentArityErrors.errors.txt | 8 +-- .../parserConstructorAmbiguity3.errors.txt | 6 +- ...CallExpressionWithTypeArguments.errors.txt | 4 +- .../tooManyTypeParameters1.errors.txt | 12 ++-- ...OnFunctionsWithNoTypeParameters.errors.txt | 8 +-- .../reference/typeAssertions.errors.txt | 4 +- ...unctionCallsWithTypeParameters1.errors.txt | 16 +++--- 25 files changed, 138 insertions(+), 133 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 01b3a047d1d..bb1fbbdd526 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16717,7 +16717,7 @@ namespace ts { const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); - let typeArguments: ReadonlyArray; + let typeArguments: NodeArray; if (!isTaggedTemplate && !isDecorator && !isJsxOpeningOrSelfClosingElement) { typeArguments = (node).typeArguments; @@ -16846,7 +16846,7 @@ namespace ts { max = Math.max(max, length(sig.typeParameters)); } const paramCount = min < max ? min + "-" + max : min; - diagnostics.add(createDiagnosticForNode(node, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length)); + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length)); } else if (args) { let min = Number.POSITIVE_INFINITY; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bf6a086967c..f4ddf1d8319 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -599,6 +599,11 @@ namespace ts { return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2, arg3); } + export function createDiagnosticForNodeArray(sourceFile: SourceFile, nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const start = skipTrivia(sourceFile.text, nodes.pos); + return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2, arg3); + } + export function createDiagnosticForNodeInSourceFile(sourceFile: SourceFile, node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { const span = getErrorSpanForNode(sourceFile, node); return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2, arg3); diff --git a/tests/baselines/reference/assigningFromObjectToAnythingElse.errors.txt b/tests/baselines/reference/assigningFromObjectToAnythingElse.errors.txt index c8fb57e3616..bc92c376c7c 100644 --- a/tests/baselines/reference/assigningFromObjectToAnythingElse.errors.txt +++ b/tests/baselines/reference/assigningFromObjectToAnythingElse.errors.txt @@ -1,8 +1,8 @@ tests/cases/compiler/assigningFromObjectToAnythingElse.ts(3,1): error TS2322: Type 'Object' is not assignable to type 'RegExp'. The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead? Property 'exec' is missing in type 'Object'. -tests/cases/compiler/assigningFromObjectToAnythingElse.ts(5,17): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/assigningFromObjectToAnythingElse.ts(6,17): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/assigningFromObjectToAnythingElse.ts(5,31): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/assigningFromObjectToAnythingElse.ts(6,31): error TS2558: Expected 0 type arguments, but got 1. tests/cases/compiler/assigningFromObjectToAnythingElse.ts(8,5): error TS2322: Type 'Object' is not assignable to type 'Error'. The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead? Property 'name' is missing in type 'Object'. @@ -18,10 +18,10 @@ tests/cases/compiler/assigningFromObjectToAnythingElse.ts(8,5): error TS2322: Ty !!! error TS2322: Property 'exec' is missing in type 'Object'. var a: String = Object.create(""); - ~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var c: String = Object.create(1); - ~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var w: Error = new Object(); diff --git a/tests/baselines/reference/callGenericFunctionWithIncorrectNumberOfTypeArguments.errors.txt b/tests/baselines/reference/callGenericFunctionWithIncorrectNumberOfTypeArguments.errors.txt index 6d95b588ebf..d5809d677e4 100644 --- a/tests/baselines/reference/callGenericFunctionWithIncorrectNumberOfTypeArguments.errors.txt +++ b/tests/baselines/reference/callGenericFunctionWithIncorrectNumberOfTypeArguments.errors.txt @@ -1,17 +1,17 @@ -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(5,10): error TS2558: Expected 2 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(6,11): error TS2558: Expected 2 type arguments, but got 3. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(9,10): error TS2558: Expected 2 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(10,11): error TS2558: Expected 2 type arguments, but got 3. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(13,10): error TS2558: Expected 2 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(14,11): error TS2558: Expected 2 type arguments, but got 3. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(21,10): error TS2558: Expected 2 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(22,11): error TS2558: Expected 2 type arguments, but got 3. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(28,10): error TS2558: Expected 2 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(29,11): error TS2558: Expected 2 type arguments, but got 3. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(36,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(37,11): error TS2558: Expected 0 type arguments, but got 3. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(43,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(44,11): error TS2558: Expected 0 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(5,12): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(6,13): error TS2558: Expected 2 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(9,13): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(10,14): error TS2558: Expected 2 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(13,13): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(14,14): error TS2558: Expected 2 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(21,22): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(22,23): error TS2558: Expected 2 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(28,14): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(29,15): error TS2558: Expected 2 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(36,23): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(37,24): error TS2558: Expected 0 type arguments, but got 3. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(43,15): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts(44,16): error TS2558: Expected 0 type arguments, but got 3. ==== tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFunctionWithIncorrectNumberOfTypeArguments.ts (14 errors) ==== @@ -20,26 +20,26 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFuncti function f(x: T, y: U): T { return null; } var r1 = f(1, ''); - ~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. var r1b = f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 3. var f2 = (x: T, y: U): T => { return null; } var r2 = f2(1, ''); - ~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. var r2b = f2(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 3. var f3: { (x: T, y: U): T; } var r3 = f3(1, ''); - ~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. var r3b = f3(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 3. class C { @@ -48,10 +48,10 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFuncti } } var r4 = (new C()).f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. var r4b = (new C()).f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 3. interface I { @@ -59,10 +59,10 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFuncti } var i: I; var r5 = i.f(1, ''); - ~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. var r5b = i.f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 3. class C2 { @@ -71,10 +71,10 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFuncti } } var r6 = (new C2()).f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r6b = (new C2()).f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 3. interface I2 { @@ -82,8 +82,8 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callGenericFuncti } var i2: I2; var r7 = i2.f(1, ''); - ~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r7b = i2.f(1, ''); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 3. \ No newline at end of file diff --git a/tests/baselines/reference/callNonGenericFunctionWithTypeArguments.errors.txt b/tests/baselines/reference/callNonGenericFunctionWithTypeArguments.errors.txt index b014eae0fd8..6bfb0c1113e 100644 --- a/tests/baselines/reference/callNonGenericFunctionWithTypeArguments.errors.txt +++ b/tests/baselines/reference/callNonGenericFunctionWithTypeArguments.errors.txt @@ -1,10 +1,10 @@ -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(5,9): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(8,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(11,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(18,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(24,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(31,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(37,10): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(5,11): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(8,13): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(11,13): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(18,22): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(24,14): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(31,23): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(37,15): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(40,10): error TS2347: Untyped function calls may not accept type arguments. tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFunctionWithTypeArguments.ts(43,10): error TS2347: Untyped function calls may not accept type arguments. @@ -15,17 +15,17 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFun function f(x: number) { return null; } var r = f(1); - ~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var f2 = (x: number) => { return null; } var r2 = f2(1); - ~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var f3: { (x: number): any; } var r3 = f3(1); - ~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. class C { @@ -34,7 +34,7 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFun } } var r4 = (new C()).f(1); - ~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. interface I { @@ -42,7 +42,7 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFun } var i: I; var r5 = i.f(1); - ~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. class C2 { @@ -51,7 +51,7 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFun } } var r6 = (new C2()).f(1); - ~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. interface I2 { @@ -59,7 +59,7 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/callNonGenericFun } var i2: I2; var r7 = i2.f(1); - ~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var a; diff --git a/tests/baselines/reference/callWithWrongNumberOfTypeArguments.errors.txt b/tests/baselines/reference/callWithWrongNumberOfTypeArguments.errors.txt index e3fca9dff9c..c8553e8fc0d 100644 --- a/tests/baselines/reference/callWithWrongNumberOfTypeArguments.errors.txt +++ b/tests/baselines/reference/callWithWrongNumberOfTypeArguments.errors.txt @@ -1,14 +1,14 @@ -tests/cases/compiler/callWithWrongNumberOfTypeArguments.ts(3,1): error TS2558: Expected 2 type arguments, but got 1. -tests/cases/compiler/callWithWrongNumberOfTypeArguments.ts(5,1): error TS2558: Expected 2 type arguments, but got 3. +tests/cases/compiler/callWithWrongNumberOfTypeArguments.ts(3,3): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/compiler/callWithWrongNumberOfTypeArguments.ts(5,3): error TS2558: Expected 2 type arguments, but got 3. ==== tests/cases/compiler/callWithWrongNumberOfTypeArguments.ts (2 errors) ==== function f() { } f(); - ~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. f(); f(); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 3. \ No newline at end of file diff --git a/tests/baselines/reference/constructorInvocationWithTooFewTypeArgs.errors.txt b/tests/baselines/reference/constructorInvocationWithTooFewTypeArgs.errors.txt index 265ce4686db..10dd4ad82fb 100644 --- a/tests/baselines/reference/constructorInvocationWithTooFewTypeArgs.errors.txt +++ b/tests/baselines/reference/constructorInvocationWithTooFewTypeArgs.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/constructorInvocationWithTooFewTypeArgs.ts(9,9): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/compiler/constructorInvocationWithTooFewTypeArgs.ts(9,15): error TS2558: Expected 2 type arguments, but got 1. ==== tests/cases/compiler/constructorInvocationWithTooFewTypeArgs.ts (1 errors) ==== @@ -11,6 +11,6 @@ tests/cases/compiler/constructorInvocationWithTooFewTypeArgs.ts(9,9): error TS25 } var d = new D(); - ~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. \ No newline at end of file diff --git a/tests/baselines/reference/emptyTypeArgumentList.errors.txt b/tests/baselines/reference/emptyTypeArgumentList.errors.txt index 036f307447b..395c990a3d9 100644 --- a/tests/baselines/reference/emptyTypeArgumentList.errors.txt +++ b/tests/baselines/reference/emptyTypeArgumentList.errors.txt @@ -1,11 +1,11 @@ -tests/cases/compiler/emptyTypeArgumentList.ts(2,1): error TS2558: Expected 1 type arguments, but got 0. tests/cases/compiler/emptyTypeArgumentList.ts(2,4): error TS1099: Type argument list cannot be empty. +tests/cases/compiler/emptyTypeArgumentList.ts(2,5): error TS2558: Expected 1 type arguments, but got 0. ==== tests/cases/compiler/emptyTypeArgumentList.ts (2 errors) ==== function foo() { } foo<>(); - ~~~~~~~ -!!! error TS2558: Expected 1 type arguments, but got 0. ~~ -!!! error TS1099: Type argument list cannot be empty. \ No newline at end of file +!!! error TS1099: Type argument list cannot be empty. + +!!! error TS2558: Expected 1 type arguments, but got 0. \ No newline at end of file diff --git a/tests/baselines/reference/emptyTypeArgumentListWithNew.errors.txt b/tests/baselines/reference/emptyTypeArgumentListWithNew.errors.txt index 648aa7c6c50..92eb250291a 100644 --- a/tests/baselines/reference/emptyTypeArgumentListWithNew.errors.txt +++ b/tests/baselines/reference/emptyTypeArgumentListWithNew.errors.txt @@ -1,11 +1,11 @@ -tests/cases/compiler/emptyTypeArgumentListWithNew.ts(2,1): error TS2558: Expected 1 type arguments, but got 0. tests/cases/compiler/emptyTypeArgumentListWithNew.ts(2,8): error TS1099: Type argument list cannot be empty. +tests/cases/compiler/emptyTypeArgumentListWithNew.ts(2,9): error TS2558: Expected 1 type arguments, but got 0. ==== tests/cases/compiler/emptyTypeArgumentListWithNew.ts (2 errors) ==== class foo { } new foo<>(); - ~~~~~~~~~~~ -!!! error TS2558: Expected 1 type arguments, but got 0. ~~ -!!! error TS1099: Type argument list cannot be empty. \ No newline at end of file +!!! error TS1099: Type argument list cannot be empty. + +!!! error TS2558: Expected 1 type arguments, but got 0. \ No newline at end of file diff --git a/tests/baselines/reference/genericCallbackInvokedInsideItsContainingFunction1.errors.txt b/tests/baselines/reference/genericCallbackInvokedInsideItsContainingFunction1.errors.txt index 7ff051bd63e..ff4c2235b49 100644 --- a/tests/baselines/reference/genericCallbackInvokedInsideItsContainingFunction1.errors.txt +++ b/tests/baselines/reference/genericCallbackInvokedInsideItsContainingFunction1.errors.txt @@ -1,32 +1,32 @@ -tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(2,14): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(2,16): error TS2558: Expected 0 type arguments, but got 1. tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(3,16): error TS2345: Argument of type '1' is not assignable to parameter of type 'T'. -tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(4,14): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(8,15): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(9,15): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(4,16): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(8,17): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(9,17): error TS2558: Expected 0 type arguments, but got 1. tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(12,17): error TS2345: Argument of type 'U' is not assignable to parameter of type 'T'. -tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(13,15): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(14,15): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(13,17): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(14,17): error TS2558: Expected 0 type arguments, but got 1. ==== tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts (8 errors) ==== function foo(x:T, y:U, f: (v: T) => U) { var r1 = f(1); - ~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r2 = f(1); ~ !!! error TS2345: Argument of type '1' is not assignable to parameter of type 'T'. var r3 = f(null); - ~~~~~~~~~~~~ + ~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r4 = f(null); var r11 = f(x); var r21 = f(x); - ~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r31 = f(null); - ~~~~~~~~~~~~ + ~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r41 = f(null); @@ -34,10 +34,10 @@ tests/cases/compiler/genericCallbackInvokedInsideItsContainingFunction1.ts(14,15 ~ !!! error TS2345: Argument of type 'U' is not assignable to parameter of type 'T'. var r22 = f(y); - ~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r32 = f(null); - ~~~~~~~~~~~~ + ~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r42 = f(null); } \ No newline at end of file diff --git a/tests/baselines/reference/genericDefaultsErrors.errors.txt b/tests/baselines/reference/genericDefaultsErrors.errors.txt index b1afd6f173b..e1dca77d958 100644 --- a/tests/baselines/reference/genericDefaultsErrors.errors.txt +++ b/tests/baselines/reference/genericDefaultsErrors.errors.txt @@ -3,8 +3,8 @@ tests/cases/compiler/genericDefaultsErrors.ts(4,59): error TS2344: Type 'T' does Type 'string' is not assignable to type 'number'. tests/cases/compiler/genericDefaultsErrors.ts(5,44): error TS2344: Type 'T' does not satisfy the constraint 'number'. tests/cases/compiler/genericDefaultsErrors.ts(6,39): error TS2344: Type 'number' does not satisfy the constraint 'T'. -tests/cases/compiler/genericDefaultsErrors.ts(10,1): error TS2558: Expected 2-3 type arguments, but got 1. -tests/cases/compiler/genericDefaultsErrors.ts(13,1): error TS2558: Expected 2-3 type arguments, but got 4. +tests/cases/compiler/genericDefaultsErrors.ts(10,5): error TS2558: Expected 2-3 type arguments, but got 1. +tests/cases/compiler/genericDefaultsErrors.ts(13,5): error TS2558: Expected 2-3 type arguments, but got 4. tests/cases/compiler/genericDefaultsErrors.ts(17,13): error TS2345: Argument of type '"a"' is not assignable to parameter of type 'number'. tests/cases/compiler/genericDefaultsErrors.ts(19,11): error TS2428: All declarations of 'i00' must have identical type parameters. tests/cases/compiler/genericDefaultsErrors.ts(20,11): error TS2428: All declarations of 'i00' must have identical type parameters. @@ -44,12 +44,12 @@ tests/cases/compiler/genericDefaultsErrors.ts(42,29): error TS2716: Type paramet declare function f11(): void; f11(); // ok f11<1>(); // error - ~~~~~~~~ + ~ !!! error TS2558: Expected 2-3 type arguments, but got 1. f11<1, 2>(); // ok f11<1, 2, 3>(); // ok f11<1, 2, 3, 4>(); // error - ~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~ !!! error TS2558: Expected 2-3 type arguments, but got 4. declare function f12(a?: U): void; diff --git a/tests/baselines/reference/genericWithOpenTypeParameters1.errors.txt b/tests/baselines/reference/genericWithOpenTypeParameters1.errors.txt index 9f8694d2fca..6a243650b1e 100644 --- a/tests/baselines/reference/genericWithOpenTypeParameters1.errors.txt +++ b/tests/baselines/reference/genericWithOpenTypeParameters1.errors.txt @@ -1,6 +1,6 @@ tests/cases/compiler/genericWithOpenTypeParameters1.ts(7,40): error TS2345: Argument of type '1' is not assignable to parameter of type 'T'. -tests/cases/compiler/genericWithOpenTypeParameters1.ts(8,35): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/genericWithOpenTypeParameters1.ts(9,35): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericWithOpenTypeParameters1.ts(8,41): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/genericWithOpenTypeParameters1.ts(9,41): error TS2558: Expected 0 type arguments, but got 1. ==== tests/cases/compiler/genericWithOpenTypeParameters1.ts (3 errors) ==== @@ -14,10 +14,10 @@ tests/cases/compiler/genericWithOpenTypeParameters1.ts(9,35): error TS2558: Expe ~ !!! error TS2345: Argument of type '1' is not assignable to parameter of type 'T'. var f2 = (x: B) => { return x.foo(1); } // error - ~~~~~~~~~~~ + ~ !!! error TS2558: Expected 0 type arguments, but got 1. var f3 = (x: B) => { return x.foo(1); } // error - ~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var f4 = (x: B) => { return x.foo(1); } // no error \ No newline at end of file diff --git a/tests/baselines/reference/instantiateGenericClassWithWrongNumberOfTypeArguments.errors.txt b/tests/baselines/reference/instantiateGenericClassWithWrongNumberOfTypeArguments.errors.txt index 05cc1ee1cd0..a09b424b8e1 100644 --- a/tests/baselines/reference/instantiateGenericClassWithWrongNumberOfTypeArguments.errors.txt +++ b/tests/baselines/reference/instantiateGenericClassWithWrongNumberOfTypeArguments.errors.txt @@ -1,5 +1,5 @@ -tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGenericClassWithWrongNumberOfTypeArguments.ts(8,9): error TS2558: Expected 1 type arguments, but got 2. -tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGenericClassWithWrongNumberOfTypeArguments.ts(16,9): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGenericClassWithWrongNumberOfTypeArguments.ts(8,15): error TS2558: Expected 1 type arguments, but got 2. +tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGenericClassWithWrongNumberOfTypeArguments.ts(16,15): error TS2558: Expected 2 type arguments, but got 1. ==== tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGenericClassWithWrongNumberOfTypeArguments.ts (2 errors) ==== @@ -11,7 +11,7 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGeneri } var c = new C(); - ~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~ !!! error TS2558: Expected 1 type arguments, but got 2. class D { @@ -21,5 +21,5 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateGeneri // BUG 794238 var d = new D(); - ~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. \ No newline at end of file diff --git a/tests/baselines/reference/instantiateNonGenericTypeWithTypeArguments.errors.txt b/tests/baselines/reference/instantiateNonGenericTypeWithTypeArguments.errors.txt index 196af18aa9a..5e70aa31249 100644 --- a/tests/baselines/reference/instantiateNonGenericTypeWithTypeArguments.errors.txt +++ b/tests/baselines/reference/instantiateNonGenericTypeWithTypeArguments.errors.txt @@ -1,8 +1,8 @@ -tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(8,9): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(8,15): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(11,9): error TS2350: Only a void function can be called with the 'new' keyword. -tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(11,9): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(11,17): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(14,10): error TS2350: Only a void function can be called with the 'new' keyword. -tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(14,10): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(14,16): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGenericTypeWithTypeArguments.ts(18,10): error TS2347: Untyped function calls may not accept type arguments. @@ -15,21 +15,21 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/instantiateNonGen } var c = new C(); - ~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. function Foo(): void { } var r = new Foo(); ~~~~~~~~~~~~~~~~~ !!! error TS2350: Only a void function can be called with the 'new' keyword. - ~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var f: { (): void }; var r2 = new f(); ~~~~~~~~~~~~~~~ !!! error TS2350: Only a void function can be called with the 'new' keyword. - ~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var a: any; diff --git a/tests/baselines/reference/mismatchedExplicitTypeParameterAndArgumentType.errors.txt b/tests/baselines/reference/mismatchedExplicitTypeParameterAndArgumentType.errors.txt index 9b95b143d64..f96dc69ee33 100644 --- a/tests/baselines/reference/mismatchedExplicitTypeParameterAndArgumentType.errors.txt +++ b/tests/baselines/reference/mismatchedExplicitTypeParameterAndArgumentType.errors.txt @@ -1,7 +1,7 @@ tests/cases/compiler/mismatchedExplicitTypeParameterAndArgumentType.ts(10,30): error TS2345: Argument of type '(string | number)[]' is not assignable to parameter of type 'number[]'. Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. -tests/cases/compiler/mismatchedExplicitTypeParameterAndArgumentType.ts(11,11): error TS2558: Expected 2 type arguments, but got 1. +tests/cases/compiler/mismatchedExplicitTypeParameterAndArgumentType.ts(11,15): error TS2558: Expected 2 type arguments, but got 1. ==== tests/cases/compiler/mismatchedExplicitTypeParameterAndArgumentType.ts (2 errors) ==== @@ -20,6 +20,6 @@ tests/cases/compiler/mismatchedExplicitTypeParameterAndArgumentType.ts(11,11): e !!! error TS2345: Type 'string | number' is not assignable to type 'number'. !!! error TS2345: Type 'string' is not assignable to type 'number'. var r7b = map([1, ""], (x) => x.toString()); // error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 2 type arguments, but got 1. var r8 = map([1, ""], (x) => x.toString()); \ No newline at end of file diff --git a/tests/baselines/reference/overloadResolution.errors.txt b/tests/baselines/reference/overloadResolution.errors.txt index 37cea935e66..6710f697b3c 100644 --- a/tests/baselines/reference/overloadResolution.errors.txt +++ b/tests/baselines/reference/overloadResolution.errors.txt @@ -1,6 +1,6 @@ tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(27,5): error TS2345: Argument of type '{}' is not assignable to parameter of type 'number'. tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(41,11): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. -tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(63,1): error TS2558: Expected 1-3 type arguments, but got 4. +tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(63,5): error TS2558: Expected 1-3 type arguments, but got 4. tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(70,21): error TS2345: Argument of type '3' is not assignable to parameter of type 'string'. tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(71,21): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(81,5): error TS2344: Type 'boolean' does not satisfy the constraint 'number'. @@ -78,7 +78,7 @@ tests/cases/conformance/expressions/functionCalls/overloadResolution.ts(91,22): // Generic overloads with differing arity called with type argument count that doesn't match any overload fn3(); // Error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 1-3 type arguments, but got 4. // Generic overloads with constraints called with type arguments that satisfy the constraints diff --git a/tests/baselines/reference/overloadResolutionClassConstructors.errors.txt b/tests/baselines/reference/overloadResolutionClassConstructors.errors.txt index 92f3a7cfa90..cfbc2605559 100644 --- a/tests/baselines/reference/overloadResolutionClassConstructors.errors.txt +++ b/tests/baselines/reference/overloadResolutionClassConstructors.errors.txt @@ -1,7 +1,7 @@ tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(27,9): error TS2345: Argument of type '{}' is not assignable to parameter of type 'number'. -tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(60,1): error TS2558: Expected 3 type arguments, but got 1. -tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(61,1): error TS2558: Expected 3 type arguments, but got 2. -tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(65,1): error TS2558: Expected 3 type arguments, but got 4. +tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(60,9): error TS2558: Expected 3 type arguments, but got 1. +tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(61,9): error TS2558: Expected 3 type arguments, but got 2. +tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(65,9): error TS2558: Expected 3 type arguments, but got 4. tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(73,25): error TS2345: Argument of type '3' is not assignable to parameter of type 'string'. tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(74,9): error TS2344: Type 'number' does not satisfy the constraint 'string'. tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstructors.ts(75,9): error TS2344: Type 'number' does not satisfy the constraint 'string'. @@ -77,16 +77,16 @@ tests/cases/conformance/expressions/functionCalls/overloadResolutionClassConstru // Generic overloads with differing arity called with type arguments matching each overload type parameter count new fn3(4); // Error - ~~~~~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 3 type arguments, but got 1. new fn3('', '', ''); // Error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~ !!! error TS2558: Expected 3 type arguments, but got 2. new fn3('', '', 3); // Generic overloads with differing arity called with type argument count that doesn't match any overload new fn3(); // Error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 3 type arguments, but got 4. // Generic overloads with constraints called with type arguments that satisfy the constraints diff --git a/tests/baselines/reference/overloadResolutionConstructors.errors.txt b/tests/baselines/reference/overloadResolutionConstructors.errors.txt index 9b09992228e..fe1c955651b 100644 --- a/tests/baselines/reference/overloadResolutionConstructors.errors.txt +++ b/tests/baselines/reference/overloadResolutionConstructors.errors.txt @@ -1,6 +1,6 @@ tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(27,9): error TS2345: Argument of type '{}' is not assignable to parameter of type 'number'. tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(43,15): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. -tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(67,1): error TS2558: Expected 1-3 type arguments, but got 4. +tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(67,9): error TS2558: Expected 1-3 type arguments, but got 4. tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(77,25): error TS2345: Argument of type '3' is not assignable to parameter of type 'string'. tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(78,25): error TS2345: Argument of type '""' is not assignable to parameter of type 'number'. tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors.ts(88,9): error TS2344: Type 'boolean' does not satisfy the constraint 'number'. @@ -82,7 +82,7 @@ tests/cases/conformance/expressions/functionCalls/overloadResolutionConstructors // Generic overloads with differing arity called with type argument count that doesn't match any overload new fn3(); // Error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 1-3 type arguments, but got 4. // Generic overloads with constraints called with type arguments that satisfy the constraints diff --git a/tests/baselines/reference/overloadsAndTypeArgumentArityErrors.errors.txt b/tests/baselines/reference/overloadsAndTypeArgumentArityErrors.errors.txt index 52bcaaf790b..45a5616d188 100644 --- a/tests/baselines/reference/overloadsAndTypeArgumentArityErrors.errors.txt +++ b/tests/baselines/reference/overloadsAndTypeArgumentArityErrors.errors.txt @@ -1,6 +1,6 @@ -tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts(5,1): error TS2558: Expected 0-2 type arguments, but got 3. +tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts(5,11): error TS2558: Expected 0-2 type arguments, but got 3. tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts(6,1): error TS2350: Only a void function can be called with the 'new' keyword. -tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts(6,1): error TS2558: Expected 0-2 type arguments, but got 3. +tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts(6,15): error TS2558: Expected 0-2 type arguments, but got 3. ==== tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts (3 errors) ==== @@ -9,10 +9,10 @@ tests/cases/compiler/overloadsAndTypeArgumentArityErrors.ts(6,1): error TS2558: declare function Callbacks(flags?: string): void; Callbacks('s'); // wrong number of type arguments - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 0-2 type arguments, but got 3. new Callbacks('s'); // wrong number of type arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2350: Only a void function can be called with the 'new' keyword. - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2558: Expected 0-2 type arguments, but got 3. \ No newline at end of file diff --git a/tests/baselines/reference/parserConstructorAmbiguity3.errors.txt b/tests/baselines/reference/parserConstructorAmbiguity3.errors.txt index c13916fa39d..cd58ed117f6 100644 --- a/tests/baselines/reference/parserConstructorAmbiguity3.errors.txt +++ b/tests/baselines/reference/parserConstructorAmbiguity3.errors.txt @@ -1,13 +1,13 @@ -tests/cases/conformance/parser/ecmascript5/Generics/parserConstructorAmbiguity3.ts(1,1): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/parser/ecmascript5/Generics/parserConstructorAmbiguity3.ts(1,10): error TS2304: Cannot find name 'A'. +tests/cases/conformance/parser/ecmascript5/Generics/parserConstructorAmbiguity3.ts(1,10): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/parser/ecmascript5/Generics/parserConstructorAmbiguity3.ts(1,12): error TS1005: '(' expected. ==== tests/cases/conformance/parser/ecmascript5/Generics/parserConstructorAmbiguity3.ts (3 errors) ==== new Date - ~~~~~~~~~~~ -!!! error TS2558: Expected 0 type arguments, but got 1. ~ !!! error TS2304: Cannot find name 'A'. + ~ +!!! error TS2558: Expected 0 type arguments, but got 1. !!! error TS1005: '(' expected. \ No newline at end of file diff --git a/tests/baselines/reference/thisExpressionInCallExpressionWithTypeArguments.errors.txt b/tests/baselines/reference/thisExpressionInCallExpressionWithTypeArguments.errors.txt index 1dac3478c63..22f4632cef8 100644 --- a/tests/baselines/reference/thisExpressionInCallExpressionWithTypeArguments.errors.txt +++ b/tests/baselines/reference/thisExpressionInCallExpressionWithTypeArguments.errors.txt @@ -1,10 +1,10 @@ -tests/cases/compiler/thisExpressionInCallExpressionWithTypeArguments.ts(2,20): error TS2558: Expected 1 type arguments, but got 2. +tests/cases/compiler/thisExpressionInCallExpressionWithTypeArguments.ts(2,32): error TS2558: Expected 1 type arguments, but got 2. ==== tests/cases/compiler/thisExpressionInCallExpressionWithTypeArguments.ts (1 errors) ==== class C { public foo() { [1,2,3].map((x) => { return this; })} - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~ !!! error TS2558: Expected 1 type arguments, but got 2. } \ No newline at end of file diff --git a/tests/baselines/reference/tooManyTypeParameters1.errors.txt b/tests/baselines/reference/tooManyTypeParameters1.errors.txt index 7997e68b575..b966225bef5 100644 --- a/tests/baselines/reference/tooManyTypeParameters1.errors.txt +++ b/tests/baselines/reference/tooManyTypeParameters1.errors.txt @@ -1,23 +1,23 @@ -tests/cases/compiler/tooManyTypeParameters1.ts(2,1): error TS2558: Expected 1 type arguments, but got 2. -tests/cases/compiler/tooManyTypeParameters1.ts(5,1): error TS2558: Expected 1 type arguments, but got 2. -tests/cases/compiler/tooManyTypeParameters1.ts(8,9): error TS2558: Expected 1 type arguments, but got 2. +tests/cases/compiler/tooManyTypeParameters1.ts(2,3): error TS2558: Expected 1 type arguments, but got 2. +tests/cases/compiler/tooManyTypeParameters1.ts(5,3): error TS2558: Expected 1 type arguments, but got 2. +tests/cases/compiler/tooManyTypeParameters1.ts(8,15): error TS2558: Expected 1 type arguments, but got 2. tests/cases/compiler/tooManyTypeParameters1.ts(11,8): error TS2314: Generic type 'I' requires 1 type argument(s). ==== tests/cases/compiler/tooManyTypeParameters1.ts (4 errors) ==== function f() { } f(); - ~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~ !!! error TS2558: Expected 1 type arguments, but got 2. var x = () => {}; x(); - ~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~ !!! error TS2558: Expected 1 type arguments, but got 2. class C {} var c = new C(); - ~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~ !!! error TS2558: Expected 1 type arguments, but got 2. interface I {} diff --git a/tests/baselines/reference/typeArgumentsOnFunctionsWithNoTypeParameters.errors.txt b/tests/baselines/reference/typeArgumentsOnFunctionsWithNoTypeParameters.errors.txt index 5c916331950..a162b997c0f 100644 --- a/tests/baselines/reference/typeArgumentsOnFunctionsWithNoTypeParameters.errors.txt +++ b/tests/baselines/reference/typeArgumentsOnFunctionsWithNoTypeParameters.errors.txt @@ -1,18 +1,18 @@ -tests/cases/compiler/typeArgumentsOnFunctionsWithNoTypeParameters.ts(2,13): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/typeArgumentsOnFunctionsWithNoTypeParameters.ts(2,15): error TS2558: Expected 0 type arguments, but got 1. tests/cases/compiler/typeArgumentsOnFunctionsWithNoTypeParameters.ts(3,15): error TS2345: Argument of type '1' is not assignable to parameter of type 'T'. -tests/cases/compiler/typeArgumentsOnFunctionsWithNoTypeParameters.ts(4,13): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/typeArgumentsOnFunctionsWithNoTypeParameters.ts(4,15): error TS2558: Expected 0 type arguments, but got 1. ==== tests/cases/compiler/typeArgumentsOnFunctionsWithNoTypeParameters.ts (3 errors) ==== function foo(f: (v: T) => U) { var r1 = f(1); - ~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r2 = f(1); ~ !!! error TS2345: Argument of type '1' is not assignable to parameter of type 'T'. var r3 = f(null); - ~~~~~~~~~~~~ + ~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var r4 = f(null); } diff --git a/tests/baselines/reference/typeAssertions.errors.txt b/tests/baselines/reference/typeAssertions.errors.txt index 45f20437860..cefc15b8e23 100644 --- a/tests/baselines/reference/typeAssertions.errors.txt +++ b/tests/baselines/reference/typeAssertions.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/expressions/typeAssertions/typeAssertions.ts(5,5): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/conformance/expressions/typeAssertions/typeAssertions.ts(5,9): error TS2558: Expected 0 type arguments, but got 1. tests/cases/conformance/expressions/typeAssertions/typeAssertions.ts(31,12): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeBase'. Property 'p' is missing in type 'SomeOther'. tests/cases/conformance/expressions/typeAssertions/typeAssertions.ts(35,15): error TS2352: Type 'SomeOther' cannot be converted to type 'SomeDerived'. @@ -29,7 +29,7 @@ tests/cases/conformance/expressions/typeAssertions/typeAssertions.ts(48,50): err function fn2(t: any) { } fn1(fn2(4)); // Error - ~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var a: any; diff --git a/tests/baselines/reference/untypedFunctionCallsWithTypeParameters1.errors.txt b/tests/baselines/reference/untypedFunctionCallsWithTypeParameters1.errors.txt index 6b6594fc1ed..d1a1b0f08f9 100644 --- a/tests/baselines/reference/untypedFunctionCallsWithTypeParameters1.errors.txt +++ b/tests/baselines/reference/untypedFunctionCallsWithTypeParameters1.errors.txt @@ -1,20 +1,20 @@ -tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(3,10): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(3,12): error TS2558: Expected 0 type arguments, but got 1. tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(5,10): error TS2347: Untyped function calls may not accept type arguments. tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(8,10): error TS2347: Untyped function calls may not accept type arguments. tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(10,7): error TS2420: Class 'C' incorrectly implements interface 'Function'. Property 'apply' is missing in type 'C'. tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(18,10): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'C' has no compatible call signatures. tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(22,10): error TS2347: Untyped function calls may not accept type arguments. -tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(28,10): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(35,1): error TS2558: Expected 0 type arguments, but got 1. -tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(41,1): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(28,12): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(35,4): error TS2558: Expected 0 type arguments, but got 1. +tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(41,4): error TS2558: Expected 0 type arguments, but got 1. ==== tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts (9 errors) ==== // none of these function calls should be allowed var x = function () { return; }; var r1 = x(); - ~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. var y: any = x; var r2 = y(); @@ -52,7 +52,7 @@ tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(41,1): error TS2 } var z: I; var r6 = z(1); // error - ~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. interface callable2 { @@ -61,7 +61,7 @@ tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(41,1): error TS2 var c4: callable2; c4(1); - ~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. interface callable3 { (a: T): T; @@ -69,7 +69,7 @@ tests/cases/compiler/untypedFunctionCallsWithTypeParameters1.ts(41,1): error TS2 var c5: callable3; c5(1); // error - ~~~~~~~~~~~~~ + ~~~~~~ !!! error TS2558: Expected 0 type arguments, but got 1. \ No newline at end of file From 1cc164b330929860f520cbad27e190cda621e00d Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 24 Jan 2018 12:05:44 -0800 Subject: [PATCH 139/163] Update version (#21319) --- package.json | 2 +- src/compiler/core.ts | 2 +- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fb089b64918..d0ec2d70ce2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "http://typescriptlang.org/", - "version": "2.7.0", + "version": "2.8.0", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ae506fd616a..cefa9367b48 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -4,7 +4,7 @@ namespace ts { // WARNING: The script `configureNightly.ts` uses a regexp to parse out these values. // If changing the text in this section, be sure to test `configureNightly` too. - export const versionMajorMinor = "2.7"; + export const versionMajorMinor = "2.8"; /** The version of the TypeScript compiler release */ export const version = `${versionMajorMinor}.0`; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index cd1b577b711..a2ded98383e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2808,7 +2808,7 @@ declare namespace ts { } } declare namespace ts { - const versionMajorMinor = "2.7"; + const versionMajorMinor = "2.8"; /** The version of the TypeScript compiler release */ const version: string; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3cbca96f8fa..2cf666b1cf3 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2808,7 +2808,7 @@ declare namespace ts { } } declare namespace ts { - const versionMajorMinor = "2.7"; + const versionMajorMinor = "2.8"; /** The version of the TypeScript compiler release */ const version: string; } From ba797f2c50bd6bf211950bef932cb8fb64ec7cb2 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 24 Jan 2018 12:37:45 -0800 Subject: [PATCH 140/163] Add flag to skip qualification check when symbol is already in the process of being qualified (#21337) --- src/compiler/checker.ts | 19 +++--- ...tionExportStarShadowingGlobalIsNameable.js | 60 +++++++++++++++++++ ...xportStarShadowingGlobalIsNameable.symbols | 49 +++++++++++++++ ...nExportStarShadowingGlobalIsNameable.types | 50 ++++++++++++++++ ...tionExportStarShadowingGlobalIsNameable.ts | 24 ++++++++ 5 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.js create mode 100644 tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.symbols create mode 100644 tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.types create mode 100644 tests/cases/compiler/moduleDeclarationExportStarShadowingGlobalIsNameable.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bb1fbbdd526..f3658c645db 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2424,12 +2424,15 @@ namespace ts { const visitedSymbolTables: SymbolTable[] = []; return forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); - function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable): Symbol[] | undefined { + /** + * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) + */ + function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean): Symbol[] | undefined { if (!pushIfUnique(visitedSymbolTables, symbols)) { return undefined; } - const result = trySymbolTable(symbols); + const result = trySymbolTable(symbols, ignoreQualification); visitedSymbolTables.pop(); return result; } @@ -2441,22 +2444,22 @@ namespace ts { !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing); } - function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol) { + function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { return symbol === (resolvedAliasSymbol || symbolFromSymbolTable) && // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) // and if symbolFromSymbolTable or alias resolution matches the symbol, // check the symbol can be qualified, it is only then this symbol is accessible !some(symbolFromSymbolTable.declarations, hasExternalModuleSymbol) && - canQualifySymbol(symbolFromSymbolTable, meaning); + (ignoreQualification || canQualifySymbol(symbolFromSymbolTable, meaning)); } function isUMDExportSymbol(symbol: Symbol) { return symbol && symbol.declarations && symbol.declarations[0] && isNamespaceExportDeclaration(symbol.declarations[0]); } - function trySymbolTable(symbols: SymbolTable) { + function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined) { // If symbol is directly available by its name in the symbol table - if (isAccessible(symbols.get(symbol.escapedName))) { + if (isAccessible(symbols.get(symbol.escapedName), /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { return [symbol]; } @@ -2469,14 +2472,14 @@ namespace ts { && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration))) { const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); - if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol)) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { return [symbolFromSymbolTable]; } // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain // but only if the symbolFromSymbolTable can be qualified const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); - const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable); + const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); } diff --git a/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.js b/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.js new file mode 100644 index 00000000000..77b1b5f8ff4 --- /dev/null +++ b/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.js @@ -0,0 +1,60 @@ +//// [tests/cases/compiler/moduleDeclarationExportStarShadowingGlobalIsNameable.ts] //// + +//// [index.ts] +export * from "./account"; + +//// [account.ts] +export interface Account { + myAccNum: number; +} +interface Account2 { + myAccNum: number; +} +export { Account2 as Acc }; + +//// [index.ts] +declare global { + interface Account { + someProp: number; + } + interface Acc { + someProp: number; + } +} +import * as model from "./model"; +export const func = (account: model.Account, acc2: model.Acc) => {}; + + +//// [account.js] +"use strict"; +exports.__esModule = true; +//// [index.js] +"use strict"; +exports.__esModule = true; +//// [index.js] +"use strict"; +exports.__esModule = true; +exports.func = function (account, acc2) { }; + + +//// [account.d.ts] +export interface Account { + myAccNum: number; +} +interface Account2 { + myAccNum: number; +} +export { Account2 as Acc }; +//// [index.d.ts] +export * from "./account"; +//// [index.d.ts] +declare global { + interface Account { + someProp: number; + } + interface Acc { + someProp: number; + } +} +import * as model from "./model"; +export declare const func: (account: model.Account, acc2: model.Acc) => void; diff --git a/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.symbols b/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.symbols new file mode 100644 index 00000000000..add5fce7476 --- /dev/null +++ b/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.symbols @@ -0,0 +1,49 @@ +=== tests/cases/compiler/model/index.ts === +export * from "./account"; +No type information for this code. +No type information for this code.=== tests/cases/compiler/model/account.ts === +export interface Account { +>Account : Symbol(Account, Decl(account.ts, 0, 0)) + + myAccNum: number; +>myAccNum : Symbol(Account.myAccNum, Decl(account.ts, 0, 26)) +} +interface Account2 { +>Account2 : Symbol(Account2, Decl(account.ts, 2, 1)) + + myAccNum: number; +>myAccNum : Symbol(Account2.myAccNum, Decl(account.ts, 3, 20)) +} +export { Account2 as Acc }; +>Account2 : Symbol(Acc, Decl(account.ts, 6, 8)) +>Acc : Symbol(Acc, Decl(account.ts, 6, 8)) + +=== tests/cases/compiler/index.ts === +declare global { +>global : Symbol(global, Decl(index.ts, 0, 0)) + + interface Account { +>Account : Symbol(Account, Decl(index.ts, 0, 16)) + + someProp: number; +>someProp : Symbol(Account.someProp, Decl(index.ts, 1, 23)) + } + interface Acc { +>Acc : Symbol(Acc, Decl(index.ts, 3, 5)) + + someProp: number; +>someProp : Symbol(Acc.someProp, Decl(index.ts, 4, 19)) + } +} +import * as model from "./model"; +>model : Symbol(model, Decl(index.ts, 8, 6)) + +export const func = (account: model.Account, acc2: model.Acc) => {}; +>func : Symbol(func, Decl(index.ts, 9, 12)) +>account : Symbol(account, Decl(index.ts, 9, 21)) +>model : Symbol(model, Decl(index.ts, 8, 6)) +>Account : Symbol(model.Account, Decl(account.ts, 0, 0)) +>acc2 : Symbol(acc2, Decl(index.ts, 9, 44)) +>model : Symbol(model, Decl(index.ts, 8, 6)) +>Acc : Symbol(model.Acc, Decl(account.ts, 6, 8)) + diff --git a/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.types b/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.types new file mode 100644 index 00000000000..af27f7e3678 --- /dev/null +++ b/tests/baselines/reference/moduleDeclarationExportStarShadowingGlobalIsNameable.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/model/index.ts === +export * from "./account"; +No type information for this code. +No type information for this code.=== tests/cases/compiler/model/account.ts === +export interface Account { +>Account : Account + + myAccNum: number; +>myAccNum : number +} +interface Account2 { +>Account2 : Account2 + + myAccNum: number; +>myAccNum : number +} +export { Account2 as Acc }; +>Account2 : any +>Acc : any + +=== tests/cases/compiler/index.ts === +declare global { +>global : any + + interface Account { +>Account : Account + + someProp: number; +>someProp : number + } + interface Acc { +>Acc : Acc + + someProp: number; +>someProp : number + } +} +import * as model from "./model"; +>model : typeof model + +export const func = (account: model.Account, acc2: model.Acc) => {}; +>func : (account: model.Account, acc2: model.Acc) => void +>(account: model.Account, acc2: model.Acc) => {} : (account: model.Account, acc2: model.Acc) => void +>account : model.Account +>model : any +>Account : model.Account +>acc2 : model.Acc +>model : any +>Acc : model.Acc + diff --git a/tests/cases/compiler/moduleDeclarationExportStarShadowingGlobalIsNameable.ts b/tests/cases/compiler/moduleDeclarationExportStarShadowingGlobalIsNameable.ts new file mode 100644 index 00000000000..9113e6e329b --- /dev/null +++ b/tests/cases/compiler/moduleDeclarationExportStarShadowingGlobalIsNameable.ts @@ -0,0 +1,24 @@ +// @declaration: true +// @filename: model/index.ts +export * from "./account"; + +// @filename: model/account.ts +export interface Account { + myAccNum: number; +} +interface Account2 { + myAccNum: number; +} +export { Account2 as Acc }; + +// @filename: index.ts +declare global { + interface Account { + someProp: number; + } + interface Acc { + someProp: number; + } +} +import * as model from "./model"; +export const func = (account: model.Account, acc2: model.Acc) => {}; From 08aa2653dd4609561c9ffe94e8aa5c349a4d12c5 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 24 Jan 2018 12:46:13 -0800 Subject: [PATCH 141/163] Remove incorrect assert --- src/harness/unittests/extractRanges.ts | 6 ++++++ src/services/refactors/extractSymbol.ts | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/harness/unittests/extractRanges.ts b/src/harness/unittests/extractRanges.ts index 2810f323abf..00fbf334b38 100644 --- a/src/harness/unittests/extractRanges.ts +++ b/src/harness/unittests/extractRanges.ts @@ -411,6 +411,12 @@ switch (x) { refactor.extractSymbol.Messages.cannotExtractRange.message ]); + testExtractRangeFailed("extractRangeFailed18", + `[#|{ 1;|] }`, + [ + refactor.extractSymbol.Messages.cannotExtractRange.message + ]); + testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); }); } \ No newline at end of file diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index e8a2186aa8b..e3a2763b783 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -242,7 +242,6 @@ namespace ts.refactor.extractSymbol { // they will never find `start` in `start.parent.statements`. // Consider: We could support ranges like [|case 1:|] by refining them to just // the expression. - Debug.assert(isCaseClause(start.parent) && span.start < start.parent.expression.end); return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; } From e58391d9c53c22a1207c5cb2fe7ec21b5399887e Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 24 Jan 2018 15:06:00 -0800 Subject: [PATCH 142/163] In fourslash.ts, remove unused exports and use '{}' instead of 'any' (#21377) --- src/harness/fourslash.ts | 43 +++++++++++++--------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index bf5f46de248..75476669b59 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -23,7 +23,7 @@ namespace FourSlash { ts.disableIncrementalParsing = false; // Represents a parsed source file with metadata - export interface FourSlashFile { + interface FourSlashFile { // The contents of the file (with markers, etc stripped out) content: string; fileName: string; @@ -34,7 +34,7 @@ namespace FourSlash { } // Represents a set of parsed source files and options - export interface FourSlashData { + interface FourSlashData { // Global options (name/value pairs) globalOptions: Harness.TestCaseParser.CompilerSettings; @@ -59,7 +59,7 @@ namespace FourSlash { export interface Marker { fileName: string; position: number; - data?: any; + data?: {}; } export interface Range { @@ -89,21 +89,6 @@ namespace FourSlash { end: number; } - export import IndentStyle = ts.IndentStyle; - - const entityMap = ts.createMapFromTemplate({ - "&": "&", - "\"": """, - "'": "'", - "/": "/", - "<": "<", - ">": ">" - }); - - export function escapeXmlAttributeValue(s: string) { - return s.replace(/[&<>"'\/]/g, ch => entityMap.get(ch)); - } - // Name of testcase metadata including ts.CompilerOptions properties that will be used by globalOptions // To add additional option, add property into the testOptMetadataNames, refer the property in either globalMetadataNames or fileMetadataNames // Add cases into convertGlobalOptionsToCompilationsSettings function for the compiler to acknowledge such option from meta data @@ -1079,7 +1064,7 @@ namespace FourSlash { for (const reference of expectedReferences) { const { fileName, start, end } = reference; if (reference.marker && reference.marker.data) { - const { isWriteAccess, isDefinition } = reference.marker.data; + const { isWriteAccess, isDefinition } = reference.marker.data as { isWriteAccess?: boolean, isDefinition?: boolean }; this.verifyReferencesWorker(actualReferences, fileName, start, end, isWriteAccess, isDefinition); } else { @@ -1108,7 +1093,16 @@ namespace FourSlash { } const fullExpected = ts.map(parts, ({ definition, ranges }) => ({ definition: typeof definition === "string" ? definition : { ...definition, range: textSpanFromRange(definition.range) }, - references: ranges.map(rangeToReferenceEntry), + references: ranges.map(r => { + const { isWriteAccess = false, isDefinition = false, isInString } = (r.marker && r.marker.data || {}) as { isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true }; + return { + isWriteAccess, + isDefinition, + fileName: r.fileName, + textSpan: textSpanFromRange(r), + ...(isInString ? { isInString: true } : undefined), + }; + }), })); for (const startRange of toArray(startRanges)) { @@ -1122,15 +1116,6 @@ namespace FourSlash { }); this.assertObjectsEqual(fullActual, fullExpected); } - - function rangeToReferenceEntry(r: Range): ts.ReferenceEntry { - const { isWriteAccess, isDefinition, isInString } = (r.marker && r.marker.data) || { isWriteAccess: false, isDefinition: false, isInString: undefined }; - const result: ts.ReferenceEntry = { fileName: r.fileName, textSpan: textSpanFromRange(r), isWriteAccess: !!isWriteAccess, isDefinition: !!isDefinition }; - if (isInString !== undefined) { - result.isInString = isInString; - } - return result; - } } public verifyNoReferences(markerNameOrRange?: string | Range) { From d333d889c1d42a98deceb81545ac5a9a57448abe Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 24 Jan 2018 15:06:52 -0800 Subject: [PATCH 143/163] Test for (and fix) order of import fixes (#21398) --- src/harness/fourslash.ts | 8 +++---- src/services/codefixes/importFixes.ts | 2 +- .../fourslash/completionsImportBaseUrl.ts | 21 +++++++++++++++++++ .../importNameCodeFixExistingImport2.ts | 4 ++-- .../importNameCodeFixNewImportBaseUrl0.ts | 6 +++--- .../importNameCodeFixOptionalImport0.ts | 6 +++--- .../importNameCodeFixOptionalImport1.ts | 10 ++++----- 7 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 tests/cases/fourslash/completionsImportBaseUrl.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 75476669b59..98e8e61eef5 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2572,12 +2572,10 @@ Actual: ${stringify(fullActual)}`); actualTextArray.push(text); scriptInfo.updateContent(originalContent); } - const sortedExpectedArray = expectedTextArray.sort(); - const sortedActualArray = actualTextArray.sort(); - if (sortedExpectedArray.length !== sortedActualArray.length) { - this.raiseError(`Expected ${sortedExpectedArray.length} import fixes, got ${sortedActualArray.length}`); + if (expectedTextArray.length !== actualTextArray.length) { + this.raiseError(`Expected ${expectedTextArray.length} import fixes, got ${actualTextArray.length}`); } - ts.zipWith(sortedExpectedArray, sortedActualArray, (expected, actual, index) => { + ts.zipWith(expectedTextArray, actualTextArray, (expected, actual, index) => { if (expected !== actual) { this.raiseError(`Import fix at index ${index} doesn't match.\n${showTextDiff(expected, actual)}`); } diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 4c536ce3fd2..ac88d98eba5 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -390,7 +390,7 @@ namespace ts.codefix { In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a". */ const pathFromSourceToBaseUrl = getRelativePath(baseUrl, sourceDirectory, getCanonicalFileName); - const relativeFirst = getRelativePathNParents(pathFromSourceToBaseUrl) < getRelativePathNParents(relativePath); + const relativeFirst = getRelativePathNParents(relativePath) < getRelativePathNParents(pathFromSourceToBaseUrl); return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath]; })); // Only return results for the re-export with the shortest possible path (and also give the other path even if that's long.) diff --git a/tests/cases/fourslash/completionsImportBaseUrl.ts b/tests/cases/fourslash/completionsImportBaseUrl.ts new file mode 100644 index 00000000000..e5f5b07b9f4 --- /dev/null +++ b/tests/cases/fourslash/completionsImportBaseUrl.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /tsconfig.json +////{ +//// "compilerOptions": { +//// "baseUrl": "." +//// } +////} + +// @Filename: /src/a.ts +////export const foo = 0; + +// @Filename: /src/b.ts +////fo/**/ + +// Test that it prefers a relative import (see sourceDisplay). +goTo.marker(""); +verify.completionListContains({ name: "foo", source: "/src/a" }, "const foo: 0", "", "const", undefined, /*hasAction*/ true, { + includeExternalModuleExports: true, + sourceDisplay: "./a", +}); diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport2.ts b/tests/cases/fourslash/importNameCodeFixExistingImport2.ts index 1146f24aeae..dbf34f4389e 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport2.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport2.ts @@ -9,8 +9,8 @@ verify.importFixAtPosition([ `import * as ns from "./module"; +ns.f1();`, +`import * as ns from "./module"; import { f1 } from "./module"; f1();`, -`import * as ns from "./module"; -ns.f1();`, ]); diff --git a/tests/cases/fourslash/importNameCodeFixNewImportBaseUrl0.ts b/tests/cases/fourslash/importNameCodeFixNewImportBaseUrl0.ts index bc852b5c47c..fba8875a610 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportBaseUrl0.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportBaseUrl0.ts @@ -13,10 +13,10 @@ //// export function f1() { }; verify.importFixAtPosition([ +`import { f1 } from "b"; + +f1();`, `import { f1 } from "./a/b"; f1();`, -`import { f1 } from "b"; - -f1();` ]); diff --git a/tests/cases/fourslash/importNameCodeFixOptionalImport0.ts b/tests/cases/fourslash/importNameCodeFixOptionalImport0.ts index 218ec4fabb7..3fbae01fab0 100644 --- a/tests/cases/fourslash/importNameCodeFixOptionalImport0.ts +++ b/tests/cases/fourslash/importNameCodeFixOptionalImport0.ts @@ -12,9 +12,9 @@ verify.importFixAtPosition([ `import * as ns from "./foo"; -import { foo } from "./foo"; -foo();`, +ns.foo();`, `import * as ns from "./foo"; -ns.foo();`, +import { foo } from "./foo"; +foo();`, ]); diff --git a/tests/cases/fourslash/importNameCodeFixOptionalImport1.ts b/tests/cases/fourslash/importNameCodeFixOptionalImport1.ts index 7a3d19e1532..11204b3c4d2 100644 --- a/tests/cases/fourslash/importNameCodeFixOptionalImport1.ts +++ b/tests/cases/fourslash/importNameCodeFixOptionalImport1.ts @@ -7,14 +7,14 @@ //// export function foo() {}; // @Filename: a/foo.ts -//// export { foo } from "bar"; +//// export { foo } from "bar"; verify.importFixAtPosition([ -`import { foo } from "./foo"; - -foo();`, - `import { foo } from "bar"; +foo();`, + +`import { foo } from "./foo"; + foo();`, ]); From fe0c461d9139c01372e6b6816f85c38cfd20ada2 Mon Sep 17 00:00:00 2001 From: csigs Date: Wed, 24 Jan 2018 23:10:36 +0000 Subject: [PATCH 144/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- .../diagnosticMessages/diagnosticMessages.generated.json.lcl | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl index 7ae7fab1b3b..5d67c5c6cdc 100644 --- a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + diff --git a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl index 7cc0cf16633..c7e02a6c2cc 100644 --- a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + diff --git a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl index 7ef4d27d021..3747e00c1f9 100644 --- a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -19,7 +19,7 @@ - + diff --git a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl index 7fe309d50f8..4d8f2721a08 100644 --- a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + diff --git a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 3ad2bbf63b2..517b05df202 100644 --- a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -19,7 +19,7 @@ - + diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index 1262a3743cf..99776a461cc 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -19,7 +19,7 @@ - + diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl index 7579d5ce794..e13e8191eb2 100644 --- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + diff --git a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 551a0e28806..1a892b757cb 100644 --- a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl index d9b0532182c..b534bf56365 100644 --- a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + diff --git a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 400187f844a..c5473f41421 100644 --- a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -7,7 +7,7 @@ - + diff --git a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl index b6290bed3fb..080b2e11402 100644 --- a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -7,7 +7,7 @@ - + diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl index 46dce16bc93..bb0b07794a3 100644 --- a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -13,7 +13,7 @@ - + diff --git a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl index c1894d0db09..44a9f695fca 100644 --- a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -7,7 +7,7 @@ - + From 5698a6ab52d50f25f912bebe012ea8e99c28cc0e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 25 Jan 2018 02:11:01 -0800 Subject: [PATCH 145/163] Fix loop labels for for..await --- src/compiler/transformers/esnext.ts | 2 +- .../reference/emitter.forAwait.es2015.js | 88 +++++++++ .../reference/emitter.forAwait.es2015.symbols | 30 +++ .../reference/emitter.forAwait.es2015.types | 34 ++++ .../reference/emitter.forAwait.es2017.js | 78 ++++++++ .../reference/emitter.forAwait.es2017.symbols | 30 +++ .../reference/emitter.forAwait.es2017.types | 34 ++++ .../reference/emitter.forAwait.es5.js | 182 ++++++++++++++++++ .../reference/emitter.forAwait.es5.symbols | 30 +++ .../reference/emitter.forAwait.es5.types | 34 ++++ .../reference/emitter.forAwait.esnext.js | 32 +++ .../reference/emitter.forAwait.esnext.symbols | 30 +++ .../reference/emitter.forAwait.esnext.types | 34 ++++ .../forAwait/emitter.forAwait.es2015.ts | 16 ++ .../forAwait/emitter.forAwait.es2017.ts | 16 ++ .../es5/forAwait/emitter.forAwait.es5.ts | 16 ++ .../forAwait/emitter.forAwait.esnext.ts | 16 ++ 17 files changed, 701 insertions(+), 1 deletion(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 5d46c8430d2..424371ad311 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -148,7 +148,7 @@ namespace ts { } function visitLabeledStatement(node: LabeledStatement) { - if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) { + if (enclosingFunctionFlags & FunctionFlags.Async) { const statement = unwrapInnermostStatementOfLabel(node); if (statement.kind === SyntaxKind.ForOfStatement && (statement).awaitModifier) { return visitForOfStatement(statement, node); diff --git a/tests/baselines/reference/emitter.forAwait.es2015.js b/tests/baselines/reference/emitter.forAwait.es2015.js index 323d62f0ac8..6b6bdd739fd 100644 --- a/tests/baselines/reference/emitter.forAwait.es2015.js +++ b/tests/baselines/reference/emitter.forAwait.es2015.js @@ -24,6 +24,22 @@ async function* f4() { for await (x of y) { } } +//// [file5.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +//// [file6.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} //// [file1.js] var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -161,3 +177,75 @@ function f4() { var e_1, _a; }); } +//// [file5.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator]; + return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); +}; +// https://github.com/Microsoft/TypeScript/issues/21363 +function f5() { + return __awaiter(this, void 0, void 0, function* () { + let y; + try { + outer: for (var y_1 = __asyncValues(y), y_1_1; y_1_1 = yield y_1.next(), !y_1_1.done;) { + const x = yield y_1_1.value; + continue outer; + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (y_1_1 && !y_1_1.done && (_a = y_1.return)) yield _a.call(y_1); + } + finally { if (e_1) throw e_1.error; } + } + var e_1, _a; + }); +} +//// [file6.js] +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator]; + return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); +}; +var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } +var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +}; +// https://github.com/Microsoft/TypeScript/issues/21363 +function f6() { + return __asyncGenerator(this, arguments, function* f6_1() { + let y; + try { + outer: for (var y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), !y_1_1.done;) { + const x = yield __await(y_1_1.value); + continue outer; + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (y_1_1 && !y_1_1.done && (_a = y_1.return)) yield __await(_a.call(y_1)); + } + finally { if (e_1) throw e_1.error; } + } + var e_1, _a; + }); +} diff --git a/tests/baselines/reference/emitter.forAwait.es2015.symbols b/tests/baselines/reference/emitter.forAwait.es2015.symbols index 6c4ed3b10e9..bc75908f601 100644 --- a/tests/baselines/reference/emitter.forAwait.es2015.symbols +++ b/tests/baselines/reference/emitter.forAwait.es2015.symbols @@ -48,3 +48,33 @@ async function* f4() { >y : Symbol(y, Decl(file4.ts, 1, 15)) } } +=== tests/cases/conformance/emitter/es2015/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : Symbol(f5, Decl(file5.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file5.ts, 3, 27)) +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + continue outer; + } +} +=== tests/cases/conformance/emitter/es2015/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : Symbol(f6, Decl(file6.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file6.ts, 3, 27)) +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + continue outer; + } +} diff --git a/tests/baselines/reference/emitter.forAwait.es2015.types b/tests/baselines/reference/emitter.forAwait.es2015.types index ac7321e43b4..e7d1c77f346 100644 --- a/tests/baselines/reference/emitter.forAwait.es2015.types +++ b/tests/baselines/reference/emitter.forAwait.es2015.types @@ -48,3 +48,37 @@ async function* f4() { >y : any } } +=== tests/cases/conformance/emitter/es2015/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : () => Promise + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} +=== tests/cases/conformance/emitter/es2015/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : () => AsyncIterableIterator + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} diff --git a/tests/baselines/reference/emitter.forAwait.es2017.js b/tests/baselines/reference/emitter.forAwait.es2017.js index 4b3770f02af..8eb21442558 100644 --- a/tests/baselines/reference/emitter.forAwait.es2017.js +++ b/tests/baselines/reference/emitter.forAwait.es2017.js @@ -24,6 +24,22 @@ async function* f4() { for await (x of y) { } } +//// [file5.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +//// [file6.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} //// [file1.js] var __asyncValues = (this && this.__asyncValues) || function (o) { @@ -141,3 +157,65 @@ function f4() { var e_1, _a; }); } +//// [file5.js] +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator]; + return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); +}; +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y; + try { + outer: for (var y_1 = __asyncValues(y), y_1_1; y_1_1 = await y_1.next(), !y_1_1.done;) { + const x = await y_1_1.value; + continue outer; + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (y_1_1 && !y_1_1.done && (_a = y_1.return)) await _a.call(y_1); + } + finally { if (e_1) throw e_1.error; } + } + var e_1, _a; +} +//// [file6.js] +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator]; + return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); +}; +var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } +var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +}; +// https://github.com/Microsoft/TypeScript/issues/21363 +function f6() { + return __asyncGenerator(this, arguments, function* f6_1() { + let y; + try { + outer: for (var y_1 = __asyncValues(y), y_1_1; y_1_1 = yield __await(y_1.next()), !y_1_1.done;) { + const x = yield __await(y_1_1.value); + continue outer; + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (y_1_1 && !y_1_1.done && (_a = y_1.return)) yield __await(_a.call(y_1)); + } + finally { if (e_1) throw e_1.error; } + } + var e_1, _a; + }); +} diff --git a/tests/baselines/reference/emitter.forAwait.es2017.symbols b/tests/baselines/reference/emitter.forAwait.es2017.symbols index a08e31f5553..184d26c1266 100644 --- a/tests/baselines/reference/emitter.forAwait.es2017.symbols +++ b/tests/baselines/reference/emitter.forAwait.es2017.symbols @@ -48,3 +48,33 @@ async function* f4() { >y : Symbol(y, Decl(file4.ts, 1, 15)) } } +=== tests/cases/conformance/emitter/es2017/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : Symbol(f5, Decl(file5.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file5.ts, 3, 27)) +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + continue outer; + } +} +=== tests/cases/conformance/emitter/es2017/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : Symbol(f6, Decl(file6.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file6.ts, 3, 27)) +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + continue outer; + } +} diff --git a/tests/baselines/reference/emitter.forAwait.es2017.types b/tests/baselines/reference/emitter.forAwait.es2017.types index 0d886cca4cc..f1dffdd280a 100644 --- a/tests/baselines/reference/emitter.forAwait.es2017.types +++ b/tests/baselines/reference/emitter.forAwait.es2017.types @@ -48,3 +48,37 @@ async function* f4() { >y : any } } +=== tests/cases/conformance/emitter/es2017/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : () => Promise + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} +=== tests/cases/conformance/emitter/es2017/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : () => AsyncIterableIterator + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} diff --git a/tests/baselines/reference/emitter.forAwait.es5.js b/tests/baselines/reference/emitter.forAwait.es5.js index 2b073a3f43a..b4ea4ddcf54 100644 --- a/tests/baselines/reference/emitter.forAwait.es5.js +++ b/tests/baselines/reference/emitter.forAwait.es5.js @@ -24,6 +24,22 @@ async function* f4() { for await (x of y) { } } +//// [file5.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +//// [file6.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} //// [file1.js] var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -353,3 +369,169 @@ function f4() { }); }); } +//// [file5.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [0, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator]; + return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); +}; +// https://github.com/Microsoft/TypeScript/issues/21363 +function f5() { + return __awaiter(this, void 0, void 0, function () { + var y, y_1, y_1_1, x, e_1_1, e_1, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _b.trys.push([0, 6, 7, 12]); + y_1 = __asyncValues(y); + _b.label = 1; + case 1: return [4 /*yield*/, y_1.next()]; + case 2: + if (!(y_1_1 = _b.sent(), !y_1_1.done)) return [3 /*break*/, 5]; + return [4 /*yield*/, y_1_1.value]; + case 3: + x = _b.sent(); + return [3 /*break*/, 4]; + case 4: return [3 /*break*/, 1]; + case 5: return [3 /*break*/, 12]; + case 6: + e_1_1 = _b.sent(); + e_1 = { error: e_1_1 }; + return [3 /*break*/, 12]; + case 7: + _b.trys.push([7, , 10, 11]); + if (!(y_1_1 && !y_1_1.done && (_a = y_1.return))) return [3 /*break*/, 9]; + return [4 /*yield*/, _a.call(y_1)]; + case 8: + _b.sent(); + _b.label = 9; + case 9: return [3 /*break*/, 11]; + case 10: + if (e_1) throw e_1.error; + return [7 /*endfinally*/]; + case 11: return [7 /*endfinally*/]; + case 12: return [2 /*return*/]; + } + }); + }); +} +//// [file6.js] +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [0, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator]; + return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator](); +}; +var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } +var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +}; +// https://github.com/Microsoft/TypeScript/issues/21363 +function f6() { + return __asyncGenerator(this, arguments, function f6_1() { + var y, y_1, y_1_1, x, e_1_1, e_1, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _b.trys.push([0, 6, 7, 12]); + y_1 = __asyncValues(y); + _b.label = 1; + case 1: return [4 /*yield*/, __await(y_1.next())]; + case 2: + if (!(y_1_1 = _b.sent(), !y_1_1.done)) return [3 /*break*/, 5]; + return [4 /*yield*/, __await(y_1_1.value)]; + case 3: + x = _b.sent(); + return [3 /*break*/, 4]; + case 4: return [3 /*break*/, 1]; + case 5: return [3 /*break*/, 12]; + case 6: + e_1_1 = _b.sent(); + e_1 = { error: e_1_1 }; + return [3 /*break*/, 12]; + case 7: + _b.trys.push([7, , 10, 11]); + if (!(y_1_1 && !y_1_1.done && (_a = y_1.return))) return [3 /*break*/, 9]; + return [4 /*yield*/, __await(_a.call(y_1))]; + case 8: + _b.sent(); + _b.label = 9; + case 9: return [3 /*break*/, 11]; + case 10: + if (e_1) throw e_1.error; + return [7 /*endfinally*/]; + case 11: return [7 /*endfinally*/]; + case 12: return [2 /*return*/]; + } + }); + }); +} diff --git a/tests/baselines/reference/emitter.forAwait.es5.symbols b/tests/baselines/reference/emitter.forAwait.es5.symbols index 40c5562453c..05fb915c0fc 100644 --- a/tests/baselines/reference/emitter.forAwait.es5.symbols +++ b/tests/baselines/reference/emitter.forAwait.es5.symbols @@ -48,3 +48,33 @@ async function* f4() { >y : Symbol(y, Decl(file4.ts, 1, 15)) } } +=== tests/cases/conformance/emitter/es5/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : Symbol(f5, Decl(file5.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file5.ts, 3, 27)) +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + continue outer; + } +} +=== tests/cases/conformance/emitter/es5/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : Symbol(f6, Decl(file6.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file6.ts, 3, 27)) +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + continue outer; + } +} diff --git a/tests/baselines/reference/emitter.forAwait.es5.types b/tests/baselines/reference/emitter.forAwait.es5.types index 8c8f6dec723..fe0a49a9016 100644 --- a/tests/baselines/reference/emitter.forAwait.es5.types +++ b/tests/baselines/reference/emitter.forAwait.es5.types @@ -48,3 +48,37 @@ async function* f4() { >y : any } } +=== tests/cases/conformance/emitter/es5/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : () => Promise + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} +=== tests/cases/conformance/emitter/es5/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : () => AsyncIterableIterator + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} diff --git a/tests/baselines/reference/emitter.forAwait.esnext.js b/tests/baselines/reference/emitter.forAwait.esnext.js index 7a6c6837c9a..0042ae38fbd 100644 --- a/tests/baselines/reference/emitter.forAwait.esnext.js +++ b/tests/baselines/reference/emitter.forAwait.esnext.js @@ -24,6 +24,22 @@ async function* f4() { for await (x of y) { } } +//// [file5.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +//// [file6.ts] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} //// [file1.js] async function f1() { @@ -49,3 +65,19 @@ async function* f4() { for await (x of y) { } } +//// [file5.js] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y; + outer: for await (const x of y) { + continue outer; + } +} +//// [file6.js] +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y; + outer: for await (const x of y) { + continue outer; + } +} diff --git a/tests/baselines/reference/emitter.forAwait.esnext.symbols b/tests/baselines/reference/emitter.forAwait.esnext.symbols index 137ebed0802..2910738102b 100644 --- a/tests/baselines/reference/emitter.forAwait.esnext.symbols +++ b/tests/baselines/reference/emitter.forAwait.esnext.symbols @@ -48,3 +48,33 @@ async function* f4() { >y : Symbol(y, Decl(file4.ts, 1, 15)) } } +=== tests/cases/conformance/emitter/esnext/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : Symbol(f5, Decl(file5.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file5.ts, 3, 27)) +>y : Symbol(y, Decl(file5.ts, 2, 7)) + + continue outer; + } +} +=== tests/cases/conformance/emitter/esnext/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : Symbol(f6, Decl(file6.ts, 0, 0)) + + let y: any; +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + outer: for await (const x of y) { +>x : Symbol(x, Decl(file6.ts, 3, 27)) +>y : Symbol(y, Decl(file6.ts, 2, 7)) + + continue outer; + } +} diff --git a/tests/baselines/reference/emitter.forAwait.esnext.types b/tests/baselines/reference/emitter.forAwait.esnext.types index ae52bd1080d..6339aeebeff 100644 --- a/tests/baselines/reference/emitter.forAwait.esnext.types +++ b/tests/baselines/reference/emitter.forAwait.esnext.types @@ -48,3 +48,37 @@ async function* f4() { >y : any } } +=== tests/cases/conformance/emitter/esnext/forAwait/file5.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { +>f5 : () => Promise + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} +=== tests/cases/conformance/emitter/esnext/forAwait/file6.ts === +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { +>f6 : () => AsyncIterableIterator + + let y: any; +>y : any + + outer: for await (const x of y) { +>outer : any +>x : any +>y : any + + continue outer; +>outer : any + } +} diff --git a/tests/cases/conformance/emitter/es2015/forAwait/emitter.forAwait.es2015.ts b/tests/cases/conformance/emitter/es2015/forAwait/emitter.forAwait.es2015.ts index 2069f08af30..64b44e901e2 100644 --- a/tests/cases/conformance/emitter/es2015/forAwait/emitter.forAwait.es2015.ts +++ b/tests/cases/conformance/emitter/es2015/forAwait/emitter.forAwait.es2015.ts @@ -23,4 +23,20 @@ async function* f4() { let x: any, y: any; for await (x of y) { } +} +// @filename: file5.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +// @filename: file6.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } } \ No newline at end of file diff --git a/tests/cases/conformance/emitter/es2017/forAwait/emitter.forAwait.es2017.ts b/tests/cases/conformance/emitter/es2017/forAwait/emitter.forAwait.es2017.ts index f2d1f46873d..f9d9fc3d4d9 100644 --- a/tests/cases/conformance/emitter/es2017/forAwait/emitter.forAwait.es2017.ts +++ b/tests/cases/conformance/emitter/es2017/forAwait/emitter.forAwait.es2017.ts @@ -23,4 +23,20 @@ async function* f4() { let x: any, y: any; for await (x of y) { } +} +// @filename: file5.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +// @filename: file6.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } } \ No newline at end of file diff --git a/tests/cases/conformance/emitter/es5/forAwait/emitter.forAwait.es5.ts b/tests/cases/conformance/emitter/es5/forAwait/emitter.forAwait.es5.ts index beb9ac9c2ca..a441b0da2bd 100644 --- a/tests/cases/conformance/emitter/es5/forAwait/emitter.forAwait.es5.ts +++ b/tests/cases/conformance/emitter/es5/forAwait/emitter.forAwait.es5.ts @@ -23,4 +23,20 @@ async function* f4() { let x: any, y: any; for await (x of y) { } +} +// @filename: file5.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +// @filename: file6.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } } \ No newline at end of file diff --git a/tests/cases/conformance/emitter/esnext/forAwait/emitter.forAwait.esnext.ts b/tests/cases/conformance/emitter/esnext/forAwait/emitter.forAwait.esnext.ts index 2f532f55ed5..ea1b030599e 100644 --- a/tests/cases/conformance/emitter/esnext/forAwait/emitter.forAwait.esnext.ts +++ b/tests/cases/conformance/emitter/esnext/forAwait/emitter.forAwait.esnext.ts @@ -23,4 +23,20 @@ async function* f4() { let x: any, y: any; for await (x of y) { } +} +// @filename: file5.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function f5() { + let y: any; + outer: for await (const x of y) { + continue outer; + } +} +// @filename: file6.ts +// https://github.com/Microsoft/TypeScript/issues/21363 +async function* f6() { + let y: any; + outer: for await (const x of y) { + continue outer; + } } \ No newline at end of file From 058e3ad75e5ad5239f3b89c83d3a2dba6933fa24 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 25 Jan 2018 07:36:38 -0800 Subject: [PATCH 146/163] Improve assertion in computePositionOfLineAndCharacter (#21361) --- src/compiler/core.ts | 4 ++++ src/compiler/scanner.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cefa9367b48..06f8b24a4c6 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -340,6 +340,10 @@ namespace ts { return false; } + export function arraysEqual(a: ReadonlyArray, b: ReadonlyArray, equalityComparer: EqualityComparer = equateValues): boolean { + return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); + } + export function indexOfAnyCharCode(text: string, charCodes: ReadonlyArray, start?: number): number { for (let i = start || 0; i < text.length; i++) { if (contains(charCodes, text.charCodeAt(i))) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 5001ea58336..6b0d10223fd 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -335,7 +335,10 @@ namespace ts { /* @internal */ export function computePositionOfLineAndCharacter(lineStarts: ReadonlyArray, line: number, character: number, debugText?: string): number { - Debug.assert(line >= 0 && line < lineStarts.length); + if (line < 0 || line >= lineStarts.length) { + Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); + } + const res = lineStarts[line] + character; if (line < lineStarts.length - 1) { Debug.assert(res < lineStarts[line + 1]); From 0b7f6d5911fae749d9dd8e67a0f86dbc2ba8421e Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 25 Jan 2018 07:42:01 -0800 Subject: [PATCH 147/163] Fix bug: Support `this.` completions even when isGlobalCompletion is false (#21330) --- src/services/completions.ts | 5 +++-- tests/cases/fourslash/completionsThisType.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 8b7f564ff08..492aca5b26c 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -180,7 +180,7 @@ namespace ts.Completions { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; - if (kind === CompletionKind.Global && origin && origin.type === "this-type") { + if (origin && origin.type === "this-type") { insertText = needsConvertPropertyAccess ? `this["${name}"]` : `this.${name}`; } else if (needsConvertPropertyAccess) { @@ -683,6 +683,7 @@ namespace ts.Completions { const enum CompletionKind { ObjectPropertyDeclaration, + /** Note that sometimes we access completions from global scope, but use "None" instead of this. See isGlobalCompletionScope. */ Global, PropertyAccess, MemberLike, @@ -2112,13 +2113,13 @@ namespace ts.Completions { const validIdentiferResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; if (isIdentifierText(name, target)) return validIdentiferResult; switch (kind) { - case CompletionKind.None: case CompletionKind.MemberLike: return undefined; case CompletionKind.ObjectPropertyDeclaration: // TODO: GH#18169 return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; case CompletionKind.PropertyAccess: + case CompletionKind.None: case CompletionKind.Global: // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; diff --git a/tests/cases/fourslash/completionsThisType.ts b/tests/cases/fourslash/completionsThisType.ts index 58325182a22..471251d2134 100644 --- a/tests/cases/fourslash/completionsThisType.ts +++ b/tests/cases/fourslash/completionsThisType.ts @@ -3,7 +3,7 @@ ////class C { //// "foo bar": number; //// xyz() { -//// /**/ +//// return (/**/) //// } ////} //// @@ -11,7 +11,7 @@ goTo.marker(""); -verify.completionListContains("xyz", "(method) C.xyz(): void", "", "method", undefined, undefined, { +verify.completionListContains("xyz", "(method) C.xyz(): any", "", "method", undefined, undefined, { includeInsertTextCompletions: true, insertText: "this.xyz", }); From dcd3b5e1f7582fba9dc4a6c0ac924e836c19444b Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 25 Jan 2018 09:02:20 -0800 Subject: [PATCH 148/163] At `
{ @@ -246,7 +256,7 @@ namespace ts.Completions { const uniques = createMap(); for (const symbol of symbols) { const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined; - const entry = createCompletionEntry(symbol, location, sourceFile, typeChecker, target, kind, origin, recommendedCompletion, propertyAccessToConvert, includeInsertTextCompletions); + const entry = createCompletionEntry(symbol, location, sourceFile, typeChecker, target, kind, origin, recommendedCompletion, propertyAccessToConvert, isJsxInitializer, includeInsertTextCompletions); if (!entry) { continue; } @@ -483,6 +493,7 @@ namespace ts.Completions { location: Node; symbolToOriginInfoMap: SymbolOriginInfoMap; previousToken: Node; + readonly isJsxInitializer: boolean; } function getSymbolCompletionFromEntryId( typeChecker: TypeChecker, @@ -501,7 +512,7 @@ namespace ts.Completions { return { type: "request", request: completionData }; } - const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken } = completionData; + const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData; // Find the symbol with the matching entry name. // We don't need to perform character checks here because we're only comparing the @@ -510,7 +521,7 @@ namespace ts.Completions { return firstDefined(symbols, (symbol): SymbolCompletion => { // TODO: Shouldn't need return type annotation (GH#12632) const origin = symbolToOriginInfoMap[getSymbolId(symbol)]; const info = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target, origin, completionKind); - return info && info.name === name && getSourceFromOrigin(origin) === source ? { type: "symbol" as "symbol", symbol, location, symbolToOriginInfoMap, previousToken } : undefined; + return info && info.name === name && getSourceFromOrigin(origin) === source ? { type: "symbol" as "symbol", symbol, location, symbolToOriginInfoMap, previousToken, isJsxInitializer } : undefined; }) || { type: "none" }; } @@ -678,6 +689,7 @@ namespace ts.Completions { readonly symbolToOriginInfoMap: SymbolOriginInfoMap; readonly recommendedCompletion: Symbol | undefined; readonly previousToken: Node | undefined; + readonly isJsxInitializer: boolean; } type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; @@ -859,6 +871,7 @@ namespace ts.Completions { let isRightOfDot = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; + let isJsxInitializer = false; let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853 if (contextToken) { @@ -917,6 +930,10 @@ namespace ts.Completions { location = contextToken; } break; + + case SyntaxKind.JsxAttribute: + isJsxInitializer = previousToken.kind === SyntaxKind.EqualsToken; + break; } } } @@ -962,7 +979,7 @@ namespace ts.Completions { log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, typeChecker); - return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken }; + return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; diff --git a/tests/cases/fourslash/completionsJsxAttributeInitializer.ts b/tests/cases/fourslash/completionsJsxAttributeInitializer.ts new file mode 100644 index 00000000000..510e0630de9 --- /dev/null +++ b/tests/cases/fourslash/completionsJsxAttributeInitializer.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /a.tsx +////function f(this: { p: number; "a b": number }, x: number): void { +////
; +////} + +goTo.marker(); + +verify.completionListContains("x", "(parameter) x: number", "", "parameter", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "{x}", +}); + +verify.completionListContains("p", "(property) p: number", "", "property", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "{this.p}", +}); + +verify.completionListContains("a b", '(property) "a b": number', "", "property", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: '{this["a b"]}', +}); From 8e8a02f8f13dfd0d0da5da703711633b09f2570e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 25 Jan 2018 10:06:59 -0800 Subject: [PATCH 149/163] Rename parseConfigFile to getParsedCommandLineOfConfigFile --- 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 beccb65f7b8..790295c2254 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -68,7 +68,7 @@ namespace ts { const host: ParseConfigFileHost = system; host.onConfigFileDiagnostic = reportDiagnostic; host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(sys, reportDiagnostic, diagnostic); - const result = parseConfigFile(configFileName, optionsToExtend, host); + const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host); host.onConfigFileDiagnostic = undefined; host.onUnRecoverableConfigFileDiagnostic = undefined; return result; @@ -77,7 +77,7 @@ namespace ts { /** * Reads the config file, reports errors if any and exits if the config file cannot be found */ - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { + export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { let configFileText: string; try { configFileText = host.readFile(configFileName); @@ -791,7 +791,7 @@ namespace ts { } function parseConfigFile() { - const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost); + const configParseResult = ts.getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost); rootFileNames = configParseResult.fileNames; compilerOptions = configParseResult.options; configFileSpecs = configParseResult.configFileSpecs; From 7c4e755effa696419e39d7f0eeb3bcb51f831e57 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 25 Jan 2018 13:53:08 -0800 Subject: [PATCH 150/163] When applying `// @ts-ignore` fix as a group, apply to a line only once. (#21413) * When applying `// @ts-ignore` fix as a group, apply to a line only once. * Rename line to lineNumber --- .../codefixes/disableJsDiagnostics.ts | 48 ++++++++++--------- .../codeFixDisableJsDiagnosticsInFile_all.ts | 8 ++-- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index a887878bac6..3bcbf886df8 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -1,8 +1,8 @@ /* @internal */ namespace ts.codefix { const fixId = "disableJsDiagnostics"; - const errorCodes = mapDefined(Object.keys(Diagnostics), key => { - const diag = (Diagnostics as MapLike)[key]; + const errorCodes = mapDefined(Object.keys(Diagnostics) as ReadonlyArray, key => { + const diag = Diagnostics[key]; return diag.category === DiagnosticCategory.Error ? diag.code : undefined; }); @@ -19,7 +19,7 @@ namespace ts.codefix { return [{ description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), - changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], + changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter).change])], fixId, }, { @@ -35,17 +35,23 @@ namespace ts.codefix { fixId: undefined, }]; }, - 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, getNewLineOrDefaultFromHost(context.host, context.formatContext.options))); - } - }), + fixIds: [fixId], + getAllCodeActions: context => { + const seenLines = createMap(); // Only need to add `// @ts-ignore` for a line once. + return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { + if (err.start !== undefined) { + const { lineNumber, change } = getIgnoreCommentLocationForLocation(err.file!, err.start, getNewLineOrDefaultFromHost(context.host, context.formatContext.options)); + if (addToSeen(seenLines, lineNumber)) { + changes.push(change); + } + } + }); + }, }); - function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string): TextChange { - const { line } = getLineAndCharacterOfPosition(sourceFile, position); - const lineStartPosition = getStartPositionOfLine(line, sourceFile); + function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string): { lineNumber: number, change: TextChange } { + const { line: lineNumber } = getLineAndCharacterOfPosition(sourceFile, position); + const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); // First try to see if we can put the '// @ts-ignore' on the previous line. @@ -54,19 +60,17 @@ namespace ts.codefix { // if so, we do not want to separate the node from its comment if we can. if (!isInComment(sourceFile, startPosition) && !isInString(sourceFile, startPosition) && !isInTemplateString(sourceFile, startPosition)) { const token = getTouchingToken(sourceFile, startPosition, /*includeJsDocComment*/ false); - const tokenLeadingCommnets = getLeadingCommentRangesOfNode(token, sourceFile); - if (!tokenLeadingCommnets || !tokenLeadingCommnets.length || tokenLeadingCommnets[0].pos >= startPosition) { - return { - span: { start: startPosition, length: 0 }, - newText: `// @ts-ignore${newLineCharacter}` - }; + const tokenLeadingComments = getLeadingCommentRangesOfNode(token, sourceFile); + if (!tokenLeadingComments || !tokenLeadingComments.length || tokenLeadingComments[0].pos >= startPosition) { + return { lineNumber, change: createTextChange(startPosition, 0, `// @ts-ignore${newLineCharacter}`) }; } } // If all fails, add an extra new line immediately before the error span. - return { - span: { start: position, length: 0 }, - newText: `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}` - }; + return { lineNumber, change: createTextChange(position, 0, `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`) }; + } + + function createTextChange(start: number, length: number, newText: string): TextChange { + return { span: { start, length }, newText }; } } diff --git a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts index 9c3785cf2ae..89f94520374 100644 --- a/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts +++ b/tests/cases/fourslash/codeFixDisableJsDiagnosticsInFile_all.ts @@ -6,15 +6,15 @@ // @Filename: a.js ////let x = ""; -////x = 1; -////x = true; +////x = 1; x = true; +////x = []; verify.codeFixAll({ fixId: "disableJsDiagnostics", newFileContent: `let x = ""; // @ts-ignore -x = 1; +x = 1; x = true; // @ts-ignore -x = true;`, +x = [];`, }); From afc588eb9e6322faf55bc28f26994712e3f02261 Mon Sep 17 00:00:00 2001 From: Manoj Patel Date: Thu, 25 Jan 2018 15:35:18 -0800 Subject: [PATCH 151/163] --emitDeclarationsOnly flag to enable declarations only output (#20735) * Add emitOnlyDeclarations flag * Fix name * verifyOptions checking logic * Passing tests * doJsEmitBaseline * Tests !!! --- src/compiler/commandLineParser.ts | 6 ++++ src/compiler/diagnosticMessages.json | 4 +++ src/compiler/emitter.ts | 2 +- src/compiler/program.ts | 14 +++++++- src/compiler/types.ts | 1 + src/harness/harness.ts | 16 ++++++++-- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../reference/declFileEmitDeclarationsOnly.js | 26 +++++++++++++++ .../declFileEmitDeclarationsOnly.symbols | 29 +++++++++++++++++ .../declFileEmitDeclarationsOnly.types | 32 +++++++++++++++++++ ...lFileEmitDeclarationsOnlyError1.errors.txt | 7 ++++ ...declFileEmitDeclarationsOnlyError1.symbols | 4 +++ .../declFileEmitDeclarationsOnlyError1.types | 5 +++ ...lFileEmitDeclarationsOnlyError2.errors.txt | 9 ++++++ ...declFileEmitDeclarationsOnlyError2.symbols | 4 +++ .../declFileEmitDeclarationsOnlyError2.types | 5 +++ .../compiler/declFileEmitDeclarationsOnly.ts | 16 ++++++++++ .../declFileEmitDeclarationsOnlyError1.ts | 4 +++ .../declFileEmitDeclarationsOnlyError2.ts | 5 +++ 20 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnly.js create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnly.symbols create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnly.types create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnlyError1.errors.txt create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnlyError1.symbols create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnlyError1.types create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnlyError2.errors.txt create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnlyError2.symbols create mode 100644 tests/baselines/reference/declFileEmitDeclarationsOnlyError2.types create mode 100644 tests/cases/compiler/declFileEmitDeclarationsOnly.ts create mode 100644 tests/cases/compiler/declFileEmitDeclarationsOnlyError1.ts create mode 100644 tests/cases/compiler/declFileEmitDeclarationsOnlyError2.ts diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index eb9f1e3cf34..9c30cb87a4d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -186,6 +186,12 @@ namespace ts { category: Diagnostics.Basic_Options, description: Diagnostics.Generates_corresponding_d_ts_file, }, + { + name: "emitDeclarationsOnly", + type: "boolean", + category: Diagnostics.Advanced_Options, + description: Diagnostics.Only_emit_d_ts_declaration_files, + }, { name: "sourceMap", type: "boolean", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7a62d6d5aa4..6fffd576e7c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2807,6 +2807,10 @@ "category": "Message", "code": 6013 }, + "Only emit '.d.ts' declaration files.": { + "category": "Message", + "code": 6014 + }, "Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'.": { "category": "Message", "code": 6015 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6f3041666ba..03ff63a4211 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -135,7 +135,7 @@ namespace ts { function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) { // Make sure not to write js file and source map file if any of them cannot be written - if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) { + if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit && !compilerOptions.emitDeclarationsOnly) { if (!emitOnlyDtsFiles) { printSourceFileOrBundle(jsFilePath, sourceMapFilePath, sourceFileOrBundle); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bd45857fc51..23f9b08aa79 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2201,6 +2201,16 @@ namespace ts { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); } + if (options.emitDeclarationsOnly) { + if (!options.declaration) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDeclarationsOnly", "declarations"); + } + + if (options.noEmit) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationsOnly", "noEmit"); + } + } + if (options.emitDecoratorMetadata && !options.experimentalDecorators) { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); @@ -2223,7 +2233,9 @@ namespace ts { const emitHost = getEmitHost(); const emitFilesSeen = createMap(); forEachEmittedFile(emitHost, (emitFileNames) => { - verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); + if (!options.emitDeclarationsOnly) { + verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); + } verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); }); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f6ca2f45d34..481b91c9276 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3967,6 +3967,7 @@ namespace ts { /** configFile is set as non enumerable property so as to avoid checking of json source files */ /* @internal */ readonly configFile?: JsonSourceFile; declaration?: boolean; + emitDeclarationsOnly?: boolean; declarationDir?: string; /* @internal */ diagnostics?: boolean; /* @internal */ extendedDiagnostics?: boolean; diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f2f8587d834..3e6f4cfa163 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1252,8 +1252,18 @@ namespace Harness { options: ts.CompilerOptions, // Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file currentDirectory: string): DeclarationCompilationContext | undefined { - if (options.declaration && result.errors.length === 0 && result.declFilesCode.length !== result.files.length) { - throw new Error("There were no errors and declFiles generated did not match number of js files generated"); + + if (result.errors.length === 0) { + if (options.declaration) { + if (options.emitDeclarationsOnly) { + if (result.files.length > 0 || result.declFilesCode.length === 0) { + throw new Error("Only declaration files should be generated when emitDeclarationsOnly:true"); + } + } + else if (result.declFilesCode.length !== result.files.length) { + throw new Error("There were no errors and declFiles generated did not match number of js files generated"); + } + } } const declInputFiles: TestFile[] = []; @@ -1654,7 +1664,7 @@ namespace Harness { } export function doJsEmitBaseline(baselinePath: string, header: string, options: ts.CompilerOptions, result: CompilerResult, tsConfigFiles: Harness.Compiler.TestFile[], toBeCompiled: Harness.Compiler.TestFile[], otherFiles: Harness.Compiler.TestFile[], harnessSettings: Harness.TestCaseParser.CompilerSettings) { - if (!options.noEmit && result.files.length === 0 && result.errors.length === 0) { + if (!options.noEmit && !options.emitDeclarationsOnly && result.files.length === 0 && result.errors.length === 0) { throw new Error("Expected at least one js file to be emitted or at least one error to be created."); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c1af8fc036f..78f68a80c50 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2266,6 +2266,7 @@ declare namespace ts { charset?: string; checkJs?: boolean; declaration?: boolean; + emitDeclarationsOnly?: boolean; declarationDir?: string; disableSizeLimit?: boolean; downlevelIteration?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4f84f50e3bb..bdcc29af76e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2266,6 +2266,7 @@ declare namespace ts { charset?: string; checkJs?: boolean; declaration?: boolean; + emitDeclarationsOnly?: boolean; declarationDir?: string; disableSizeLimit?: boolean; downlevelIteration?: boolean; diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnly.js b/tests/baselines/reference/declFileEmitDeclarationsOnly.js new file mode 100644 index 00000000000..78d9842fe88 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnly.js @@ -0,0 +1,26 @@ +//// [helloworld.ts] +const Log = { + info(msg: string) {} +} + +class HelloWorld { + constructor(private name: string) { + } + + public hello() { + Log.info(`Hello ${this.name}`); + } +} + + + + +//// [helloworld.d.ts] +declare const Log: { + info(msg: string): void; +}; +declare class HelloWorld { + private name; + constructor(name: string); + hello(): void; +} diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnly.symbols b/tests/baselines/reference/declFileEmitDeclarationsOnly.symbols new file mode 100644 index 00000000000..e2a277fb603 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnly.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/helloworld.ts === +const Log = { +>Log : Symbol(Log, Decl(helloworld.ts, 0, 5)) + + info(msg: string) {} +>info : Symbol(info, Decl(helloworld.ts, 0, 13)) +>msg : Symbol(msg, Decl(helloworld.ts, 1, 7)) +} + +class HelloWorld { +>HelloWorld : Symbol(HelloWorld, Decl(helloworld.ts, 2, 1)) + + constructor(private name: string) { +>name : Symbol(HelloWorld.name, Decl(helloworld.ts, 5, 14)) + } + + public hello() { +>hello : Symbol(HelloWorld.hello, Decl(helloworld.ts, 6, 3)) + + Log.info(`Hello ${this.name}`); +>Log.info : Symbol(info, Decl(helloworld.ts, 0, 13)) +>Log : Symbol(Log, Decl(helloworld.ts, 0, 5)) +>info : Symbol(info, Decl(helloworld.ts, 0, 13)) +>this.name : Symbol(HelloWorld.name, Decl(helloworld.ts, 5, 14)) +>this : Symbol(HelloWorld, Decl(helloworld.ts, 2, 1)) +>name : Symbol(HelloWorld.name, Decl(helloworld.ts, 5, 14)) + } +} + diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnly.types b/tests/baselines/reference/declFileEmitDeclarationsOnly.types new file mode 100644 index 00000000000..9a3cf8cfc70 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnly.types @@ -0,0 +1,32 @@ +=== tests/cases/compiler/helloworld.ts === +const Log = { +>Log : { info(msg: string): void; } +>{ info(msg: string) {}} : { info(msg: string): void; } + + info(msg: string) {} +>info : (msg: string) => void +>msg : string +} + +class HelloWorld { +>HelloWorld : HelloWorld + + constructor(private name: string) { +>name : string + } + + public hello() { +>hello : () => void + + Log.info(`Hello ${this.name}`); +>Log.info(`Hello ${this.name}`) : void +>Log.info : (msg: string) => void +>Log : { info(msg: string): void; } +>info : (msg: string) => void +>`Hello ${this.name}` : string +>this.name : string +>this : this +>name : string + } +} + diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.errors.txt b/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.errors.txt new file mode 100644 index 00000000000..88a154cc508 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.errors.txt @@ -0,0 +1,7 @@ +error TS5052: Option 'emitDeclarationsOnly' cannot be specified without specifying option 'declarations'. + + +!!! error TS5052: Option 'emitDeclarationsOnly' cannot be specified without specifying option 'declarations'. +==== tests/cases/compiler/hello.ts (0 errors) ==== + var hello = "yo!"; + \ No newline at end of file diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.symbols b/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.symbols new file mode 100644 index 00000000000..52b2f9bbe64 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.symbols @@ -0,0 +1,4 @@ +=== tests/cases/compiler/hello.ts === +var hello = "yo!"; +>hello : Symbol(hello, Decl(hello.ts, 0, 3)) + diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.types b/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.types new file mode 100644 index 00000000000..e1603394d7c --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnlyError1.types @@ -0,0 +1,5 @@ +=== tests/cases/compiler/hello.ts === +var hello = "yo!"; +>hello : string +>"yo!" : "yo!" + diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.errors.txt b/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.errors.txt new file mode 100644 index 00000000000..7c067d83ba2 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.errors.txt @@ -0,0 +1,9 @@ +error TS5052: Option 'emitDeclarationsOnly' cannot be specified without specifying option 'declarations'. +error TS5053: Option 'emitDeclarationsOnly' cannot be specified with option 'noEmit'. + + +!!! error TS5052: Option 'emitDeclarationsOnly' cannot be specified without specifying option 'declarations'. +!!! error TS5053: Option 'emitDeclarationsOnly' cannot be specified with option 'noEmit'. +==== tests/cases/compiler/hello.ts (0 errors) ==== + var hello = "yo!"; + \ No newline at end of file diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.symbols b/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.symbols new file mode 100644 index 00000000000..52b2f9bbe64 --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.symbols @@ -0,0 +1,4 @@ +=== tests/cases/compiler/hello.ts === +var hello = "yo!"; +>hello : Symbol(hello, Decl(hello.ts, 0, 3)) + diff --git a/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.types b/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.types new file mode 100644 index 00000000000..e1603394d7c --- /dev/null +++ b/tests/baselines/reference/declFileEmitDeclarationsOnlyError2.types @@ -0,0 +1,5 @@ +=== tests/cases/compiler/hello.ts === +var hello = "yo!"; +>hello : string +>"yo!" : "yo!" + diff --git a/tests/cases/compiler/declFileEmitDeclarationsOnly.ts b/tests/cases/compiler/declFileEmitDeclarationsOnly.ts new file mode 100644 index 00000000000..e55496e4ca3 --- /dev/null +++ b/tests/cases/compiler/declFileEmitDeclarationsOnly.ts @@ -0,0 +1,16 @@ +// @declaration: true +// @emitDeclarationsOnly: true + +// @filename: helloworld.ts +const Log = { + info(msg: string) {} +} + +class HelloWorld { + constructor(private name: string) { + } + + public hello() { + Log.info(`Hello ${this.name}`); + } +} diff --git a/tests/cases/compiler/declFileEmitDeclarationsOnlyError1.ts b/tests/cases/compiler/declFileEmitDeclarationsOnlyError1.ts new file mode 100644 index 00000000000..477934a7b2a --- /dev/null +++ b/tests/cases/compiler/declFileEmitDeclarationsOnlyError1.ts @@ -0,0 +1,4 @@ +// @emitDeclarationsOnly: true + +// @filename: hello.ts +var hello = "yo!"; diff --git a/tests/cases/compiler/declFileEmitDeclarationsOnlyError2.ts b/tests/cases/compiler/declFileEmitDeclarationsOnlyError2.ts new file mode 100644 index 00000000000..994a6a5dbd8 --- /dev/null +++ b/tests/cases/compiler/declFileEmitDeclarationsOnlyError2.ts @@ -0,0 +1,5 @@ +// @noEmit: true +// @emitDeclarationsOnly: true + +// @filename: hello.ts +var hello = "yo!"; From 9677b0641cc5ba7d8b701b4f892ed7e54ceaee9a Mon Sep 17 00:00:00 2001 From: Adnan Chowdhury Date: Thu, 25 Jan 2018 16:17:58 -0800 Subject: [PATCH 152/163] Implement fallback hashing algorithm when crypto module is not available (#19941) * Implement fallback hashing algorithm when crypto module is not available * Fix lint errors * Expose method internally and use in watch.ts * Simplify syntax; Remove fallback from watch.ts --- src/compiler/sys.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 3256eef4dc2..ef1a3f8fd49 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -126,10 +126,32 @@ namespace ts { const _fs = require("fs"); const _path = require("path"); const _os = require("os"); - const _crypto = require("crypto"); + // crypto can be absent on reduced node installations + let _crypto: any; + try { + _crypto = require("crypto"); + } + catch { + _crypto = undefined; + } const useNonPollingWatchers = process.env.TSC_NONPOLLING_WATCHER; + /** + * djb2 hashing algorithm + * http://www.cse.yorku.ca/~oz/hash.html + */ + function generateDjb2Hash(data: string): string { + const chars = data.split("").map(str => str.charCodeAt(0)); + return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`; + } + + function createMD5HashUsingNativeCrypto(data: string) { + const hash = _crypto.createHash("md5"); + hash.update(data); + return hash.digest("hex"); + } + function createWatchedFileSet() { const dirWatchers = createMap(); // One file can have multiple watchers @@ -493,11 +515,7 @@ namespace ts { return undefined; } }, - createHash(data) { - const hash = _crypto.createHash("md5"); - hash.update(data); - return hash.digest("hex"); - }, + createHash: _crypto ? createMD5HashUsingNativeCrypto : generateDjb2Hash, getMemoryUsage() { if (global.gc) { global.gc(); From f0ba16c9a558bf11a82b517fd789d7d41f88c0da Mon Sep 17 00:00:00 2001 From: Matt McCutchen Date: Thu, 25 Jan 2018 19:18:35 -0500 Subject: [PATCH 153/163] Unused type parameters should be checked by --noUnusedParameters, not (#21167) --noUnusedLocals. Fixes #20568. --- src/compiler/checker.ts | 4 +-- ...tersCheckedByNoUnusedParameters.errors.txt | 33 +++++++++++++++++++ ...peParametersCheckedByNoUnusedParameters.js | 25 ++++++++++++++ ...ametersCheckedByNoUnusedParameters.symbols | 27 +++++++++++++++ ...arametersCheckedByNoUnusedParameters.types | 28 ++++++++++++++++ ...ypeParametersNotCheckedByNoUnusedLocals.js | 25 ++++++++++++++ ...rametersNotCheckedByNoUnusedLocals.symbols | 27 +++++++++++++++ ...ParametersNotCheckedByNoUnusedLocals.types | 28 ++++++++++++++++ ...sedTypeParametersWithUnderscore.errors.txt | 14 ++++++-- .../unusedTypeParametersWithUnderscore.js | 8 ++++- ...unusedTypeParametersWithUnderscore.symbols | 14 +++++++- .../unusedTypeParametersWithUnderscore.types | 15 ++++++++- ...peParametersCheckedByNoUnusedParameters.ts | 13 ++++++++ ...ypeParametersNotCheckedByNoUnusedLocals.ts | 13 ++++++++ .../unusedTypeParametersWithUnderscore.ts | 8 +++-- .../fourslash/unusedTypeParametersInClass1.ts | 2 +- .../fourslash/unusedTypeParametersInClass2.ts | 2 +- .../fourslash/unusedTypeParametersInClass3.ts | 2 +- .../unusedTypeParametersInFunction1.ts | 2 +- .../unusedTypeParametersInFunction2.ts | 2 +- .../unusedTypeParametersInFunction3.ts | 2 +- .../unusedTypeParametersInLambda4.ts | 2 +- .../unusedTypeParametersInMethod1.ts | 2 +- .../unusedTypeParametersInMethod2.ts | 2 +- .../unusedTypeParametersInMethods1.ts | 2 +- 25 files changed, 283 insertions(+), 19 deletions(-) create mode 100644 tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.errors.txt create mode 100644 tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.js create mode 100644 tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.symbols create mode 100644 tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.types create mode 100644 tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.js create mode 100644 tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.symbols create mode 100644 tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.types create mode 100644 tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts create mode 100644 tests/cases/compiler/unusedTypeParametersNotCheckedByNoUnusedLocals.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 26f0baec5ec..ba3c3e6b83c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20967,7 +20967,7 @@ namespace ts { error(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local)); } } - else if (compilerOptions.noUnusedLocals) { + else if (local.flags & SymbolFlags.TypeParameter ? compilerOptions.noUnusedParameters : compilerOptions.noUnusedLocals) { forEach(local.declarations, d => errorUnusedLocal(d, symbolName(local))); } } @@ -21042,7 +21042,7 @@ namespace ts { } function checkUnusedTypeParameters(node: ClassDeclaration | ClassExpression | FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction | ConstructorDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration) { - if (compilerOptions.noUnusedLocals && !(node.flags & NodeFlags.Ambient)) { + if (compilerOptions.noUnusedParameters && !(node.flags & NodeFlags.Ambient)) { if (node.typeParameters) { // Only report errors on the last declaration for the type parameter container; // this ensures that all uses have been accounted for. diff --git a/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.errors.txt b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.errors.txt new file mode 100644 index 00000000000..e5bc774b706 --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.errors.txt @@ -0,0 +1,33 @@ +tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(1,12): error TS6133: 'T' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(3,8): error TS6133: 'T' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(5,13): error TS6133: 'T' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(7,9): error TS6133: 'T' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(8,14): error TS6133: 'V' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts(11,10): error TS6133: 'T' is declared but its value is never read. + + +==== tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts (6 errors) ==== + function f() { } + ~ +!!! error TS6133: 'T' is declared but its value is never read. + + type T = { }; + ~ +!!! error TS6133: 'T' is declared but its value is never read. + + interface I { }; + ~ +!!! error TS6133: 'T' is declared but its value is never read. + + class C { + ~ +!!! error TS6133: 'T' is declared but its value is never read. + public m() { } + ~ +!!! error TS6133: 'V' is declared but its value is never read. + }; + + let l = () => { }; + ~ +!!! error TS6133: 'T' is declared but its value is never read. + \ No newline at end of file diff --git a/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.js b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.js new file mode 100644 index 00000000000..50a7dfcfeac --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.js @@ -0,0 +1,25 @@ +//// [unusedTypeParametersCheckedByNoUnusedParameters.ts] +function f() { } + +type T = { }; + +interface I { }; + +class C { + public m() { } +}; + +let l = () => { }; + + +//// [unusedTypeParametersCheckedByNoUnusedParameters.js] +function f() { } +; +var C = /** @class */ (function () { + function C() { + } + C.prototype.m = function () { }; + return C; +}()); +; +var l = function () { }; diff --git a/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.symbols b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.symbols new file mode 100644 index 00000000000..916329800ef --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.symbols @@ -0,0 +1,27 @@ +=== tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts === +function f() { } +>f : Symbol(f, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 0, 0)) +>T : Symbol(T, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 0, 11)) + +type T = { }; +>T : Symbol(T, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 0, 19)) +>T : Symbol(T, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 2, 7)) + +interface I { }; +>I : Symbol(I, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 2, 16)) +>T : Symbol(T, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 4, 12)) + +class C { +>C : Symbol(C, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 4, 19)) +>T : Symbol(T, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 6, 8)) + + public m() { } +>m : Symbol(C.m, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 6, 12)) +>V : Symbol(V, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 7, 13)) + +}; + +let l = () => { }; +>l : Symbol(l, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 10, 3)) +>T : Symbol(T, Decl(unusedTypeParametersCheckedByNoUnusedParameters.ts, 10, 9)) + diff --git a/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.types b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.types new file mode 100644 index 00000000000..eb49d0a731f --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersCheckedByNoUnusedParameters.types @@ -0,0 +1,28 @@ +=== tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts === +function f() { } +>f : () => void +>T : T + +type T = { }; +>T : {} +>T : T + +interface I { }; +>I : I +>T : T + +class C { +>C : C +>T : T + + public m() { } +>m : () => void +>V : V + +}; + +let l = () => { }; +>l : () => void +>() => { } : () => void +>T : T + diff --git a/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.js b/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.js new file mode 100644 index 00000000000..2b215504bda --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.js @@ -0,0 +1,25 @@ +//// [unusedTypeParametersNotCheckedByNoUnusedLocals.ts] +function f() { } + +type T = { }; + +interface I { }; + +class C { + public m() { } +}; + +let l = () => { }; + + +//// [unusedTypeParametersNotCheckedByNoUnusedLocals.js] +function f() { } +; +var C = /** @class */ (function () { + function C() { + } + C.prototype.m = function () { }; + return C; +}()); +; +var l = function () { }; diff --git a/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.symbols b/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.symbols new file mode 100644 index 00000000000..27bc52564d5 --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.symbols @@ -0,0 +1,27 @@ +=== tests/cases/compiler/unusedTypeParametersNotCheckedByNoUnusedLocals.ts === +function f() { } +>f : Symbol(f, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 0, 0)) +>T : Symbol(T, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 0, 11)) + +type T = { }; +>T : Symbol(T, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 0, 19)) +>T : Symbol(T, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 2, 7)) + +interface I { }; +>I : Symbol(I, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 2, 16)) +>T : Symbol(T, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 4, 12)) + +class C { +>C : Symbol(C, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 4, 19)) +>T : Symbol(T, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 6, 8)) + + public m() { } +>m : Symbol(C.m, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 6, 12)) +>V : Symbol(V, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 7, 13)) + +}; + +let l = () => { }; +>l : Symbol(l, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 10, 3)) +>T : Symbol(T, Decl(unusedTypeParametersNotCheckedByNoUnusedLocals.ts, 10, 9)) + diff --git a/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.types b/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.types new file mode 100644 index 00000000000..bed3e964d2c --- /dev/null +++ b/tests/baselines/reference/unusedTypeParametersNotCheckedByNoUnusedLocals.types @@ -0,0 +1,28 @@ +=== tests/cases/compiler/unusedTypeParametersNotCheckedByNoUnusedLocals.ts === +function f() { } +>f : () => void +>T : T + +type T = { }; +>T : {} +>T : T + +interface I { }; +>I : I +>T : T + +class C { +>C : C +>T : T + + public m() { } +>m : () => void +>V : V + +}; + +let l = () => { }; +>l : () => void +>() => { } : () => void +>T : T + diff --git a/tests/baselines/reference/unusedTypeParametersWithUnderscore.errors.txt b/tests/baselines/reference/unusedTypeParametersWithUnderscore.errors.txt index cd44a68b78c..b41b62694f8 100644 --- a/tests/baselines/reference/unusedTypeParametersWithUnderscore.errors.txt +++ b/tests/baselines/reference/unusedTypeParametersWithUnderscore.errors.txt @@ -2,9 +2,11 @@ tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(1,16): error TS6133: tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(3,12): error TS6133: 'U' is declared but its value is never read. tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(5,17): error TS6133: 'U' is declared but its value is never read. tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(7,13): error TS6133: 'U' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(8,18): error TS6133: 'W' is declared but its value is never read. +tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(11,14): error TS6133: 'U' is declared but its value is never read. -==== tests/cases/compiler/unusedTypeParametersWithUnderscore.ts (4 errors) ==== +==== tests/cases/compiler/unusedTypeParametersWithUnderscore.ts (6 errors) ==== function f<_T, U>() { } ~ !!! error TS6133: 'U' is declared but its value is never read. @@ -17,7 +19,15 @@ tests/cases/compiler/unusedTypeParametersWithUnderscore.ts(7,13): error TS6133: ~ !!! error TS6133: 'U' is declared but its value is never read. - class C<_T, U> { }; + class C<_T, U> { ~ !!! error TS6133: 'U' is declared but its value is never read. + public m<_V, W>() { } + ~ +!!! error TS6133: 'W' is declared but its value is never read. + }; + + let l = <_T, U>() => { }; + ~ +!!! error TS6133: 'U' is declared but its value is never read. \ No newline at end of file diff --git a/tests/baselines/reference/unusedTypeParametersWithUnderscore.js b/tests/baselines/reference/unusedTypeParametersWithUnderscore.js index 095cf2da85f..bade5f6f95f 100644 --- a/tests/baselines/reference/unusedTypeParametersWithUnderscore.js +++ b/tests/baselines/reference/unusedTypeParametersWithUnderscore.js @@ -5,7 +5,11 @@ type T<_T, U> = { }; interface I<_T, U> { }; -class C<_T, U> { }; +class C<_T, U> { + public m<_V, W>() { } +}; + +let l = <_T, U>() => { }; //// [unusedTypeParametersWithUnderscore.js] @@ -14,6 +18,8 @@ function f() { } var C = /** @class */ (function () { function C() { } + C.prototype.m = function () { }; return C; }()); ; +var l = function () { }; diff --git a/tests/baselines/reference/unusedTypeParametersWithUnderscore.symbols b/tests/baselines/reference/unusedTypeParametersWithUnderscore.symbols index 4429bc1bd25..3f8444d7069 100644 --- a/tests/baselines/reference/unusedTypeParametersWithUnderscore.symbols +++ b/tests/baselines/reference/unusedTypeParametersWithUnderscore.symbols @@ -14,8 +14,20 @@ interface I<_T, U> { }; >_T : Symbol(_T, Decl(unusedTypeParametersWithUnderscore.ts, 4, 12)) >U : Symbol(U, Decl(unusedTypeParametersWithUnderscore.ts, 4, 15)) -class C<_T, U> { }; +class C<_T, U> { >C : Symbol(C, Decl(unusedTypeParametersWithUnderscore.ts, 4, 23)) >_T : Symbol(_T, Decl(unusedTypeParametersWithUnderscore.ts, 6, 8)) >U : Symbol(U, Decl(unusedTypeParametersWithUnderscore.ts, 6, 11)) + public m<_V, W>() { } +>m : Symbol(C.m, Decl(unusedTypeParametersWithUnderscore.ts, 6, 16)) +>_V : Symbol(_V, Decl(unusedTypeParametersWithUnderscore.ts, 7, 13)) +>W : Symbol(W, Decl(unusedTypeParametersWithUnderscore.ts, 7, 16)) + +}; + +let l = <_T, U>() => { }; +>l : Symbol(l, Decl(unusedTypeParametersWithUnderscore.ts, 10, 3)) +>_T : Symbol(_T, Decl(unusedTypeParametersWithUnderscore.ts, 10, 9)) +>U : Symbol(U, Decl(unusedTypeParametersWithUnderscore.ts, 10, 12)) + diff --git a/tests/baselines/reference/unusedTypeParametersWithUnderscore.types b/tests/baselines/reference/unusedTypeParametersWithUnderscore.types index bfafa3c9b38..9fbb3b974df 100644 --- a/tests/baselines/reference/unusedTypeParametersWithUnderscore.types +++ b/tests/baselines/reference/unusedTypeParametersWithUnderscore.types @@ -14,8 +14,21 @@ interface I<_T, U> { }; >_T : _T >U : U -class C<_T, U> { }; +class C<_T, U> { >C : C<_T, U> >_T : _T +>U : U + + public m<_V, W>() { } +>m : <_V, W>() => void +>_V : _V +>W : W + +}; + +let l = <_T, U>() => { }; +>l : <_T, U>() => void +><_T, U>() => { } : <_T, U>() => void +>_T : _T >U : U diff --git a/tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts b/tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts new file mode 100644 index 00000000000..b2c132a399e --- /dev/null +++ b/tests/cases/compiler/unusedTypeParametersCheckedByNoUnusedParameters.ts @@ -0,0 +1,13 @@ +//@noUnusedParameters:true + +function f() { } + +type T = { }; + +interface I { }; + +class C { + public m() { } +}; + +let l = () => { }; diff --git a/tests/cases/compiler/unusedTypeParametersNotCheckedByNoUnusedLocals.ts b/tests/cases/compiler/unusedTypeParametersNotCheckedByNoUnusedLocals.ts new file mode 100644 index 00000000000..b305f7a8d72 --- /dev/null +++ b/tests/cases/compiler/unusedTypeParametersNotCheckedByNoUnusedLocals.ts @@ -0,0 +1,13 @@ +//@noUnusedLocals:true + +function f() { } + +type T = { }; + +interface I { }; + +class C { + public m() { } +}; + +let l = () => { }; diff --git a/tests/cases/compiler/unusedTypeParametersWithUnderscore.ts b/tests/cases/compiler/unusedTypeParametersWithUnderscore.ts index dc66534118f..ed4c23411db 100644 --- a/tests/cases/compiler/unusedTypeParametersWithUnderscore.ts +++ b/tests/cases/compiler/unusedTypeParametersWithUnderscore.ts @@ -1,4 +1,4 @@ -//@noUnusedLocals:true +//@noUnusedParameters:true function f<_T, U>() { } @@ -6,4 +6,8 @@ type T<_T, U> = { }; interface I<_T, U> { }; -class C<_T, U> { }; +class C<_T, U> { + public m<_V, W>() { } +}; + +let l = <_T, U>() => { }; diff --git a/tests/cases/fourslash/unusedTypeParametersInClass1.ts b/tests/cases/fourslash/unusedTypeParametersInClass1.ts index 327558922e7..86beb10a445 100644 --- a/tests/cases/fourslash/unusedTypeParametersInClass1.ts +++ b/tests/cases/fourslash/unusedTypeParametersInClass1.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true ////[|class greeter |] { ////} diff --git a/tests/cases/fourslash/unusedTypeParametersInClass2.ts b/tests/cases/fourslash/unusedTypeParametersInClass2.ts index 4f78aa3fbe9..23a4c39602d 100644 --- a/tests/cases/fourslash/unusedTypeParametersInClass2.ts +++ b/tests/cases/fourslash/unusedTypeParametersInClass2.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true ////[|class greeter |] { //// public a: X; ////} diff --git a/tests/cases/fourslash/unusedTypeParametersInClass3.ts b/tests/cases/fourslash/unusedTypeParametersInClass3.ts index f0b12f868f1..8312830407b 100644 --- a/tests/cases/fourslash/unusedTypeParametersInClass3.ts +++ b/tests/cases/fourslash/unusedTypeParametersInClass3.ts @@ -1,7 +1,7 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true ////[|class greeter |] { //// public a: X; //// public b: Z; diff --git a/tests/cases/fourslash/unusedTypeParametersInFunction1.ts b/tests/cases/fourslash/unusedTypeParametersInFunction1.ts index a33ecf60ea6..686ced26503 100644 --- a/tests/cases/fourslash/unusedTypeParametersInFunction1.ts +++ b/tests/cases/fourslash/unusedTypeParametersInFunction1.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// [|function f1() {}|] verify.codeFix({ diff --git a/tests/cases/fourslash/unusedTypeParametersInFunction2.ts b/tests/cases/fourslash/unusedTypeParametersInFunction2.ts index 9c0d9e7b098..87d7e90207b 100644 --- a/tests/cases/fourslash/unusedTypeParametersInFunction2.ts +++ b/tests/cases/fourslash/unusedTypeParametersInFunction2.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// [|function f1(a: X) {a}|] verify.codeFix({ diff --git a/tests/cases/fourslash/unusedTypeParametersInFunction3.ts b/tests/cases/fourslash/unusedTypeParametersInFunction3.ts index 0c8ea449731..0a3bc6bb79b 100644 --- a/tests/cases/fourslash/unusedTypeParametersInFunction3.ts +++ b/tests/cases/fourslash/unusedTypeParametersInFunction3.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// [|function f1(a: X) {a;var b:Z;b}|] verify.codeFix({ diff --git a/tests/cases/fourslash/unusedTypeParametersInLambda4.ts b/tests/cases/fourslash/unusedTypeParametersInLambda4.ts index b3e3f4b11f3..b9a258bac3d 100644 --- a/tests/cases/fourslash/unusedTypeParametersInLambda4.ts +++ b/tests/cases/fourslash/unusedTypeParametersInLambda4.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// class A { //// public x: T; //// } diff --git a/tests/cases/fourslash/unusedTypeParametersInMethod1.ts b/tests/cases/fourslash/unusedTypeParametersInMethod1.ts index 260a9399650..a3afd76251d 100644 --- a/tests/cases/fourslash/unusedTypeParametersInMethod1.ts +++ b/tests/cases/fourslash/unusedTypeParametersInMethod1.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// class C1 { //// [|f1()|] {} //// } diff --git a/tests/cases/fourslash/unusedTypeParametersInMethod2.ts b/tests/cases/fourslash/unusedTypeParametersInMethod2.ts index 377642c5bd5..3ca7a0f8a87 100644 --- a/tests/cases/fourslash/unusedTypeParametersInMethod2.ts +++ b/tests/cases/fourslash/unusedTypeParametersInMethod2.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// class C1 { //// [|f1(a: U)|] {a;} //// } diff --git a/tests/cases/fourslash/unusedTypeParametersInMethods1.ts b/tests/cases/fourslash/unusedTypeParametersInMethods1.ts index 4a538fb4b39..d394d9af772 100644 --- a/tests/cases/fourslash/unusedTypeParametersInMethods1.ts +++ b/tests/cases/fourslash/unusedTypeParametersInMethods1.ts @@ -1,6 +1,6 @@ /// -// @noUnusedLocals: true +// @noUnusedParameters: true //// class A { //// [|public f1(a: X)|] { a; var b: Z; b } //// } From cae4bc5e83b38e724a13a3f8d86920b7c167a757 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 25 Jan 2018 17:48:22 -0800 Subject: [PATCH 154/163] Move createTextChange to services/utilities.ts (#21416) * Move createTextChange to services/utilities.ts * Use separate functions instead of overloads --- src/services/codefixes/disableJsDiagnostics.ts | 18 +++++------------- src/services/codefixes/fixJSDocTypes.ts | 2 +- src/services/codefixes/inferFromUsage.ts | 2 +- src/services/formatting/formatting.ts | 8 ++------ src/services/textChanges.ts | 5 +---- src/services/utilities.ts | 8 ++++++++ 6 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 3bcbf886df8..091ee07a9cc 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -24,13 +24,9 @@ namespace ts.codefix { }, { description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file), - changes: [createFileTextChanges(sourceFile.fileName, [{ - span: { - start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0, - length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0 - }, - newText: `// @ts-nocheck${newLineCharacter}` - }])], + changes: [createFileTextChanges(sourceFile.fileName, [ + createTextChange(sourceFile.checkJsDirective ? createTextSpanFromBounds(sourceFile.checkJsDirective.pos, sourceFile.checkJsDirective.end) : createTextSpan(0, 0), `// @ts-nocheck${newLineCharacter}`), + ])], // fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file. fixId: undefined, }]; @@ -62,15 +58,11 @@ namespace ts.codefix { const token = getTouchingToken(sourceFile, startPosition, /*includeJsDocComment*/ false); const tokenLeadingComments = getLeadingCommentRangesOfNode(token, sourceFile); if (!tokenLeadingComments || !tokenLeadingComments.length || tokenLeadingComments[0].pos >= startPosition) { - return { lineNumber, change: createTextChange(startPosition, 0, `// @ts-ignore${newLineCharacter}`) }; + return { lineNumber, change: createTextChangeFromStartLength(startPosition, 0, `// @ts-ignore${newLineCharacter}`) }; } } // If all fails, add an extra new line immediately before the error span. - return { lineNumber, change: createTextChange(position, 0, `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`) }; - } - - function createTextChange(start: number, length: number, newText: string): TextChange { - return { span: { start, length }, newText }; + return { lineNumber, change: createTextChangeFromStartLength(position, 0, `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`) }; } } diff --git a/src/services/codefixes/fixJSDocTypes.ts b/src/services/codefixes/fixJSDocTypes.ts index 8c43ed0cc7f..3158e90ce96 100644 --- a/src/services/codefixes/fixJSDocTypes.ts +++ b/src/services/codefixes/fixJSDocTypes.ts @@ -50,7 +50,7 @@ namespace ts.codefix { } function createChange(declaration: TypeNode, sourceFile: SourceFile, newText: string): TextChange { - return { span: createTextSpanFromBounds(declaration.getStart(sourceFile), declaration.getEnd()), newText }; + return createTextChange(createTextSpanFromNode(declaration, sourceFile), newText); } function typeString(type: Type, checker: TypeChecker): string { diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 6f6b2f3c61e..2f3dae01acf 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -191,7 +191,7 @@ namespace ts.codefix { function makeChange(declaration: Declaration, start: number, type: Type | undefined, program: Program): TextChange | undefined { const typeString = type && typeToString(type, declaration, program.getTypeChecker()); - return typeString === undefined ? undefined : { span: createTextSpan(start, 0), newText: `: ${typeString}` }; + return typeString === undefined ? undefined : createTextChangeFromStartLength(start, 0, `: ${typeString}`); } function getReferences(token: PropertyName | Token, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Identifier[] { diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 818e2815944..22446a98ce2 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1078,19 +1078,15 @@ namespace ts.formatting { trimTrailingWhitespacesForLines(startLine, endLine + 1, previousRange); } - function newTextChange(start: number, len: number, newText: string): TextChange { - return { span: createTextSpan(start, len), newText }; - } - function recordDelete(start: number, len: number) { if (len) { - edits.push(newTextChange(start, len, "")); + edits.push(createTextChangeFromStartLength(start, len, "")); } } function recordReplace(start: number, len: number, newText: string) { if (len || newText) { - edits.push(newTextChange(start, len, newText)); + edits.push(createTextChangeFromStartLength(start, len, newText)); } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 3674cd31b97..83b434aac7d 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -620,10 +620,7 @@ namespace ts.textChanges { const sourceFile = changesInFile[0].sourceFile; const fileTextChanges: FileTextChanges = { fileName: sourceFile.fileName, textChanges: [] }; for (const c of ChangeTracker.normalize(changesInFile)) { - fileTextChanges.textChanges.push({ - span: this.computeSpan(c, sourceFile), - newText: this.computeNewText(c, sourceFile) - }); + fileTextChanges.textChanges.push(createTextChange(this.computeSpan(c, sourceFile), this.computeNewText(c, sourceFile))); } fileChangesList.push(fileTextChanges); }); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index c724fa5643d..e30fd8cfe2d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1067,6 +1067,14 @@ namespace ts { return createTextSpanFromBounds(range.pos, range.end); } + export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange { + return createTextChange(createTextSpan(start, length), newText); + } + + export function createTextChange(span: TextSpan, newText: string): TextChange { + return { span, newText }; + } + export const typeKeywords: ReadonlyArray = [ SyntaxKind.AnyKeyword, SyntaxKind.BooleanKeyword, From 7b855d8f67bfafbf4f481f5a8abe0b50a8993553 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 26 Jan 2018 17:11:10 -0800 Subject: [PATCH 155/163] Fix dependency for 'publish-nightly'. --- Jakefile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jakefile.js b/Jakefile.js index 9bef7946722..87fca36a841 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -594,7 +594,7 @@ task("configure-insiders", [configurePrereleaseJs], function () { }, { async: true }); desc("Configure, build, test, and publish the insiders release."); -task("publish-insiders", ["configure-nightly", "LKG", "clean", "setDebugMode", "runtests-parallel"], function () { +task("publish-insiders", ["configure-insiders", "LKG", "clean", "setDebugMode", "runtests-parallel"], function () { var cmd = "npm publish --tag insiders"; console.log(cmd); exec(cmd); From e3e849687a6550430f3c1baa886ce31b44dfe952 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 28 Jan 2018 15:14:34 -0800 Subject: [PATCH 156/163] Skip unnecessary type and symbol instantiations --- src/compiler/checker.ts | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba3c3e6b83c..8b579474382 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8330,11 +8330,18 @@ namespace ts { function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { - const result: T[] = []; - for (const v of items) { - result.push(instantiator(v, mapper)); + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const mapped = instantiator(item, mapper); + if (item !== mapped) { + const result = i === 0 ? [] : items.slice(0, i); + result.push(mapped); + for (i++; i < items.length; i++) { + result.push(instantiator(items[i], mapper)); + } + return result; + } } - return result; } return items; } @@ -8456,8 +8463,13 @@ namespace ts { } function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { + const links = getSymbolLinks(symbol); + if (links.type && !maybeTypeOfKind(links.type, TypeFlags.Object | TypeFlags.TypeVariable | TypeFlags.Index)) { + // If the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + return symbol; + } if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - const links = getSymbolLinks(symbol); // If symbol being instantiated is itself a instantiation, fetch the original target and combine the // type mappers. This ensures that original type identities are properly preserved and that aliases // always reference a non-aliases. @@ -8600,14 +8612,20 @@ namespace ts { return getAnonymousTypeInstantiation(type, mapper); } if ((type).objectFlags & ObjectFlags.Reference) { - return createTypeReference((type).target, instantiateTypes((type).typeArguments, mapper)); + const typeArguments = (type).typeArguments; + const newTypeArguments = instantiateTypes(typeArguments, mapper); + return newTypeArguments !== typeArguments ? createTypeReference((type).target, newTypeArguments) : type; } } if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { - return getUnionType(instantiateTypes((type).types, mapper), UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); + const types = (type).types; + const newTypes = instantiateTypes(types, mapper); + return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; } if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(instantiateTypes((type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); + const types = (type).types; + const newTypes = instantiateTypes(types, mapper); + return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; } if (type.flags & TypeFlags.Index) { return getIndexType(instantiateType((type).type, mapper)); From 27ff2b0c9f53fd37a2e378a32e0e1487b20d6e95 Mon Sep 17 00:00:00 2001 From: csigs Date: Mon, 29 Jan 2018 23:10:53 +0000 Subject: [PATCH 157/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index 99776a461cc..3507c91d278 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -19,7 +19,7 @@ - + @@ -5112,6 +5112,15 @@ + + + + + + + + + @@ -8656,7 +8665,7 @@ - + From 76bf47007201923503aee6b1214be12c28450530 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 29 Jan 2018 15:41:21 -0800 Subject: [PATCH 158/163] Simplify isEmittedFile check instead of iterating through all source files. Fixes #21459 --- src/compiler/program.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 23f9b08aa79..6ea50b30f6f 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -2363,9 +2363,30 @@ namespace ts { return false; } - return forEachEmittedFile(getEmitHost(), ({ jsFilePath, declarationFilePath }) => - isSameFile(jsFilePath, file) || - (declarationFilePath && isSameFile(declarationFilePath, file))); + // If this is source file, its not emitted file + const filePath = toPath(file); + if (getSourceFileByPath(filePath)) { + return false; + } + + // If options have --outFile or --out just check that + const out = options.outFile || options.out; + if (out) { + return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); + } + + // If --outDir, check if file is in that directory + if (options.outDir) { + return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); + } + + if (fileExtensionIsOneOf(filePath, supportedJavascriptExtensions) || fileExtensionIs(filePath, Extension.Dts)) { + // Otherwise just check if sourceFile with the name exists + const filePathWithoutExtension = removeFileExtension(filePath); + return !!getSourceFileByPath(combinePaths(filePathWithoutExtension, Extension.Ts) as Path) || + !!getSourceFileByPath(combinePaths(filePathWithoutExtension, Extension.Tsx) as Path); + } + return false; } function isSameFile(file1: string, file2: string) { From 659424e33f08e3b9ac5ae41ca941deba5b58f528 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 29 Jan 2018 16:02:16 -0800 Subject: [PATCH 159/163] Log more info about platform for further diagnosis --- src/server/server.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/server/server.ts b/src/server/server.ts index 8e53c4d5109..722193829f9 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -33,6 +33,7 @@ namespace ts.server { const os: { homedir?(): string; tmpdir(): string; + platform(): string; } = require("os"); interface NodeSocket { @@ -824,8 +825,9 @@ namespace ts.server { const logger = createLogger(); const sys = ts.sys; + const nodeVersion = getNodeMajorVersion(); // use watchGuard process on Windows when node version is 4 or later - const useWatchGuard = process.platform === "win32" && getNodeMajorVersion() >= 4; + const useWatchGuard = process.platform === "win32" && nodeVersion >= 4; const originalWatchDirectory: ServerHost["watchDirectory"] = sys.watchDirectory.bind(sys); const noopWatcher: FileWatcher = { close: noop }; // This is the function that catches the exceptions when watching directory, and yet lets project service continue to function @@ -980,8 +982,9 @@ namespace ts.server { }; logger.info(`Starting TS Server`); - logger.info(`Version: ${versionMajorMinor}`); + logger.info(`Version: ${version}`); logger.info(`Arguments: ${process.argv.join(" ")}`); + logger.info(`Platform: ${os.platform()} NodeVersion: ${nodeVersion} CaseSensitive: ${sys.useCaseSensitiveFileNames}`); const ioSession = new IOSession(options); process.on("uncaughtException", err => { From a9fc00188d3d815eae61561ae2c278d7df3d6135 Mon Sep 17 00:00:00 2001 From: TravCav Date: Tue, 30 Jan 2018 01:36:14 -0500 Subject: [PATCH 160/163] fixes tslint warning (#21469) --- Gulpfile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gulpfile.ts b/Gulpfile.ts index 0c410bdfb08..7222c9bcd6a 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -1115,7 +1115,7 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: const fileMatcher = cmdLineOptions.files; const files = fileMatcher ? `src/**/${fileMatcher}` - : "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude 'src/lib/*.d.ts'"; + : `Gulpfile.ts "scripts/generateLocalizedDiagnosticMessages.ts" "scripts/tslint/**/*.ts" "src/**/*.ts" --exclude "src/lib/*.d.ts"`; const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`; console.log("Linting: " + cmd); child_process.execSync(cmd, { stdio: [0, 1, 2] }); From d0721e45b1f71bb3a2210861043a0a88581f1a44 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 29 Jan 2018 23:29:37 -0800 Subject: [PATCH 161/163] Update authors --- .mailmap | 10 +++++++--- AUTHORS.md | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.mailmap b/.mailmap index b246515e467..bc99382425f 100644 --- a/.mailmap +++ b/.mailmap @@ -291,14 +291,13 @@ Zeeshan Ahmed Orta # Orta Therox IdeaHunter # @IdeaHunter kujon # Jakub Korzeniowski -Matt @begincalendar +Matt # @begincalendar meyer # @meyer micbou # @micbou Alan Agius Alex Khomchenko Oussama Ben Brahim benbraou Cameron Taggart -csigs csigs Eugene Timokhov Kris Zyp Jing Ma @@ -312,4 +311,9 @@ Stanislav Iliev Wenlu Wang <805037171@163.com> wenlu.wang <805037171@163.com> kingwl <805037171@163.com> Wilson Hobbs Yuval Greenfield -Daniel # @nieltg \ No newline at end of file +Daniel # @nieltg +Adnan Chowdhury +Esakki Raj +Jack Williams +Philippe Voinov +Stephan Ginthör <26004708+Lazarus535@users.noreply.github.com> \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 7662a5642e7..839971f37eb 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -3,6 +3,7 @@ TypeScript is authored by: * Abubaker Bashir * Adam Freidin * Adi Dahiya +* Adnan Chowdhury * Adrian Leonhard * Ahmad Farid * Akshar Patel @@ -36,6 +37,7 @@ TypeScript is authored by: * Asad Saeeduddin * Avery Morin * Basarat Ali Syed +* @begincalendar * Ben Duffield * Ben Mosher * Benjamin Bock @@ -59,7 +61,6 @@ TypeScript is authored by: * Colby Russell * Colin Snover * Cotton Hou -* csigs * Cyrus Najmabadi * Dafrok Zhang * Dahan Gong @@ -87,6 +88,7 @@ TypeScript is authored by: * Eric Tsang * Erik Edrosa * Erik McClenney +* Esakki Raj * Ethan Resnick * Ethan Rubio * Eugene Timokhov @@ -124,6 +126,7 @@ TypeScript is authored by: * Ivan Enderlin * Ivo Gabe de Wolff * Iwata Hidetaka +* Jack Williams * Jakub Korzeniowski * Jakub Młokosiewicz * James Henry @@ -182,7 +185,6 @@ TypeScript is authored by: * Martin Hiller * Martin Vseticka * Masahiro Wakame -* Matt * Matt Bierner * Matt McCutchen * Matt Mitchell @@ -224,6 +226,7 @@ TypeScript is authored by: * Perry Jiang * Peter Burns * Philip Bulley +* Philippe Voinov * Piero Cangianiello * @piloopin * Prayag Verma @@ -261,6 +264,7 @@ TypeScript is authored by: * Stanislav Iliev * Stanislav Sysoev * Stas Vilchik +* Stephan Ginthör * Steve Lucco * Sudheesh Singanamalla * Sébastien Arod From 5ef3fde1ab952597e61320674c6c6d55bff04edd Mon Sep 17 00:00:00 2001 From: csigs Date: Tue, 30 Jan 2018 11:10:58 +0000 Subject: [PATCH 162/163] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 11 ++++++++++- .../diagnosticMessages.generated.json.lcl | 13 +++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl index 5d67c5c6cdc..363a0ac6a41 100644 --- a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + @@ -5103,6 +5103,15 @@ + + + + + + + + + diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl index e13e8191eb2..a2ee47c3620 100644 --- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -14,7 +14,7 @@ - + @@ -5103,6 +5103,15 @@ + + + + + + + + + @@ -8647,7 +8656,7 @@ - + From 01516c84d2ce87c387b3bf037af2e90a25a6c213 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 30 Jan 2018 06:47:58 -0800 Subject: [PATCH 163/163] Update to use TypeFlags.Instantiable in instantiateSymbol --- 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 8c97259107b..a6c0e436e8d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8591,7 +8591,7 @@ namespace ts { function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { const links = getSymbolLinks(symbol); - if (links.type && !maybeTypeOfKind(links.type, TypeFlags.Object | TypeFlags.TypeVariable | TypeFlags.Index)) { + if (links.type && !maybeTypeOfKind(links.type, TypeFlags.Object | TypeFlags.Instantiable)) { // If the type of the symbol is already resolved, and if that type could not possibly // be affected by instantiation, simply return the symbol itself. return symbol;