From 3fe72631ce6293a9b8336ff6a913b4f275e079bb Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 5 Dec 2018 15:02:00 -0800 Subject: [PATCH 001/120] Added test case for trivia preceding dot in numeric LHS in property accesses. --- .../cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts b/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts index b24b174db2c..4e8e3529f3a 100644 --- a/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts +++ b/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts @@ -16,3 +16,4 @@ var test7 = 3 .toString(); var test8 = new Number(4).toString(); var test9 = 3. + 3. +var test10 = 0 /* any comment */.toString(); From 87e45f9e04ea19cb6d6d8fbd6074eced98dbc96e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 5 Dec 2018 15:02:15 -0800 Subject: [PATCH 002/120] Accepted baselines. --- .../numericLiteralsWithTrailingDecimalPoints01.errors.txt | 1 + .../numericLiteralsWithTrailingDecimalPoints01.js | 2 ++ .../numericLiteralsWithTrailingDecimalPoints01.symbols | 5 +++++ .../numericLiteralsWithTrailingDecimalPoints01.types | 7 +++++++ 4 files changed, 15 insertions(+) diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt index ca85c992fa4..c1484b2da66 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt @@ -30,4 +30,5 @@ tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts(9,24): error .toString(); var test8 = new Number(4).toString(); var test9 = 3. + 3. + var test10 = 0 /* any comment */.toString(); \ No newline at end of file diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js index efa7ae96cf2..62e40a94e22 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js @@ -16,6 +16,7 @@ var test7 = 3 .toString(); var test8 = new Number(4).toString(); var test9 = 3. + 3. +var test10 = 0 /* any comment */.toString(); //// [numericLiteralsWithTrailingDecimalPoints01.js] @@ -37,3 +38,4 @@ var test7 = 3 .toString(); var test8 = new Number(4).toString(); var test9 = 3. + 3.; +var test10 = 0 /* any comment */..toString(); diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols index 76bbea5a195..39ef3b1c0be 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols @@ -61,3 +61,8 @@ var test8 = new Number(4).toString(); var test9 = 3. + 3. >test9 : Symbol(test9, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 16, 3)) +var test10 = 0 /* any comment */.toString(); +>test10 : Symbol(test10, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 17, 3)) +>0 /* any comment */.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types index f9fe9b49280..839f03ce420 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types @@ -94,3 +94,10 @@ var test9 = 3. + 3. >3. : 3 >3. : 3 +var test10 = 0 /* any comment */.toString(); +>test10 : string +>0 /* any comment */.toString() : string +>0 /* any comment */.toString : (radix?: number) => string +>0 : 0 +>toString : (radix?: number) => string + From c65e43e85e3a4e5b0e4384189d9a3837038e3fce Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 15 Nov 2018 10:17:00 -0800 Subject: [PATCH 003/120] Do not close over program in getSourceFileLike --- src/services/sourcemaps.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 92ebcc8c261..9de8557cd19 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -39,9 +39,10 @@ namespace ts { // obviously invalid map return file.sourceMapper = identitySourceMapConsumer; } - const program = getProgram(); + return file.sourceMapper = createDocumentPositionMapper({ getSourceFileLike: s => { + const program = getProgram(); // Lookup file in program, if provided const file = program && program.getSourceFileByPath(s); // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file From 86857d5b09d6723b6f9043d96abfe6f553f6e7cf Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 15 Nov 2018 10:40:59 -0800 Subject: [PATCH 004/120] Use program directly to get the sourceFile of source position --- src/services/sourcemaps.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 9de8557cd19..9b8f97a6640 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -93,8 +93,9 @@ namespace ts { function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { if (!isDeclarationFileName(info.fileName)) return undefined; - const file = getFile(info.fileName); + const file = getSourceFile(info.fileName); if (!file) return undefined; + const newLoc = getSourceMapper(info.fileName, file).getSourcePosition(info); return newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; } @@ -103,27 +104,31 @@ namespace ts { const program = getProgram(); const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; + const declarationPath = outPath ? removeFileExtension(outPath) + Extension.Dts : getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); if (declarationPath === undefined) return undefined; - const declarationFile = getFile(declarationPath); + + const declarationFile = getGeneratedFile(declarationPath); if (!declarationFile) return undefined; + const newLoc = getSourceMapper(declarationPath, declarationFile).getGeneratedPosition(info); return newLoc === info ? undefined : newLoc; } - function getFile(fileName: string): SourceFileLike | undefined { - const path = toPath(fileName); - const file = getProgram().getSourceFileByPath(path); - if (file && file.resolvedPath === path) { - return file; - } - return sourcemappedFileCache.get(path); + function getSourceFile(fileName: string) { + const program = getProgram(); + return program && program.getSourceFileByPath(toPath(fileName)); + } + + function getGeneratedFile(fileName: string) { + return sourcemappedFileCache.get(toPath(fileName)); // TODO: should ask host instead? } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - const file = getFile(fileName)!; // TODO: GH#18217 + const sourceFile = getSourceFile(fileName); + const file = sourceFile && sourceFile.resolvedPath === toPath(fileName) ? sourceFile : getGeneratedFile(fileName)!; // TODO: GH#18217 return file.getLineAndCharacterOfPosition(position); } From 0a8c47bd45fb4265c9d783a5492bdf86d91e5ddd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 15 Nov 2018 11:29:04 -0800 Subject: [PATCH 005/120] Refactoring to createSourceMapperHost --- src/compiler/types.ts | 2 +- src/services/services.ts | 29 ++++++---- src/services/sourcemaps.ts | 107 +++++++++++++++++-------------------- src/services/utilities.ts | 4 +- 4 files changed, 72 insertions(+), 70 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 495465f7032..f52d82a8a2f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5530,7 +5530,7 @@ namespace ts { export interface DocumentPositionMapperHost { getSourceFileLike(path: Path): SourceFileLike | undefined; getCanonicalFileName(path: string): string; - log?(text: string): void; + log(text: string): void; } /** diff --git a/src/services/services.ts b/src/services/services.ts index 20400a3ec47..67f9e537574 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1139,7 +1139,14 @@ namespace ts { const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const sourceMapper = getSourceMapper(useCaseSensitiveFileNames, currentDirectory, log, host, () => program); + const sourceMapper = getSourceMapper({ + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getCurrentDirectory: () => currentDirectory, + getProgram, + fileExists: host.fileExists ? f => host.fileExists!(f) : returnFalse, + readFile: host.readFile ? (f, encoding) => host.readFile!(f, encoding) : () => undefined, + log + }); function getValidSourceFile(fileName: string): SourceFile { const sourceFile = program.getSourceFile(fileName); @@ -1203,15 +1210,7 @@ namespace ts { writeFile: noop, getCurrentDirectory: () => currentDirectory, fileExists, - readFile(fileName) { - // stub missing host functionality - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const entry = hostCache && hostCache.getEntryByPath(path); - if (entry) { - return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot); - } - return host.readFile && host.readFile(fileName); - }, + readFile, realpath: host.realpath && (path => host.realpath!(path)), directoryExists: directoryName => { return directoryProbablyExists(directoryName, host); @@ -1272,6 +1271,16 @@ namespace ts { (!!host.fileExists && host.fileExists(fileName)); } + function readFile(fileName: string) { + // stub missing host functionality + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const entry = hostCache && hostCache.getEntryByPath(path); + if (entry) { + return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot); + } + return host.readFile && host.readFile(fileName); + } + // Release any files we have acquired in the old program but are // not part of the new program. function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 9b8f97a6640..c2b9dbfb46a 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -9,15 +9,21 @@ namespace ts { clearCache(): void; } + export interface SourceMapperHost { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + getProgram(): Program | undefined; + fileExists(path: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + log(s: string): void; + } + export function getSourceMapper( - useCaseSensitiveFileNames: boolean, - currentDirectory: string, - log: (message: string) => void, - host: LanguageServiceHost, - getProgram: () => Program, + host: SourceMapperHost ): SourceMapper { - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - let sourcemappedFileCache: SourceFileLikeCache; + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + const currentDirectory = host.getCurrentDirectory(); + const generatedFileCache = createMap(); return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; function toPath(fileName: string) { @@ -25,7 +31,7 @@ namespace ts { } function scanForSourcemapURL(fileName: string) { - const mappedFile = sourcemappedFileCache.get(toPath(fileName)); + const mappedFile = generatedFileCache.get(toPath(fileName)); if (!mappedFile) { return; } @@ -41,26 +47,13 @@ namespace ts { } return file.sourceMapper = createDocumentPositionMapper({ - getSourceFileLike: s => { - const program = getProgram(); - // Lookup file in program, if provided - const file = program && program.getSourceFileByPath(s); - // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file - if (file === undefined || file.resolvedPath !== s) { - // Otherwise check the cache (which may hit disk) - return sourcemappedFileCache.get(s); - } - return file; - }, + getSourceFileLike, getCanonicalFileName, - log, + log: s => host.log(s), }, map, mapFileName); } function getSourceMapper(fileName: string, file: SourceFileLike): DocumentPositionMapper { - if (!host.readFile || !host.fileExists) { - return file.sourceMapper = identitySourceMapConsumer; - } if (file.sourceMapper) { return file.sourceMapper; } @@ -101,7 +94,9 @@ namespace ts { } function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { - const program = getProgram(); + const program = host.getProgram(); + if (!program) return undefined; + const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; @@ -118,49 +113,47 @@ namespace ts { } function getSourceFile(fileName: string) { - const program = getProgram(); + const program = host.getProgram(); return program && program.getSourceFileByPath(toPath(fileName)); } - function getGeneratedFile(fileName: string) { - return sourcemappedFileCache.get(toPath(fileName)); // TODO: should ask host instead? + function getGeneratedFile(fileName: string): SourceFileLike | undefined { + const path = toPath(fileName); + const fileFromCache = generatedFileCache.get(path); + if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; + + // TODO: should ask host instead? + if (!host.fileExists(path)) { + generatedFileCache.set(path, false); + return undefined; + } + + // And failing that, check the disk + const text = host.readFile(path); + const file: SourceFileLike | false = text ? { + text, + lineMap: undefined, + getLineAndCharacterOfPosition(pos: number) { + return computeLineAndCharacterOfPosition(getLineStarts(this as SourceFileLike), pos); + } + } : false; + generatedFileCache.set(path, file); + return file ? file : undefined; + } + + function getSourceFileLike(fileName: string) { + const sourceFile = getSourceFile(fileName); + // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file + return sourceFile && sourceFile.resolvedPath === toPath(fileName) ? sourceFile : getGeneratedFile(fileName); } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - const sourceFile = getSourceFile(fileName); - const file = sourceFile && sourceFile.resolvedPath === toPath(fileName) ? sourceFile : getGeneratedFile(fileName)!; // TODO: GH#18217 + const file = getSourceFileLike(fileName)!; // TODO: GH#18217 return file.getLineAndCharacterOfPosition(position); } function clearCache(): void { - sourcemappedFileCache = createSourceFileLikeCache(host); + generatedFileCache.clear(); } } - - interface SourceFileLikeCache { - get(path: Path): SourceFileLike | undefined; - } - - function createSourceFileLikeCache(host: { readFile?: (path: string) => string | undefined, fileExists?: (path: string) => boolean }): SourceFileLikeCache { - const cached = createMap(); - return { - get(path: Path) { - if (cached.has(path)) { - return cached.get(path); - } - if (!host.fileExists || !host.readFile || !host.fileExists(path)) return; - // And failing that, check the disk - const text = host.readFile(path)!; // TODO: GH#18217 - const file = { - text, - lineMap: undefined, - getLineAndCharacterOfPosition(pos: number) { - return computeLineAndCharacterOfPosition(getLineStarts(this), pos); - } - } as SourceFileLike; - cached.set(path, file); - return file; - } - }; - } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 22e6b31b55c..2f78ef2324b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1282,11 +1282,11 @@ namespace ts { return !!compilerOptions.module || compilerOptions.target! >= ScriptTarget.ES2015 || !!compilerOptions.noEmit; } - export function hostUsesCaseSensitiveFileNames(host: LanguageServiceHost): boolean { + export function hostUsesCaseSensitiveFileNames(host: { useCaseSensitiveFileNames?(): boolean; }): boolean { return host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false; } - export function hostGetCanonicalFileName(host: LanguageServiceHost): GetCanonicalFileName { + export function hostGetCanonicalFileName(host: { useCaseSensitiveFileNames?(): boolean; }): GetCanonicalFileName { return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)); } From 1db8bb062c1fda1f901352471d90816656abc9ee Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 15 Nov 2018 11:49:54 -0800 Subject: [PATCH 006/120] Use file names instead of paths for reading files --- src/compiler/sourcemap.ts | 9 +++------ src/compiler/types.ts | 2 +- src/services/sourcemaps.ts | 27 ++++++++++++++------------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 9b74fb22d58..b3b4e1fbb5e 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -592,11 +592,9 @@ namespace ts { const mapDirectory = getDirectoryPath(mapPath); const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); - const generatedCanonicalFilePath = host.getCanonicalFileName(generatedAbsoluteFilePath) as Path; - const generatedFile = host.getSourceFileLike(generatedCanonicalFilePath); + const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); - const sourceFileCanonicalPaths = sourceFileAbsolutePaths.map(source => host.getCanonicalFileName(source) as Path); - const sourceToSourceIndexMap = createMapFromEntries(sourceFileCanonicalPaths.map((source, i) => [source, i] as [string, number])); + const sourceToSourceIndexMap = createMapFromEntries(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i] as [string, number])); let decodedMappings: ReadonlyArray | undefined; let generatedMappings: SortedReadonlyArray | undefined; let sourceMappings: ReadonlyArray> | undefined; @@ -613,8 +611,7 @@ namespace ts { let source: string | undefined; let sourcePosition: number | undefined; if (isSourceMapping(mapping)) { - const sourceFilePath = sourceFileCanonicalPaths[mapping.sourceIndex]; - const sourceFile = host.getSourceFileLike(sourceFilePath); + const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); source = map.sources[mapping.sourceIndex]; sourcePosition = sourceFile !== undefined ? getPositionOfLineAndCharacterWithEdits(sourceFile, mapping.sourceLine, mapping.sourceCharacter) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f52d82a8a2f..4da96730a93 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5528,7 +5528,7 @@ namespace ts { /* @internal */ export interface DocumentPositionMapperHost { - getSourceFileLike(path: Path): SourceFileLike | undefined; + getSourceFileLike(fileName: string): SourceFileLike | undefined; getCanonicalFileName(path: string): string; log(text: string): void; } diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index c2b9dbfb46a..47f39d85136 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -23,7 +23,7 @@ namespace ts { ): SourceMapper { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); const currentDirectory = host.getCurrentDirectory(); - const generatedFileCache = createMap(); + const sourceFileLike = createMap(); return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; function toPath(fileName: string) { @@ -31,7 +31,7 @@ namespace ts { } function scanForSourcemapURL(fileName: string) { - const mappedFile = generatedFileCache.get(toPath(fileName)); + const mappedFile = sourceFileLike.get(toPath(fileName)); if (!mappedFile) { return; } @@ -39,7 +39,7 @@ namespace ts { return tryGetSourceMappingURL(mappedFile.text, getLineStarts(mappedFile)); } - function convertDocumentToSourceMapper(file: { sourceMapper?: DocumentPositionMapper }, contents: string, mapFileName: string) { + function convertDocumentToSourceMapper(file: SourceFileLike, contents: string, mapFileName: string) { const map = tryParseRawSourceMap(contents); if (!map || !map.sources || !map.file || !map.mappings) { // obviously invalid map @@ -75,9 +75,9 @@ namespace ts { } possibleMapLocations.push(fileName + ".map"); for (const location of possibleMapLocations) { - const mapPath = ts.toPath(location, getDirectoryPath(fileName), getCanonicalFileName); - if (host.fileExists(mapPath)) { - return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217 + const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(fileName)); + if (host.fileExists(mapFileName)) { + return convertDocumentToSourceMapper(file, host.readFile(mapFileName)!, mapFileName); // TODO: GH#18217 } } return file.sourceMapper = identitySourceMapConsumer; @@ -105,7 +105,7 @@ namespace ts { getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); if (declarationPath === undefined) return undefined; - const declarationFile = getGeneratedFile(declarationPath); + const declarationFile = getSourceFileLikeFromCache(declarationPath); if (!declarationFile) return undefined; const newLoc = getSourceMapper(declarationPath, declarationFile).getGeneratedPosition(info); @@ -117,14 +117,14 @@ namespace ts { return program && program.getSourceFileByPath(toPath(fileName)); } - function getGeneratedFile(fileName: string): SourceFileLike | undefined { + function getSourceFileLikeFromCache(fileName: string): SourceFileLike | undefined { const path = toPath(fileName); - const fileFromCache = generatedFileCache.get(path); + const fileFromCache = sourceFileLike.get(path); if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; // TODO: should ask host instead? if (!host.fileExists(path)) { - generatedFileCache.set(path, false); + sourceFileLike.set(path, false); return undefined; } @@ -137,14 +137,15 @@ namespace ts { return computeLineAndCharacterOfPosition(getLineStarts(this as SourceFileLike), pos); } } : false; - generatedFileCache.set(path, file); + sourceFileLike.set(path, file); return file ? file : undefined; } + // This can be called from source mapper in either source program or program that includes generated file function getSourceFileLike(fileName: string) { const sourceFile = getSourceFile(fileName); // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file - return sourceFile && sourceFile.resolvedPath === toPath(fileName) ? sourceFile : getGeneratedFile(fileName); + return sourceFile && sourceFile.resolvedPath === toPath(fileName) ? sourceFile : getSourceFileLikeFromCache(fileName); } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { @@ -153,7 +154,7 @@ namespace ts { } function clearCache(): void { - generatedFileCache.clear(); + sourceFileLike.clear(); } } } From 0dad79e8b3c077ef8b1870091ac61025b57d51ae Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 15 Nov 2018 12:02:03 -0800 Subject: [PATCH 007/120] Handle source and generated files more gracefully --- src/services/sourcemaps.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 47f39d85136..7b51c9ecff9 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -18,9 +18,7 @@ namespace ts { log(s: string): void; } - export function getSourceMapper( - host: SourceMapperHost - ): SourceMapper { + export function getSourceMapper(host: SourceMapperHost): SourceMapper { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); const currentDirectory = host.getCurrentDirectory(); const sourceFileLike = createMap(); @@ -94,9 +92,12 @@ namespace ts { } function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { - const program = host.getProgram(); - if (!program) return undefined; + if (isDeclarationFileName(info.fileName)) return undefined; + const sourceFile = getSourceFile(info.fileName); + if (!sourceFile) return undefined; + + const program = host.getProgram()!; const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; @@ -114,7 +115,12 @@ namespace ts { function getSourceFile(fileName: string) { const program = host.getProgram(); - return program && program.getSourceFileByPath(toPath(fileName)); + if (!program) return undefined; + + const path = toPath(fileName); + // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file + const file = program.getSourceFileByPath(path); + return file && file.resolvedPath === path ? file : undefined; } function getSourceFileLikeFromCache(fileName: string): SourceFileLike | undefined { @@ -143,9 +149,7 @@ namespace ts { // This can be called from source mapper in either source program or program that includes generated file function getSourceFileLike(fileName: string) { - const sourceFile = getSourceFile(fileName); - // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file - return sourceFile && sourceFile.resolvedPath === toPath(fileName) ? sourceFile : getSourceFileLikeFromCache(fileName); + return getSourceFile(fileName) || getSourceFileLikeFromCache(fileName); } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { From 12428d45c02c46b38d76009774700be840ad63dc Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 16 Nov 2018 10:15:51 -0800 Subject: [PATCH 008/120] Add method on host to get DocumentPositionMapper so it can be cached. --- src/compiler/scanner.ts | 8 +- src/compiler/sourcemap.ts | 20 ++- src/compiler/types.ts | 2 + src/harness/sourceMapRecorder.ts | 2 +- src/server/project.ts | 60 +++++++ src/server/scriptInfo.ts | 31 +++- src/server/scriptVersionCache.ts | 14 +- src/server/session.ts | 2 +- src/services/services.ts | 8 +- src/services/sourcemaps.ts | 151 ++++++++++-------- src/services/types.ts | 6 +- src/testRunner/unittests/textStorage.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 2 +- 13 files changed, 219 insertions(+), 89 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index a9438110593..e9ccc23d6eb 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -338,12 +338,16 @@ namespace ts { } export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number { - return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text); + return sourceFile.getPositionOfLineAndCharacter ? + sourceFile.getPositionOfLineAndCharacter(line, character) : + computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text); } /* @internal */ export function getPositionOfLineAndCharacterWithEdits(sourceFile: SourceFileLike, line: number, character: number): number { - return computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, /*allowEdits*/ true); + return sourceFile.getPositionOfLineAndCharacter ? + sourceFile.getPositionOfLineAndCharacter(line, character) : + computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, /*allowEdits*/ true); } /* @internal */ diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index b3b4e1fbb5e..190cac3b761 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -266,14 +266,24 @@ namespace ts { const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/; const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; + export interface LineInfo { + getLineCount(): number; + getLineText(line: number): string; + } + + export function getLineInfo(text: string, lineStarts: ReadonlyArray): LineInfo { + return { + getLineCount: () => lineStarts.length, + getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) + }; + } + /** * Tries to find the sourceMappingURL comment at the end of a file. - * @param text The source text of the file. - * @param lineStarts The line starts of the file. */ - export function tryGetSourceMappingURL(text: string, lineStarts: ReadonlyArray = computeLineStarts(text)) { - for (let index = lineStarts.length - 1; index >= 0; index--) { - const line = text.substring(lineStarts[index], lineStarts[index + 1]); + export function tryGetSourceMappingURL(lineInfo: LineInfo) { + for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { + const line = lineInfo.getLineText(index); const comment = sourceMapCommentRegExp.exec(line); if (comment) { return comment[1]; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4da96730a93..e654f4faab7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2614,6 +2614,8 @@ namespace ts { export interface SourceFileLike { readonly text: string; lineMap?: ReadonlyArray; + /* @internal */ + getPositionOfLineAndCharacter?(line: number, character: number): number; } diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index 92c3e2d4a94..d16aca21d66 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -69,7 +69,7 @@ namespace Harness.SourceMapRecorder { SourceMapDecoder.initializeSourceMapDecoding(sourceMapData); sourceMapRecorder.WriteLine("==================================================================="); sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMap.file); - sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(jsFile.text, jsLineMap)); + sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(ts.getLineInfo(jsFile.text, jsLineMap))); sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMap.sourceRoot); sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMap.sources); if (sourceMapData.sourceMap.sourcesContent) { diff --git a/src/server/project.ts b/src/server/project.ts index fa39aa4abe9..566a2b9abe9 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -503,6 +503,66 @@ namespace ts.server { return this.getLanguageService().getSourceMapper(); } + /*@internal*/ + getDocumentPositionMapper(fileName: string): DocumentPositionMapper | undefined { + const declarationInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); + if (!declarationInfo) return undefined; + + declarationInfo.getSnapshot(); // Ensure synchronized + const existingMapper = declarationInfo.textStorage.mapper; + if (existingMapper !== undefined) { + return existingMapper ? existingMapper : undefined; + } + + // Create the mapper + declarationInfo.mapInfo = undefined; + + const mapper = getDocumentPositionMapper({ + getCanonicalFileName: this.projectService.toCanonicalFileName, + log: s => this.log(s), + readMapFile: f => this.readMapFile(f, declarationInfo), + getSourceFileLike: f => this.getSourceFileLike(f) + }, declarationInfo.fileName, declarationInfo.textStorage.getLineInfo()); + declarationInfo.textStorage.mapper = mapper || false; + return mapper; + } + + private readMapFile(fileName: string, declarationInfo: ScriptInfo) { + const mapInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); + if (!mapInfo) return undefined; + declarationInfo.mapInfo = mapInfo; + const snap = mapInfo.getSnapshot(); + return snap.getText(0, snap.getLength()); + } + + /*@internal*/ + getSourceFileLike(fileName: string) { + const path = this.toPath(fileName); + const sourceFile = this.getSourceFile(path); + if (sourceFile && sourceFile.resolvedPath === path) return sourceFile; + + // Need to look for other files. + const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); + if (!info) return undefined; + + // Key doesnt matter since its only for text and lines + if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile; + if (info.textStorage.sourceFileLike) return info.textStorage.sourceFileLike; + + info.textStorage.sourceFileLike = { + get text() { + Debug.fail("shouldnt need text"); + return ""; + }, + getLineAndCharacterOfPosition: pos => { + const lineOffset = info.positionToLineOffset(pos); + return { line: lineOffset.line - 1, character: lineOffset.offset - 1 }; + }, + getPositionOfLineAndCharacter: (line, character) => info.lineOffsetToPosition(line + 1, character + 1) + }; + return info.textStorage.sourceFileLike; + } + private shouldEmitFile(scriptInfo: ScriptInfo) { return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent(); } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index c090b4b14d0..3502fff1556 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -46,6 +46,9 @@ namespace ts.server { */ private pendingReloadFromDisk = false; + mapper: DocumentPositionMapper | false | undefined = false; + sourceFileLike: SourceFileLike | undefined; + constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath, initialVersion: ScriptInfoVersion | undefined, private readonly info: ScriptInfo) { this.version = initialVersion || { svc: 0, text: 0 }; } @@ -70,6 +73,8 @@ namespace ts.server { this.text = newText; this.lineMap = undefined; this.fileSize = undefined; + this.mapper = undefined; + this.sourceFileLike = undefined; this.version.text++; } @@ -79,6 +84,8 @@ namespace ts.server { this.text = undefined; this.lineMap = undefined; this.fileSize = undefined; + this.mapper = undefined; + this.sourceFileLike = undefined; } /** @@ -156,8 +163,8 @@ namespace ts.server { : ScriptSnapshot.fromString(this.getOrLoadText()); } - public getLineInfo(line: number): AbsolutePositionAndLineText { - return this.switchToScriptVersionCache().getLineInfo(line); + public getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText { + return this.switchToScriptVersionCache().getAbsolutePositionAndLineText(line); } /** * @param line 0 based index @@ -246,6 +253,17 @@ namespace ts.server { Debug.assert(!this.svc, "ScriptVersionCache should not be set"); return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText())); } + + getLineInfo(): LineInfo { + if (this.svc) { + return { + getLineCount: () => this.svc!.getLineCount(), + getLineText: line => this.svc!.getAbsolutePositionAndLineText(line + 1).lineText! + }; + } + const lineMap = this.getLineMap(); + return getLineInfo(this.text!, lineMap); + } } /*@internal*/ @@ -269,7 +287,7 @@ namespace ts.server { /* @internal */ fileWatcher: FileWatcher | undefined; - private textStorage: TextStorage; + /* @internal */ textStorage: TextStorage; /*@internal*/ readonly isDynamic: boolean; @@ -284,6 +302,9 @@ namespace ts.server { /*@internal*/ mTime?: number; + /*@internal*/ + mapInfo?: ScriptInfo; + constructor( private readonly host: ServerHost, readonly fileName: NormalizedPath, @@ -521,8 +542,8 @@ namespace ts.server { } /*@internal*/ - getLineInfo(line: number): AbsolutePositionAndLineText { - return this.textStorage.getLineInfo(line); + getAbsolutePositionAndLineText(line: number): AbsolutePositionAndLineText { + return this.textStorage.getAbsolutePositionAndLineText(line); } editContent(start: number, end: number, newText: string): void { diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index a350ed99f71..afd8f67ecbe 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -308,8 +308,8 @@ namespace ts.server { return this._getSnapshot().version; } - getLineInfo(line: number): AbsolutePositionAndLineText { - return this._getSnapshot().index.lineNumberToInfo(line); + getAbsolutePositionAndLineText(oneBasedLine: number): AbsolutePositionAndLineText { + return this._getSnapshot().index.lineNumberToInfo(oneBasedLine); } lineOffsetToPosition(line: number, column: number): number { @@ -348,6 +348,10 @@ namespace ts.server { } } + getLineCount() { + return this._getSnapshot().index.getLineCount(); + } + static fromString(script: string) { const svc = new ScriptVersionCache(); const snap = new LineIndexSnapshot(0, svc, new LineIndex()); @@ -400,8 +404,12 @@ namespace ts.server { return this.root.charOffsetToLineInfo(1, position); } + getLineCount() { + return this.root.lineCount(); + } + lineNumberToInfo(oneBasedLine: number): AbsolutePositionAndLineText { - const lineCount = this.root.lineCount(); + const lineCount = this.getLineCount(); if (oneBasedLine <= lineCount) { const { position, leaf } = this.root.lineNumberToInfo(oneBasedLine, 0); return { absolutePosition: position, lineText: leaf && leaf.text }; diff --git a/src/server/session.ts b/src/server/session.ts index 0b9ad913f8a..d534f445ee3 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1474,7 +1474,7 @@ namespace ts.server { // only to the previous line. If all this is true, then // add edits necessary to properly indent the current line. if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) { - const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line); + const { lineText, absolutePosition } = scriptInfo.getAbsolutePositionAndLineText(args.line); if (lineText && lineText.search("\\S") < 0) { const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions); let hasIndent = 0; diff --git a/src/services/services.ts b/src/services/services.ts index 67f9e537574..a0c41ffe94d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -603,7 +603,7 @@ namespace ts { } public getPositionOfLineAndCharacter(line: number, character: number): number { - return getPositionOfLineAndCharacter(this, line, character); + return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text); } public getLineEndOfPosition(pos: number): number { @@ -1143,8 +1143,10 @@ namespace ts { useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getCurrentDirectory: () => currentDirectory, getProgram, - fileExists: host.fileExists ? f => host.fileExists!(f) : returnFalse, - readFile: host.readFile ? (f, encoding) => host.readFile!(f, encoding) : () => undefined, + fileExists: host.fileExists && (f => host.fileExists!(f)), + readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)), + getDocumentPositionMapper: host.getDocumentPositionMapper && (f => host.getDocumentPositionMapper!(f)), + getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)), log }); diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 7b51c9ecff9..c679688e6c0 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -13,8 +13,10 @@ namespace ts { useCaseSensitiveFileNames(): boolean; getCurrentDirectory(): string; getProgram(): Program | undefined; - fileExists(path: string): boolean; - readFile(path: string, encoding?: string): string | undefined; + fileExists?(path: string): boolean; + readFile?(path: string, encoding?: string): string | undefined; + getSourceFileLike?(fileName: string): SourceFileLike | undefined; + getDocumentPositionMapper?(fileName: string): DocumentPositionMapper | undefined; log(s: string): void; } @@ -22,63 +24,33 @@ namespace ts { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); const currentDirectory = host.getCurrentDirectory(); const sourceFileLike = createMap(); + const documentPositionMappers = createMap(); return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; function toPath(fileName: string) { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } - function scanForSourcemapURL(fileName: string) { - const mappedFile = sourceFileLike.get(toPath(fileName)); - if (!mappedFile) { - return; - } + function getDocumentPositionMapper(fileName: string) { + const path = toPath(fileName); + const value = documentPositionMappers.get(path); + if (value) return value; - return tryGetSourceMappingURL(mappedFile.text, getLineStarts(mappedFile)); - } - - function convertDocumentToSourceMapper(file: SourceFileLike, contents: string, mapFileName: string) { - const map = tryParseRawSourceMap(contents); - if (!map || !map.sources || !map.file || !map.mappings) { - // obviously invalid map - return file.sourceMapper = identitySourceMapConsumer; + let mapper: DocumentPositionMapper | undefined; + if (host.getDocumentPositionMapper) { + mapper = host.getDocumentPositionMapper(fileName); } - - return file.sourceMapper = createDocumentPositionMapper({ - getSourceFileLike, - getCanonicalFileName, - log: s => host.log(s), - }, map, mapFileName); - } - - function getSourceMapper(fileName: string, file: SourceFileLike): DocumentPositionMapper { - if (file.sourceMapper) { - return file.sourceMapper; + else if (host.readFile) { + const file = getSourceFileLike(fileName); + mapper = file && ts.getDocumentPositionMapper({ + getSourceFileLike, + getCanonicalFileName, + log: s => host.log(s), + readMapFile: f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined + }, fileName, getLineInfo(file.text, getLineStarts(file))); } - let mapFileName = scanForSourcemapURL(fileName); - if (mapFileName) { - const match = base64UrlRegExp.exec(mapFileName); - if (match) { - if (match[1]) { - const base64Object = match[1]; - return convertDocumentToSourceMapper(file, base64decode(sys, base64Object), fileName); - } - // Not a data URL we can parse, skip it - mapFileName = undefined; - } - } - const possibleMapLocations: string[] = []; - if (mapFileName) { - possibleMapLocations.push(mapFileName); - } - possibleMapLocations.push(fileName + ".map"); - for (const location of possibleMapLocations) { - const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(fileName)); - if (host.fileExists(mapFileName)) { - return convertDocumentToSourceMapper(file, host.readFile(mapFileName)!, mapFileName); // TODO: GH#18217 - } - } - return file.sourceMapper = identitySourceMapConsumer; + documentPositionMappers.set(path, mapper || identitySourceMapConsumer); + return mapper || identitySourceMapConsumer; } function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { @@ -87,8 +59,8 @@ namespace ts { const file = getSourceFile(info.fileName); if (!file) return undefined; - const newLoc = getSourceMapper(info.fileName, file).getSourcePosition(info); - return newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; + const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info); + return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; } function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { @@ -106,10 +78,7 @@ namespace ts { getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); if (declarationPath === undefined) return undefined; - const declarationFile = getSourceFileLikeFromCache(declarationPath); - if (!declarationFile) return undefined; - - const newLoc = getSourceMapper(declarationPath, declarationFile).getGeneratedPosition(info); + const newLoc = getDocumentPositionMapper(declarationPath).getGeneratedPosition(info); return newLoc === info ? undefined : newLoc; } @@ -123,42 +92,92 @@ namespace ts { return file && file.resolvedPath === path ? file : undefined; } - function getSourceFileLikeFromCache(fileName: string): SourceFileLike | undefined { + function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined { const path = toPath(fileName); const fileFromCache = sourceFileLike.get(path); if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; // TODO: should ask host instead? - if (!host.fileExists(path)) { + if (!host.readFile || host.fileExists && !host.fileExists(path)) { sourceFileLike.set(path, false); return undefined; } // And failing that, check the disk const text = host.readFile(path); - const file: SourceFileLike | false = text ? { - text, - lineMap: undefined, - getLineAndCharacterOfPosition(pos: number) { - return computeLineAndCharacterOfPosition(getLineStarts(this as SourceFileLike), pos); - } - } : false; + const file = text ? createSourceFileLike(text) : false; sourceFileLike.set(path, file); return file ? file : undefined; } // This can be called from source mapper in either source program or program that includes generated file function getSourceFileLike(fileName: string) { - return getSourceFile(fileName) || getSourceFileLikeFromCache(fileName); + return !host.getSourceFileLike ? + getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) : + host.getSourceFileLike(fileName); } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { + // TODO:: shkamat const file = getSourceFileLike(fileName)!; // TODO: GH#18217 return file.getLineAndCharacterOfPosition(position); } function clearCache(): void { sourceFileLike.clear(); + documentPositionMappers.clear(); } } + + export interface GetDocumentPositionMapperHost extends DocumentPositionMapperHost { + readMapFile(fileName: string): string | undefined; + } + + export function getDocumentPositionMapper(host: GetDocumentPositionMapperHost, generatedFileName: string, generatedFileLineInfo: LineInfo) { + let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); + if (mapFileName) { + const match = base64UrlRegExp.exec(mapFileName); + if (match) { + if (match[1]) { + const base64Object = match[1]; + return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName); + } + // Not a data URL we can parse, skip it + mapFileName = undefined; + } + } + const possibleMapLocations: string[] = []; + if (mapFileName) { + possibleMapLocations.push(mapFileName); + } + possibleMapLocations.push(generatedFileName + ".map"); + for (const location of possibleMapLocations) { + const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); + const mapFileContents = host.readMapFile(mapFileName); + if (mapFileContents) { + return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); + } + } + return undefined; + } + + function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) { + const map = tryParseRawSourceMap(contents); + if (!map || !map.sources || !map.file || !map.mappings) { + // obviously invalid map + return undefined; + } + + return createDocumentPositionMapper(host, map, mapFileName); + } + + function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike { + return { + text, + lineMap, + getLineAndCharacterOfPosition(pos: number) { + return computeLineAndCharacterOfPosition(getLineStarts(this), pos); + } + }; + } } diff --git a/src/services/types.ts b/src/services/types.ts index 69f07b705a7..d3a8f8cdd52 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -91,7 +91,6 @@ namespace ts { export interface SourceFileLike { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; - /*@internal*/ sourceMapper?: DocumentPositionMapper; } export interface SourceMapSource { @@ -233,6 +232,11 @@ namespace ts { installPackage?(options: InstallPackageOptions): Promise; /* @internal */ inspectValue?(options: InspectValueOptions): Promise; writeFile?(fileName: string, content: string): void; + + /* @internal */ + getDocumentPositionMapper?(fileName: string): DocumentPositionMapper | undefined; + /* @internal */ + getSourceFileLike?(fileName: string): SourceFileLike | undefined; } /* @internal */ diff --git a/src/testRunner/unittests/textStorage.ts b/src/testRunner/unittests/textStorage.ts index 5dce083120b..f5fbb512e52 100644 --- a/src/testRunner/unittests/textStorage.ts +++ b/src/testRunner/unittests/textStorage.ts @@ -60,7 +60,7 @@ namespace ts.textStorage { ts1.useText(); assert.isFalse(ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2"); - ts1.getLineInfo(0); + ts1.getAbsolutePositionAndLineText(0); assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 2"); }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2b78178587e..b788d46ac9f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8046,7 +8046,6 @@ declare namespace ts.server { readonly containingProjects: Project[]; private formatSettings; private preferences; - private textStorage; constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent: boolean, path: Path, initialVersion?: ScriptInfoVersion); isScriptOpen(): boolean; open(newText: string): void; @@ -8211,6 +8210,7 @@ declare namespace ts.server { getGlobalProjectErrors(): ReadonlyArray; getAllProjectErrors(): ReadonlyArray; getLanguageService(ensureSynchronized?: boolean): LanguageService; + private readMapFile; private shouldEmitFile; getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** From afdf1e90ecd9c438cc860860fcb676af8c78d8d5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 16 Nov 2018 11:24:01 -0800 Subject: [PATCH 009/120] Dont depend on project in document position mapper so that we can unload or remove projects independently --- src/server/editorServices.ts | 64 +++++++++++++++++++ src/server/project.ts | 54 +--------------- src/services/sourcemaps.ts | 25 ++++---- .../reference/api/tsserverlibrary.d.ts | 1 - 4 files changed, 78 insertions(+), 66 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 90b516dadfc..dd05b562cff 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2195,6 +2195,70 @@ namespace ts.server { return this.filenameToScriptInfo.get(fileName); } + /*@internal*/ + getDocumentPositionMapper(fileName: string, project: Project): DocumentPositionMapper | undefined { + const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); + if (!declarationInfo) return undefined; + + declarationInfo.getSnapshot(); // Ensure synchronized + const existingMapper = declarationInfo.textStorage.mapper; + if (existingMapper !== undefined) { + return existingMapper ? existingMapper : undefined; + } + + // Create the mapper + declarationInfo.mapInfo = undefined; + + let readMapFile: ((fileName: string) => string | undefined) | undefined = fileName => { + const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); + if (!mapInfo) return undefined; + declarationInfo.mapInfo = mapInfo; + const snap = mapInfo.getSnapshot(); + return snap.getText(0, snap.getLength()); + }; + const projectName = project.projectName; + const mapper = getDocumentPositionMapper( + { getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName) }, + declarationInfo.fileName, + declarationInfo.textStorage.getLineInfo(), + readMapFile + ); + readMapFile = undefined; // Remove ref to project + declarationInfo.textStorage.mapper = mapper || false; + return mapper; + } + + /*@internal*/ + getSourceFileLike(fileName: string, projectName: string | Project) { + const project = (projectName as Project).projectName ? projectName as Project : this.findProject(projectName as string); + if (project) { + const path = project.toPath(fileName); + const sourceFile = project.getSourceFile(path); + if (sourceFile && sourceFile.resolvedPath === path) return sourceFile; + } + + // Need to look for other files. + const info = this.getOrCreateScriptInfoNotOpenedByClient(fileName, (project || this).currentDirectory, project ? project.directoryStructureHost : this.host); + if (!info) return undefined; + + // Key doesnt matter since its only for text and lines + if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile; + if (info.textStorage.sourceFileLike) return info.textStorage.sourceFileLike; + + info.textStorage.sourceFileLike = { + get text() { + Debug.fail("shouldnt need text"); + return ""; + }, + getLineAndCharacterOfPosition: pos => { + const lineOffset = info.positionToLineOffset(pos); + return { line: lineOffset.line - 1, character: lineOffset.offset - 1 }; + }, + getPositionOfLineAndCharacter: (line, character) => info.lineOffsetToPosition(line + 1, character + 1) + }; + return info.textStorage.sourceFileLike; + } + setHostConfiguration(args: protocol.ConfigureRequestArguments) { if (args.file) { const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file)); diff --git a/src/server/project.ts b/src/server/project.ts index 566a2b9abe9..6ea9589cb4b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -505,62 +505,12 @@ namespace ts.server { /*@internal*/ getDocumentPositionMapper(fileName: string): DocumentPositionMapper | undefined { - const declarationInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); - if (!declarationInfo) return undefined; - - declarationInfo.getSnapshot(); // Ensure synchronized - const existingMapper = declarationInfo.textStorage.mapper; - if (existingMapper !== undefined) { - return existingMapper ? existingMapper : undefined; - } - - // Create the mapper - declarationInfo.mapInfo = undefined; - - const mapper = getDocumentPositionMapper({ - getCanonicalFileName: this.projectService.toCanonicalFileName, - log: s => this.log(s), - readMapFile: f => this.readMapFile(f, declarationInfo), - getSourceFileLike: f => this.getSourceFileLike(f) - }, declarationInfo.fileName, declarationInfo.textStorage.getLineInfo()); - declarationInfo.textStorage.mapper = mapper || false; - return mapper; - } - - private readMapFile(fileName: string, declarationInfo: ScriptInfo) { - const mapInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); - if (!mapInfo) return undefined; - declarationInfo.mapInfo = mapInfo; - const snap = mapInfo.getSnapshot(); - return snap.getText(0, snap.getLength()); + return this.projectService.getDocumentPositionMapper(fileName, this); } /*@internal*/ getSourceFileLike(fileName: string) { - const path = this.toPath(fileName); - const sourceFile = this.getSourceFile(path); - if (sourceFile && sourceFile.resolvedPath === path) return sourceFile; - - // Need to look for other files. - const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost); - if (!info) return undefined; - - // Key doesnt matter since its only for text and lines - if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile; - if (info.textStorage.sourceFileLike) return info.textStorage.sourceFileLike; - - info.textStorage.sourceFileLike = { - get text() { - Debug.fail("shouldnt need text"); - return ""; - }, - getLineAndCharacterOfPosition: pos => { - const lineOffset = info.positionToLineOffset(pos); - return { line: lineOffset.line - 1, character: lineOffset.offset - 1 }; - }, - getPositionOfLineAndCharacter: (line, character) => info.lineOffsetToPosition(line + 1, character + 1) - }; - return info.textStorage.sourceFileLike; + return this.projectService.getSourceFileLike(fileName, this); } private shouldEmitFile(scriptInfo: ScriptInfo) { diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index c679688e6c0..00d2696abf5 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -42,12 +42,12 @@ namespace ts { } else if (host.readFile) { const file = getSourceFileLike(fileName); - mapper = file && ts.getDocumentPositionMapper({ - getSourceFileLike, - getCanonicalFileName, - log: s => host.log(s), - readMapFile: f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined - }, fileName, getLineInfo(file.text, getLineStarts(file))); + mapper = file && ts.getDocumentPositionMapper( + { getSourceFileLike, getCanonicalFileName, log: s => host.log(s) }, + fileName, + getLineInfo(file.text, getLineStarts(file)), + f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined + ); } documentPositionMappers.set(path, mapper || identitySourceMapConsumer); return mapper || identitySourceMapConsumer; @@ -118,7 +118,6 @@ namespace ts { } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - // TODO:: shkamat const file = getSourceFileLike(fileName)!; // TODO: GH#18217 return file.getLineAndCharacterOfPosition(position); } @@ -129,11 +128,11 @@ namespace ts { } } - export interface GetDocumentPositionMapperHost extends DocumentPositionMapperHost { - readMapFile(fileName: string): string | undefined; - } - - export function getDocumentPositionMapper(host: GetDocumentPositionMapperHost, generatedFileName: string, generatedFileLineInfo: LineInfo) { + export function getDocumentPositionMapper( + host: DocumentPositionMapperHost, + generatedFileName: string, + generatedFileLineInfo: LineInfo, + readMapFile: (fileName: string) => string | undefined) { let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); if (mapFileName) { const match = base64UrlRegExp.exec(mapFileName); @@ -153,7 +152,7 @@ namespace ts { possibleMapLocations.push(generatedFileName + ".map"); for (const location of possibleMapLocations) { const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); - const mapFileContents = host.readMapFile(mapFileName); + const mapFileContents = readMapFile(mapFileName); if (mapFileContents) { return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b788d46ac9f..1a104716c28 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8210,7 +8210,6 @@ declare namespace ts.server { getGlobalProjectErrors(): ReadonlyArray; getAllProjectErrors(): ReadonlyArray; getLanguageService(ensureSynchronized?: boolean): LanguageService; - private readMapFile; private shouldEmitFile; getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** From 5c920f336812ef910b75a9ad3a325d713a7b9ef9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 16 Nov 2018 12:31:23 -0800 Subject: [PATCH 010/120] Dont unnecessarily make textStorage internal --- src/server/editorServices.ts | 34 ++++++++++--------- src/server/scriptInfo.ts | 22 +++++++----- src/testRunner/unittests/textStorage.ts | 14 ++++---- .../reference/api/tsserverlibrary.d.ts | 1 + 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index dd05b562cff..49f94f3fc95 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2201,7 +2201,7 @@ namespace ts.server { if (!declarationInfo) return undefined; declarationInfo.getSnapshot(); // Ensure synchronized - const existingMapper = declarationInfo.textStorage.mapper; + const existingMapper = declarationInfo.mapper; if (existingMapper !== undefined) { return existingMapper ? existingMapper : undefined; } @@ -2220,11 +2220,11 @@ namespace ts.server { const mapper = getDocumentPositionMapper( { getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName) }, declarationInfo.fileName, - declarationInfo.textStorage.getLineInfo(), + declarationInfo.getLineInfo(), readMapFile ); readMapFile = undefined; // Remove ref to project - declarationInfo.textStorage.mapper = mapper || false; + declarationInfo.mapper = mapper || false; return mapper; } @@ -2243,20 +2243,22 @@ namespace ts.server { // Key doesnt matter since its only for text and lines if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile; - if (info.textStorage.sourceFileLike) return info.textStorage.sourceFileLike; - info.textStorage.sourceFileLike = { - get text() { - Debug.fail("shouldnt need text"); - return ""; - }, - getLineAndCharacterOfPosition: pos => { - const lineOffset = info.positionToLineOffset(pos); - return { line: lineOffset.line - 1, character: lineOffset.offset - 1 }; - }, - getPositionOfLineAndCharacter: (line, character) => info.lineOffsetToPosition(line + 1, character + 1) - }; - return info.textStorage.sourceFileLike; + // Create sourceFileLike + if (!info.sourceFileLike) { + info.sourceFileLike = { + get text() { + Debug.fail("shouldnt need text"); + return ""; + }, + getLineAndCharacterOfPosition: pos => { + const lineOffset = info.positionToLineOffset(pos); + return { line: lineOffset.line - 1, character: lineOffset.offset - 1 }; + }, + getPositionOfLineAndCharacter: (line, character) => info.lineOffsetToPosition(line + 1, character + 1) + }; + } + return info.sourceFileLike; } setHostConfiguration(args: protocol.ConfigureRequestArguments) { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 3502fff1556..71b8fe2f2b5 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -46,9 +46,6 @@ namespace ts.server { */ private pendingReloadFromDisk = false; - mapper: DocumentPositionMapper | false | undefined = false; - sourceFileLike: SourceFileLike | undefined; - constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath, initialVersion: ScriptInfoVersion | undefined, private readonly info: ScriptInfo) { this.version = initialVersion || { svc: 0, text: 0 }; } @@ -73,8 +70,8 @@ namespace ts.server { this.text = newText; this.lineMap = undefined; this.fileSize = undefined; - this.mapper = undefined; - this.sourceFileLike = undefined; + this.info.mapper = undefined; + this.info.sourceFileLike = undefined; this.version.text++; } @@ -84,8 +81,8 @@ namespace ts.server { this.text = undefined; this.lineMap = undefined; this.fileSize = undefined; - this.mapper = undefined; - this.sourceFileLike = undefined; + this.info.mapper = undefined; + this.info.sourceFileLike = undefined; } /** @@ -287,7 +284,7 @@ namespace ts.server { /* @internal */ fileWatcher: FileWatcher | undefined; - /* @internal */ textStorage: TextStorage; + private textStorage: TextStorage; /*@internal*/ readonly isDynamic: boolean; @@ -304,6 +301,10 @@ namespace ts.server { /*@internal*/ mapInfo?: ScriptInfo; + /*@internal*/ + mapper: DocumentPositionMapper | false | undefined = false; + /*@internal*/ + sourceFileLike: SourceFileLike | undefined; constructor( private readonly host: ServerHost, @@ -583,5 +584,10 @@ namespace ts.server { public isJavaScript() { return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX; } + + /*@internal*/ + getLineInfo(): LineInfo { + return this.textStorage.getLineInfo(); + } } } diff --git a/src/testRunner/unittests/textStorage.ts b/src/testRunner/unittests/textStorage.ts index f5fbb512e52..724dc8e4ed3 100644 --- a/src/testRunner/unittests/textStorage.ts +++ b/src/testRunner/unittests/textStorage.ts @@ -14,8 +14,8 @@ namespace ts.textStorage { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!); - const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); ts1.useScriptVersionCache_TestOnly(); ts2.useText(); @@ -49,7 +49,7 @@ namespace ts.textStorage { it("should switch to script version cache if necessary", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); ts1.getSnapshot(); assert.isFalse(ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1"); @@ -67,7 +67,7 @@ namespace ts.textStorage { it("should be able to return the file size immediately after construction", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); assert.strictEqual(f.content.length, ts1.getTelemetryFileSize()); }); @@ -75,7 +75,7 @@ namespace ts.textStorage { it("should be able to return the file size when backed by text", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); ts1.useText(f.content); assert.isFalse(ts1.hasScriptVersionCache_TestOnly()); @@ -86,7 +86,7 @@ namespace ts.textStorage { it("should be able to return the file size when backed by a script version cache", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); ts1.useScriptVersionCache_TestOnly(); assert.isTrue(ts1.hasScriptVersionCache_TestOnly()); @@ -126,7 +126,7 @@ namespace ts.textStorage { const host = projectSystem.createServerHost([changingFile]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(changingFile.path), /*initialVersion*/ undefined, /*info*/undefined!); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(changingFile.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); assert.isTrue(ts1.reloadFromDisk()); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1a104716c28..2b78178587e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8046,6 +8046,7 @@ declare namespace ts.server { readonly containingProjects: Project[]; private formatSettings; private preferences; + private textStorage; constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent: boolean, path: Path, initialVersion?: ScriptInfoVersion); isScriptOpen(): boolean; open(newText: string): void; From 0aa4da43adc0f73012a59942d03f2df04954d6be Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 16 Nov 2018 11:52:29 -0800 Subject: [PATCH 011/120] Life time of declaration, sources and map infos Map Info and sources is not ideal and need to revisited since we need to update mapper and projects correctly // TODO: lifetime of source project and declaration map --- src/server/editorServices.ts | 44 ++++++--- src/server/scriptInfo.ts | 2 + .../unittests/tsserverProjectSystem.ts | 95 +++++++++++++++---- .../reference/api/tsserverlibrary.d.ts | 4 - 4 files changed, 112 insertions(+), 33 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 49f94f3fc95..d98aa948113 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -437,7 +437,8 @@ namespace ts.server { /** * Container of all known scripts */ - private readonly filenameToScriptInfo = createMap(); + /*@internal*/ + readonly filenameToScriptInfo = createMap(); private readonly scriptInfoInNodeModulesWatchers = createMap (); /** * Contains all the deleted script info's version information so that @@ -2209,6 +2210,7 @@ namespace ts.server { // Create the mapper declarationInfo.mapInfo = undefined; + // TODO: shkamat Lifetime of declarationInfo and mapInfo let readMapFile: ((fileName: string) => string | undefined) | undefined = fileName => { const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); if (!mapInfo) return undefined; @@ -2218,7 +2220,7 @@ namespace ts.server { }; const projectName = project.projectName; const mapper = getDocumentPositionMapper( - { getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName) }, + { getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName, declarationInfo) }, declarationInfo.fileName, declarationInfo.getLineInfo(), readMapFile @@ -2229,8 +2231,8 @@ namespace ts.server { } /*@internal*/ - getSourceFileLike(fileName: string, projectName: string | Project) { - const project = (projectName as Project).projectName ? projectName as Project : this.findProject(projectName as string); + getSourceFileLike(fileName: string, projectNameOrProject: string | Project, declarationInfo?: ScriptInfo) { + const project = (projectNameOrProject as Project).projectName ? projectNameOrProject as Project : this.findProject(projectNameOrProject as string); if (project) { const path = project.toPath(fileName); const sourceFile = project.getSourceFile(path); @@ -2241,6 +2243,11 @@ namespace ts.server { const info = this.getOrCreateScriptInfoNotOpenedByClient(fileName, (project || this).currentDirectory, project ? project.directoryStructureHost : this.host); if (!info) return undefined; + // Attach as source + if (declarationInfo && declarationInfo.mapInfo && info !== declarationInfo) { + (declarationInfo.mapInfo.sourceInfos || (declarationInfo.mapInfo.sourceInfos = createMap())).set(info.path, true); + } + // Key doesnt matter since its only for text and lines if (info.cacheSourceFile) return info.cacheSourceFile.sourceFile; @@ -2556,13 +2563,7 @@ namespace ts.server { // when some file/s were closed which resulted in project removal. // It was then postponed to cleanup these script infos so that they can be reused if // the file from that old project is reopened because of opening file from here. - this.filenameToScriptInfo.forEach(info => { - if (!info.isScriptOpen() && info.isOrphan()) { - // if there are not projects that include this script info - delete it - this.stopWatchingScriptInfo(info); - this.deleteScriptInfo(info); - } - }); + this.removeOrphanScriptInfos(); this.printProjects(); @@ -2605,6 +2606,27 @@ namespace ts.server { } } + private removeOrphanScriptInfos() { + const toRemoveScriptInfos = cloneMap(this.filenameToScriptInfo); + this.filenameToScriptInfo.forEach(info => { + if (info.isScriptOpen() || !info.isOrphan()) { + toRemoveScriptInfos.delete(info.path); + if (info.mapInfo) { + toRemoveScriptInfos.delete(info.mapInfo.path); + if (info.mapInfo.sourceInfos) { + info.mapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); + } + } + } + }); + + toRemoveScriptInfos.forEach(info => { + // if there are not projects that include this script info - delete it + this.stopWatchingScriptInfo(info); + this.deleteScriptInfo(info); + }); + } + private telemetryOnOpenFile(scriptInfo: ScriptInfo): void { if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) { return; diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 71b8fe2f2b5..3d8c737064f 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -302,6 +302,8 @@ namespace ts.server { /*@internal*/ mapInfo?: ScriptInfo; /*@internal*/ + sourceInfos?: Map; + /*@internal*/ mapper: DocumentPositionMapper | false | undefined = false; /*@internal*/ sourceFileLike: SourceFileLike | undefined; diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 6681b4bfa54..217149efd88 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -475,6 +475,10 @@ namespace ts.projectSystem { checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); } + function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray) { + checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); + } + function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { const start = str.indexOf(substring); Debug.assert(start !== -1); @@ -10667,7 +10671,7 @@ declare class TestLib { }); }); - it("can go to definition correctly", () => { + describe("with main and depedency project", () => { const projectLocation = "/user/username/projects/myproject"; const dependecyLocation = `${projectLocation}/dependency`; const mainLocation = `${projectLocation}/main`; @@ -10704,24 +10708,79 @@ fn5();` }) }; - const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile]; - const host = createHost(files, [mainConfig.path]); - const session = createSession(host); - const service = session.getProjectService(); - openFilesForSession([mainTs], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`]); - for (let i = 0; i < 5; i++) { - const startSpan = { line: i + 5, offset: 1 }; - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...startSpan } - }).response as protocol.DefinitionInfoAndBoundSpan; - assert.deepEqual(response, { - definitions: [{ file: dependencyTs.path, start: { line: i + 1, offset: 17 }, end: { line: i + 1, offset: 20 } }], - textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } - }); + const randomFile: File = { + path: `${projectLocation}/random/random.ts`, + content: "let a = 10;" + }; + const randomConfig: File = { + path: `${projectLocation}/random/tsconfig.json`, + content: "{}" + }; + + const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; + + function verifyInfos(service: server.ProjectService, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + checkScriptInfos(service, openInfos.concat(closedInfos)); + checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); } + + it("can go to definition correctly", () => { + const host = createHost(files, [mainConfig.path]); + const session = createSession(host); + const service = session.getProjectService(); + openFilesForSession([mainTs], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + checkProjectActualFiles(service.configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`]); + for (let i = 0; i < 5; i++) { + const startSpan = { line: i + 5, offset: 1 }; + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...startSpan } + }).response as protocol.DefinitionInfoAndBoundSpan; + assert.deepEqual(response, { + definitions: [{ file: dependencyTs.path, start: { line: i + 1, offset: 17 }, end: { line: i + 1, offset: 20 } }], + textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } + }); + } + checkNumberOfProjects(service, { configuredProjects: 1 }); + const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + verifyInfos(service, host, [mainTs.path], closedInfos, [mainConfig.path]); + + openFilesForSession([randomFile], session); + verifyInfos(service, host, [mainTs.path, randomFile.path], closedInfos, [mainConfig.path, randomConfig.path]); + }); + + it("rename locations from depedency", () => { + const host = createHost(files, [mainConfig.path]); + const session = createSession(host); + const service = session.getProjectService(); + openFilesForSession([dependencyTs, randomFile], session); + checkNumberOfProjects(service, { configuredProjects: 2 }); + checkProjectActualFiles(service.configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); + debugger; + for (let i = 0; i < 5; i++) { + const startSpan = { line: i + 1, offset: 17 }; + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...startSpan } + }).response as protocol.RenameResponseBody; + assert.deepEqual(response.locs, [{ + file: dependencyTs.path, + locs: [{ start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }] + }]); + } + checkNumberOfProjects(service, { configuredProjects: 2 }); + const openInfos = [dependencyTs.path, randomFile.path]; + const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + const otherWatchedFiles = [dependencyConfig.path, randomConfig.path]; + verifyInfos(service, host, openInfos, closedInfos, otherWatchedFiles); + + // Collect the orphan projects and infos + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + + verifyInfos(service, host, openInfos, closedInfos, otherWatchedFiles); + }); }); }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2b78178587e..2bf91fac4f4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8497,10 +8497,6 @@ declare namespace ts.server { syntaxOnly?: boolean; } class ProjectService { - /** - * Container of all known scripts - */ - private readonly filenameToScriptInfo; private readonly scriptInfoInNodeModulesWatchers; /** * Contains all the deleted script info's version information so that From 56a39b754c655f726ff7efbaebf8cec8c9c2452d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 29 Nov 2018 14:14:45 -0800 Subject: [PATCH 012/120] Keep alive declaration script info and map file info if source file info is alive --- src/server/editorServices.ts | 36 +++++++++++++------ src/server/project.ts | 4 +-- src/server/scriptInfo.ts | 15 +++++--- src/services/services.ts | 2 +- src/services/sourcemaps.ts | 14 ++++---- src/services/types.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 1 - 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d98aa948113..5c70c7be8bf 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2197,8 +2197,8 @@ namespace ts.server { } /*@internal*/ - getDocumentPositionMapper(fileName: string, project: Project): DocumentPositionMapper | undefined { - const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); + getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined { + const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, project.directoryStructureHost); if (!declarationInfo) return undefined; declarationInfo.getSnapshot(); // Ensure synchronized @@ -2210,7 +2210,6 @@ namespace ts.server { // Create the mapper declarationInfo.mapInfo = undefined; - // TODO: shkamat Lifetime of declarationInfo and mapInfo let readMapFile: ((fileName: string) => string | undefined) | undefined = fileName => { const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); if (!mapInfo) return undefined; @@ -2227,6 +2226,11 @@ namespace ts.server { ); readMapFile = undefined; // Remove ref to project declarationInfo.mapper = mapper || false; + if (sourceFileName && mapper) { + // Attach as source + const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; + (declarationInfo.mapInfo!.sourceInfos || (declarationInfo.mapInfo!.sourceInfos = createMap())).set(sourceInfo.path, true); + } return mapper; } @@ -2609,13 +2613,25 @@ namespace ts.server { private removeOrphanScriptInfos() { const toRemoveScriptInfos = cloneMap(this.filenameToScriptInfo); this.filenameToScriptInfo.forEach(info => { - if (info.isScriptOpen() || !info.isOrphan()) { - toRemoveScriptInfos.delete(info.path); - if (info.mapInfo) { - toRemoveScriptInfos.delete(info.mapInfo.path); - if (info.mapInfo.sourceInfos) { - info.mapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); - } + // If script info is open or orphan, retain it and its dependencies + if (!info.isScriptOpen() && info.isOrphan()) { + // Otherwise if there is any source info that is alive, this alive too + if (!info.mapInfo || !info.mapInfo.sourceInfos) return; + if (!forEachKey(info.mapInfo.sourceInfos, path => { + const info = this.getScriptInfoForPath(path as Path)!; + return info.isScriptOpen() || !info.isOrphan(); + })) { + return; + } + } + + // Retain this script info + toRemoveScriptInfos.delete(info.path); + if (info.mapInfo) { + // And map file info and source infos + toRemoveScriptInfos.delete(info.mapInfo.path); + if (info.mapInfo.sourceInfos) { + info.mapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); } } }); diff --git a/src/server/project.ts b/src/server/project.ts index 6ea9589cb4b..a28e9c35be5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -504,8 +504,8 @@ namespace ts.server { } /*@internal*/ - getDocumentPositionMapper(fileName: string): DocumentPositionMapper | undefined { - return this.projectService.getDocumentPositionMapper(fileName, this); + getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined { + return this.projectService.getDocumentPositionMapper(this, generatedFileName, sourceFileName); } /*@internal*/ diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 3d8c737064f..d2f230178d3 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -64,14 +64,20 @@ namespace ts.server { this.switchToScriptVersionCache(); } + private resetSourceMapInfo() { + this.info.mapper = undefined; + this.info.sourceFileLike = undefined; + this.info.mapInfo = undefined; + this.info.sourceInfos = undefined; + } + /** Public for testing */ public useText(newText?: string) { this.svc = undefined; this.text = newText; this.lineMap = undefined; this.fileSize = undefined; - this.info.mapper = undefined; - this.info.sourceFileLike = undefined; + this.resetSourceMapInfo(); this.version.text++; } @@ -81,8 +87,7 @@ namespace ts.server { this.text = undefined; this.lineMap = undefined; this.fileSize = undefined; - this.info.mapper = undefined; - this.info.sourceFileLike = undefined; + this.resetSourceMapInfo(); } /** @@ -304,7 +309,7 @@ namespace ts.server { /*@internal*/ sourceInfos?: Map; /*@internal*/ - mapper: DocumentPositionMapper | false | undefined = false; + mapper?: DocumentPositionMapper | false; /*@internal*/ sourceFileLike: SourceFileLike | undefined; diff --git a/src/services/services.ts b/src/services/services.ts index a0c41ffe94d..e8dfb8640e0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1145,7 +1145,7 @@ namespace ts { getProgram, fileExists: host.fileExists && (f => host.fileExists!(f)), readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)), - getDocumentPositionMapper: host.getDocumentPositionMapper && (f => host.getDocumentPositionMapper!(f)), + getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)), getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)), log }); diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 00d2696abf5..ce692fc2184 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -16,7 +16,7 @@ namespace ts { fileExists?(path: string): boolean; readFile?(path: string, encoding?: string): string | undefined; getSourceFileLike?(fileName: string): SourceFileLike | undefined; - getDocumentPositionMapper?(fileName: string): DocumentPositionMapper | undefined; + getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; log(s: string): void; } @@ -31,20 +31,20 @@ namespace ts { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } - function getDocumentPositionMapper(fileName: string) { - const path = toPath(fileName); + function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) { + const path = toPath(generatedFileName); const value = documentPositionMappers.get(path); if (value) return value; let mapper: DocumentPositionMapper | undefined; if (host.getDocumentPositionMapper) { - mapper = host.getDocumentPositionMapper(fileName); + mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName); } else if (host.readFile) { - const file = getSourceFileLike(fileName); + const file = getSourceFileLike(generatedFileName); mapper = file && ts.getDocumentPositionMapper( { getSourceFileLike, getCanonicalFileName, log: s => host.log(s) }, - fileName, + generatedFileName, getLineInfo(file.text, getLineStarts(file)), f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined ); @@ -78,7 +78,7 @@ namespace ts { getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); if (declarationPath === undefined) return undefined; - const newLoc = getDocumentPositionMapper(declarationPath).getGeneratedPosition(info); + const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info); return newLoc === info ? undefined : newLoc; } diff --git a/src/services/types.ts b/src/services/types.ts index d3a8f8cdd52..5100a999306 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -234,7 +234,7 @@ namespace ts { writeFile?(fileName: string, content: string): void; /* @internal */ - getDocumentPositionMapper?(fileName: string): DocumentPositionMapper | undefined; + getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 217149efd88..59a6f6019d1 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10757,7 +10757,6 @@ fn5();` openFilesForSession([dependencyTs, randomFile], session); checkNumberOfProjects(service, { configuredProjects: 2 }); checkProjectActualFiles(service.configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); - debugger; for (let i = 0; i < 5; i++) { const startSpan = { line: i + 1, offset: 17 }; const response = session.executeCommandSeq({ From 751cb9e2c3e63fdbb7f373427744ce5a266be60f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 30 Nov 2018 16:29:21 -0800 Subject: [PATCH 013/120] Update source and declaration projects on update to declaration file or map file TODO: add tests --- src/server/editorServices.ts | 89 +++++++++++++++---- src/server/scriptInfo.ts | 16 ++-- .../reference/api/tsserverlibrary.d.ts | 4 + 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5c70c7be8bf..5eb009fcb52 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -945,10 +945,39 @@ namespace ts.server { // this file and set of inferred projects info.delayReloadNonMixedContentFile(); this.delayUpdateProjectGraphs(info.containingProjects); + this.handleSourceMapProjects(info); } } } + private handleSourceMapProjects(info: ScriptInfo) { + // Change in d.ts, update source projects as well + if (info.sourceMapFilePath) { + const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + if (sourceMapFileInfo && sourceMapFileInfo.sourceInfos) { + this.delayUpdateSourceInfoProjects(sourceMapFileInfo.sourceInfos); + } + } + // Change in mapInfo, update declarationProjects and source projects + if (info.sourceInfos) { + this.delayUpdateSourceInfoProjects(info.sourceInfos); + } + if (info.declarationInfoPath) { + this.delayUpdateProjectsOfScriptInfoPath(info.declarationInfoPath); + } + } + + private delayUpdateSourceInfoProjects(sourceInfos: Map) { + sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path)); + } + + private delayUpdateProjectsOfScriptInfoPath(path: Path) { + const info = this.getScriptInfoForPath(path); + if (info) { + this.delayUpdateProjectGraphs(info.containingProjects); + } + } + private handleDeletedFile(info: ScriptInfo) { this.stopWatchingScriptInfo(info); @@ -962,6 +991,7 @@ namespace ts.server { // update projects to make sure that set of referenced files is correct this.delayUpdateProjectGraphs(containingProjects); + this.handleSourceMapProjects(info); } } @@ -2201,19 +2231,30 @@ namespace ts.server { const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, project.directoryStructureHost); if (!declarationInfo) return undefined; + // Try to get from cache declarationInfo.getSnapshot(); // Ensure synchronized - const existingMapper = declarationInfo.mapper; - if (existingMapper !== undefined) { - return existingMapper ? existingMapper : undefined; + if (declarationInfo.sourceMapFilePath !== undefined) { + // Doesnt have sourceMap + if (!declarationInfo.sourceMapFilePath) return undefined; + + // Ensure mapper is synchronized + const mapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); + if (mapFileInfo) { + mapFileInfo.getSnapshot(); + if (mapFileInfo.mapper !== undefined) { + return mapFileInfo.mapper ? mapFileInfo.mapper : undefined; + } + } } // Create the mapper - declarationInfo.mapInfo = undefined; + declarationInfo.sourceMapFilePath = undefined; + let sourceMapFileInfo: ScriptInfo | undefined; let readMapFile: ((fileName: string) => string | undefined) | undefined = fileName => { const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); if (!mapInfo) return undefined; - declarationInfo.mapInfo = mapInfo; + sourceMapFileInfo = mapInfo; const snap = mapInfo.getSnapshot(); return snap.getText(0, snap.getLength()); }; @@ -2225,11 +2266,17 @@ namespace ts.server { readMapFile ); readMapFile = undefined; // Remove ref to project - declarationInfo.mapper = mapper || false; - if (sourceFileName && mapper) { - // Attach as source - const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; - (declarationInfo.mapInfo!.sourceInfos || (declarationInfo.mapInfo!.sourceInfos = createMap())).set(sourceInfo.path, true); + if (sourceMapFileInfo) { + declarationInfo.sourceMapFilePath = sourceMapFileInfo.path; + sourceMapFileInfo.mapper = mapper || false; + if (sourceFileName && mapper) { + // Attach as source + const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; + (sourceMapFileInfo.sourceInfos || (sourceMapFileInfo.sourceInfos = createMap())).set(sourceInfo.path, true); + } + } + else { + declarationInfo.sourceMapFilePath = false; } return mapper; } @@ -2248,8 +2295,11 @@ namespace ts.server { if (!info) return undefined; // Attach as source - if (declarationInfo && declarationInfo.mapInfo && info !== declarationInfo) { - (declarationInfo.mapInfo.sourceInfos || (declarationInfo.mapInfo.sourceInfos = createMap())).set(info.path, true); + if (declarationInfo && declarationInfo.sourceMapFilePath && info !== declarationInfo) { + const sourceMapInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); + if (sourceMapInfo) { + (sourceMapInfo.sourceInfos || (sourceMapInfo.sourceInfos = createMap())).set(info.path, true); + } } // Key doesnt matter since its only for text and lines @@ -2616,8 +2666,10 @@ namespace ts.server { // If script info is open or orphan, retain it and its dependencies if (!info.isScriptOpen() && info.isOrphan()) { // Otherwise if there is any source info that is alive, this alive too - if (!info.mapInfo || !info.mapInfo.sourceInfos) return; - if (!forEachKey(info.mapInfo.sourceInfos, path => { + if (!info.sourceMapFilePath) return; + const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + if (!sourceMapInfo || !sourceMapInfo.sourceInfos) return; + if (!forEachKey(sourceMapInfo.sourceInfos, path => { const info = this.getScriptInfoForPath(path as Path)!; return info.isScriptOpen() || !info.isOrphan(); })) { @@ -2627,11 +2679,12 @@ namespace ts.server { // Retain this script info toRemoveScriptInfos.delete(info.path); - if (info.mapInfo) { + if (info.sourceMapFilePath) { // And map file info and source infos - toRemoveScriptInfos.delete(info.mapInfo.path); - if (info.mapInfo.sourceInfos) { - info.mapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); + toRemoveScriptInfos.delete(info.sourceMapFilePath); + const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + if (sourceMapInfo && sourceMapInfo.sourceInfos) { + sourceMapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); } } }); diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index d2f230178d3..29685112188 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -65,10 +65,11 @@ namespace ts.server { } private resetSourceMapInfo() { - this.info.mapper = undefined; this.info.sourceFileLike = undefined; - this.info.mapInfo = undefined; + this.info.sourceMapFilePath = undefined; + this.info.declarationInfoPath = undefined; this.info.sourceInfos = undefined; + this.info.mapper = undefined; } /** Public for testing */ @@ -305,13 +306,18 @@ namespace ts.server { mTime?: number; /*@internal*/ - mapInfo?: ScriptInfo; + sourceFileLike?: SourceFileLike; + + /*@internal*/ + sourceMapFilePath?: Path | false; + + // Present on sourceMapFile info + /*@internal*/ + declarationInfoPath?: Path; /*@internal*/ sourceInfos?: Map; /*@internal*/ mapper?: DocumentPositionMapper | false; - /*@internal*/ - sourceFileLike: SourceFileLike | undefined; constructor( private readonly host: ServerHost, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2bf91fac4f4..fa6990c07f4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8595,6 +8595,9 @@ declare namespace ts.server { getHostFormatCodeOptions(): FormatCodeSettings; getHostPreferences(): protocol.UserPreferences; private onSourceFileChanged; + private handleSourceMapProjects; + private delayUpdateSourceInfoProjects; + private delayUpdateProjectsOfScriptInfoPath; private handleDeletedFile; private onConfigChangedForConfiguredProject; /** @@ -8722,6 +8725,7 @@ declare namespace ts.server { private findExternalProjectContainingOpenScriptInfo; openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; private removeOrphanConfiguredProjects; + private removeOrphanScriptInfos; private telemetryOnOpenFile; /** * Close file whose contents is managed by the client From 0113f43632b2d3a0de3f25e308b64abed7cf27e6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 3 Dec 2018 12:31:00 -0800 Subject: [PATCH 014/120] Fix the edits clamping from #28583 after rebasing to master --- src/compiler/scanner.ts | 15 ++++++--------- src/compiler/sourcemap.ts | 4 ++-- src/compiler/types.ts | 2 +- src/server/editorServices.ts | 2 +- src/server/scriptInfo.ts | 12 ++++++++---- src/services/services.ts | 4 ++-- src/services/sourcemaps.ts | 1 - 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index e9ccc23d6eb..42589c4228e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -337,17 +337,14 @@ namespace ts { return result; } - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number { - return sourceFile.getPositionOfLineAndCharacter ? - sourceFile.getPositionOfLineAndCharacter(line, character) : - computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text); - } - + export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; /* @internal */ - export function getPositionOfLineAndCharacterWithEdits(sourceFile: SourceFileLike, line: number, character: number): number { + // tslint:disable-next-line:unified-signatures + export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; + export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { return sourceFile.getPositionOfLineAndCharacter ? - sourceFile.getPositionOfLineAndCharacter(line, character) : - computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, /*allowEdits*/ true); + sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : + computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); } /* @internal */ diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 190cac3b761..00a78b0a93c 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -616,7 +616,7 @@ namespace ts { function processMapping(mapping: Mapping): MappedPosition { const generatedPosition = generatedFile !== undefined - ? getPositionOfLineAndCharacterWithEdits(generatedFile, mapping.generatedLine, mapping.generatedCharacter) + ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) : -1; let source: string | undefined; let sourcePosition: number | undefined; @@ -624,7 +624,7 @@ namespace ts { const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); source = map.sources[mapping.sourceIndex]; sourcePosition = sourceFile !== undefined - ? getPositionOfLineAndCharacterWithEdits(sourceFile, mapping.sourceLine, mapping.sourceCharacter) + ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) : -1; } return { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e654f4faab7..772c42e5aa7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2615,7 +2615,7 @@ namespace ts { readonly text: string; lineMap?: ReadonlyArray; /* @internal */ - getPositionOfLineAndCharacter?(line: number, character: number): number; + getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5eb009fcb52..4d3cf160707 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2316,7 +2316,7 @@ namespace ts.server { const lineOffset = info.positionToLineOffset(pos); return { line: lineOffset.line - 1, character: lineOffset.offset - 1 }; }, - getPositionOfLineAndCharacter: (line, character) => info.lineOffsetToPosition(line + 1, character + 1) + getPositionOfLineAndCharacter: (line, character, allowEdits) => info.lineOffsetToPosition(line + 1, character + 1, allowEdits) }; } return info.sourceFileLike; diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 29685112188..326df8c2219 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -186,9 +186,9 @@ namespace ts.server { * @param line 1 based index * @param offset 1 based index */ - lineOffsetToPosition(line: number, offset: number): number { + lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number { if (!this.useScriptVersionCacheIfValidOrOpen()) { - return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text); + return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text, allowEdits); } // TODO: assert this offset is actually on the line @@ -586,8 +586,12 @@ namespace ts.server { * @param line 1 based index * @param offset 1 based index */ - lineOffsetToPosition(line: number, offset: number): number { - return this.textStorage.lineOffsetToPosition(line, offset); + lineOffsetToPosition(line: number, offset: number): number; + /*@internal*/ + // tslint:disable-next-line:unified-signatures + lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number; + lineOffsetToPosition(line: number, offset: number, allowEdits?: true): number { + return this.textStorage.lineOffsetToPosition(line, offset, allowEdits); } positionToLineOffset(position: number): protocol.Location { diff --git a/src/services/services.ts b/src/services/services.ts index e8dfb8640e0..15f9709cb38 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -602,8 +602,8 @@ namespace ts { return getLineStarts(this); } - public getPositionOfLineAndCharacter(line: number, character: number): number { - return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text); + public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { + return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); } public getLineEndOfPosition(pos: number): number { diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index ce692fc2184..12991d49802 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -97,7 +97,6 @@ namespace ts { const fileFromCache = sourceFileLike.get(path); if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; - // TODO: should ask host instead? if (!host.readFile || host.fileExists && !host.fileExists(path)) { sourceFileLike.set(path, false); return undefined; From d0976509c943048ebb30931081b1b110b9aa0b72 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 3 Dec 2018 14:13:09 -0800 Subject: [PATCH 015/120] Add tests and fix DocumentPositionMapper creation on updates to d.ts, source file, map file etc --- src/server/editorServices.ts | 18 +- src/server/scriptInfo.ts | 4 +- src/services/sourcemaps.ts | 13 +- .../unittests/tsserverProjectSystem.ts | 227 +++++++++++++----- 4 files changed, 196 insertions(+), 66 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4d3cf160707..a3ce0989b0c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2241,8 +2241,8 @@ namespace ts.server { const mapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); if (mapFileInfo) { mapFileInfo.getSnapshot(); - if (mapFileInfo.mapper !== undefined) { - return mapFileInfo.mapper ? mapFileInfo.mapper : undefined; + if (mapFileInfo.documentPositionMapper !== undefined) { + return mapFileInfo.documentPositionMapper ? mapFileInfo.documentPositionMapper : undefined; } } } @@ -2251,15 +2251,16 @@ namespace ts.server { declarationInfo.sourceMapFilePath = undefined; let sourceMapFileInfo: ScriptInfo | undefined; - let readMapFile: ((fileName: string) => string | undefined) | undefined = fileName => { - const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(fileName, project.currentDirectory, project.directoryStructureHost); + let readMapFile: ReadMapFile | undefined = mapFileName => { + const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, project.directoryStructureHost); if (!mapInfo) return undefined; sourceMapFileInfo = mapInfo; const snap = mapInfo.getSnapshot(); + if (mapInfo.documentPositionMapper !== undefined) return mapInfo.documentPositionMapper; return snap.getText(0, snap.getLength()); }; const projectName = project.projectName; - const mapper = getDocumentPositionMapper( + const documentPositionMapper = getDocumentPositionMapper( { getCanonicalFileName: this.toCanonicalFileName, log: s => this.logger.info(s), getSourceFileLike: f => this.getSourceFileLike(f, projectName, declarationInfo) }, declarationInfo.fileName, declarationInfo.getLineInfo(), @@ -2268,8 +2269,9 @@ namespace ts.server { readMapFile = undefined; // Remove ref to project if (sourceMapFileInfo) { declarationInfo.sourceMapFilePath = sourceMapFileInfo.path; - sourceMapFileInfo.mapper = mapper || false; - if (sourceFileName && mapper) { + sourceMapFileInfo.declarationInfoPath = declarationInfo.path; + sourceMapFileInfo.documentPositionMapper = documentPositionMapper || false; + if (sourceFileName && documentPositionMapper) { // Attach as source const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; (sourceMapFileInfo.sourceInfos || (sourceMapFileInfo.sourceInfos = createMap())).set(sourceInfo.path, true); @@ -2278,7 +2280,7 @@ namespace ts.server { else { declarationInfo.sourceMapFilePath = false; } - return mapper; + return documentPositionMapper; } /*@internal*/ diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 326df8c2219..1cd00cdca45 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -69,7 +69,7 @@ namespace ts.server { this.info.sourceMapFilePath = undefined; this.info.declarationInfoPath = undefined; this.info.sourceInfos = undefined; - this.info.mapper = undefined; + this.info.documentPositionMapper = undefined; } /** Public for testing */ @@ -317,7 +317,7 @@ namespace ts.server { /*@internal*/ sourceInfos?: Map; /*@internal*/ - mapper?: DocumentPositionMapper | false; + documentPositionMapper?: DocumentPositionMapper | false; constructor( private readonly host: ServerHost, diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 12991d49802..42b00b8a8d7 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -127,11 +127,17 @@ namespace ts { } } + /** + * string | undefined to contents of map file to create DocumentPositionMapper from it + * DocumentPositionMapper | false to give back cached DocumentPositionMapper + */ + export type ReadMapFile = (mapFileName: string) => string | undefined | DocumentPositionMapper | false; + export function getDocumentPositionMapper( host: DocumentPositionMapperHost, generatedFileName: string, generatedFileLineInfo: LineInfo, - readMapFile: (fileName: string) => string | undefined) { + readMapFile: ReadMapFile) { let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); if (mapFileName) { const match = base64UrlRegExp.exec(mapFileName); @@ -152,9 +158,12 @@ namespace ts { for (const location of possibleMapLocations) { const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); const mapFileContents = readMapFile(mapFileName); - if (mapFileContents) { + if (isString(mapFileContents)) { return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); } + if (mapFileContents !== undefined) { + return mapFileContents || undefined; + } } return undefined; } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 59a6f6019d1..b36783d519f 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10681,7 +10681,8 @@ declare class TestLib { export function fn2() { } export function fn3() { } export function fn4() { } -export function fn5() { }` +export function fn5() { } +` }; const dependencyConfig: File = { path: `${dependecyLocation}/tsconfig.json`, @@ -10698,7 +10699,8 @@ fn1(); fn2(); fn3(); fn4(); -fn5();` +fn5(); +` }; const mainConfig: File = { path: `${mainLocation}/tsconfig.json`, @@ -10719,66 +10721,183 @@ fn5();` const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - function verifyInfos(service: server.ProjectService, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + function verifyScriptInfos(service: server.ProjectService, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { checkScriptInfos(service, openInfos.concat(closedInfos)); checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); } - it("can go to definition correctly", () => { - const host = createHost(files, [mainConfig.path]); - const session = createSession(host); - const service = session.getProjectService(); - openFilesForSession([mainTs], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`]); - for (let i = 0; i < 5; i++) { - const startSpan = { line: i + 5, offset: 1 }; - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...startSpan } - }).response as protocol.DefinitionInfoAndBoundSpan; - assert.deepEqual(response, { - definitions: [{ file: dependencyTs.path, start: { line: i + 1, offset: 17 }, end: { line: i + 1, offset: 20 } }], - textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } - }); - } - checkNumberOfProjects(service, { configuredProjects: 1 }); - const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; - verifyInfos(service, host, [mainTs.path], closedInfos, [mainConfig.path]); + function verifyInfosWithRandom(service: server.ProjectService, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + verifyScriptInfos(service, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); + } - openFilesForSession([randomFile], session); - verifyInfos(service, host, [mainTs.path, randomFile.path], closedInfos, [mainConfig.path, randomConfig.path]); + function verifyOnlyRandomInfos(service: server.ProjectService, host: TestServerHost) { + verifyScriptInfos(service, host, [randomFile.path], [libFile.path], [randomConfig.path]); + } + + function verifyGotoDefintinionFromMainTs(fn: number, session: TestSession) { + const startSpan = { line: fn + 4, offset: 1 }; + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...startSpan } + }).response as protocol.DefinitionInfoAndBoundSpan; + assert.deepEqual(response, { + definitions: [{ file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }], + textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } + }); + } + + function verifyRenameFromDependencyTs(fn: number, session: TestSession) { + const startSpan = { line: fn, offset: 17 }; + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...startSpan } + }).response as protocol.RenameResponseBody; + assert.deepEqual(response.locs, [{ + file: dependencyTs.path, + locs: [{ start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }] + }]); + } + + function verifyAllFnAction(action: (fn: number, session: TestSession) => void, session: TestSession) { + for (let fn = 1; fn <= 5; fn++) { + action(fn, session); + } + } + + function verifyDocumentPositionMapperUpdates( + mainScenario: string, + openFile: File, + expectedProjectActualFiles: ReadonlyArray, + action: (fn: number, session: TestSession) => void, + closedInfos: ReadonlyArray, + openFileLastLine: number) { + const configFile = `${getDirectoryPath(openFile.path)}/tsconfig.json`; + const openInfos = [openFile.path]; + const otherWatchedFiles = [configFile]; + function openTsFile() { + const host = createHost(files, [mainConfig.path]); + const session = createSession(host); + const service = session.getProjectService(); + openFilesForSession([openFile, randomFile], session); + return { host, session, service }; + } + + function checkProject(service: server.ProjectService) { + checkNumberOfProjects(service, { configuredProjects: 2 }); + checkProjectActualFiles(service.configuredProjects.get(configFile)!, expectedProjectActualFiles); + } + + function verifyInfos(service: server.ProjectService, host: TestServerHost) { + verifyInfosWithRandom(service, host, openInfos, closedInfos, otherWatchedFiles); + } + + function verifyDocumentPositionMapper(service: server.ProjectService, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { + assert.strictEqual(service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`), dependencyMap); + if (notEqual) { + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + else { + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + } + + function verifyScenarioWithChanges( + change: (host: TestServerHost, session: TestSession) => void, + afterActionDocumentPositionMapperNotEquals?: true + ) { + const { host, session, service } = openTsFile(); + + // Create DocumentPositionMapper + action(1, session); + const dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; + const documentPositionMapper = dependencyMap.documentPositionMapper; + + // change + change(host, session); + host.runQueuedTimeoutCallbacks(); + checkProject(service); + verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction(action, session); + verifyInfos(service, host); + verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper, afterActionDocumentPositionMapperNotEquals); + } + + it(mainScenario, () => { + const { host, session, service } = openTsFile(); + checkProject(service); + + // Main scenario action + verifyAllFnAction(action, session); + checkProject(service); + verifyInfos(service, host); + const dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; + const documentPositionMapper = dependencyMap.documentPositionMapper; + + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfos(service, host); + verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); + + // Closing open file, removes dependencies too + closeFilesForSession([openFile, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(service, host); + }); + + it("when usage file changes, document position mapper doesnt change", () => { + // Edit + verifyScenarioWithChanges((_host, session) => session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { file: openFile.path, line: openFileLastLine, offset: 1, endLine: openFileLastLine, endOffset: 1, insertString: "const x = 10;" } + })); + }); + + it("when dependency file changes, document position mapper doesnt change", () => { + // Edit dts to add new fn + verifyScenarioWithChanges(host => host.writeFile( + `${dependecyLocation}/fns.d.ts`, + host.readFile(`${dependecyLocation}/fns.d.ts`)!.replace( + "//# sourceMappingURL=FnS.d.ts.map", + `export declare function fn6(): void; +//# sourceMappingURL=FnS.d.ts.map` + ) + )); + }); + + it("when dependency file's map changes", () => { + // Edit map file to represent added new line + verifyScenarioWithChanges(host => host.writeFile( + `${dependecyLocation}/FnS.d.ts.map`, + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM"}` + ), /*afterActionDocumentPositionMapperNotEquals*/ true); + }); + } + + describe("from project that uses dependency", () => { + const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + verifyDocumentPositionMapperUpdates( + "can go to definition correctly", + /*openFile*/ mainTs, + /*expectedProjectActualFiles*/ [mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`], + /*action*/ verifyGotoDefintinionFromMainTs, + closedInfos, + /*openFileLastLine*/ 10 + ); }); - it("rename locations from depedency", () => { - const host = createHost(files, [mainConfig.path]); - const session = createSession(host); - const service = session.getProjectService(); - openFilesForSession([dependencyTs, randomFile], session); - checkNumberOfProjects(service, { configuredProjects: 2 }); - checkProjectActualFiles(service.configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); - for (let i = 0; i < 5; i++) { - const startSpan = { line: i + 1, offset: 17 }; - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...startSpan } - }).response as protocol.RenameResponseBody; - assert.deepEqual(response.locs, [{ - file: dependencyTs.path, - locs: [{ start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }] - }]); - } - checkNumberOfProjects(service, { configuredProjects: 2 }); - const openInfos = [dependencyTs.path, randomFile.path]; + describe("from defining project", () => { const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; - const otherWatchedFiles = [dependencyConfig.path, randomConfig.path]; - verifyInfos(service, host, openInfos, closedInfos, otherWatchedFiles); - - // Collect the orphan projects and infos - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - - verifyInfos(service, host, openInfos, closedInfos, otherWatchedFiles); + verifyDocumentPositionMapperUpdates( + "rename locations from dependency", + /*openFile*/ dependencyTs, + /*expectedProjectActualFiles*/ [dependencyTs.path, libFile.path, dependencyConfig.path], + /*action*/ verifyRenameFromDependencyTs, + closedInfos, + /*openFileLastLine*/ 6 + ); }); }); }); From d86aeb2f004d4ec7be60f5837dae1a6c1de33c54 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 3 Dec 2018 16:18:11 -0800 Subject: [PATCH 016/120] TODOs --- src/testRunner/unittests/tsserverProjectSystem.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index b36783d519f..1f595d95f99 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10899,6 +10899,9 @@ fn5(); /*openFileLastLine*/ 6 ); }); + + // TODO: test project changes when both projects are open + // TODO: project change when dependency is not built }); }); From 60ae299f0cf1d30fd935a9b2307bf30d8e1be1b5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 4 Dec 2018 12:14:33 -0800 Subject: [PATCH 017/120] Add test to verify when depedency and main project is open --- .../unittests/tsserverProjectSystem.ts | 154 ++++++++++++------ 1 file changed, 106 insertions(+), 48 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 1f595d95f99..4c611ea735c 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10758,33 +10758,34 @@ fn5(); }]); } - function verifyAllFnAction(action: (fn: number, session: TestSession) => void, session: TestSession) { - for (let fn = 1; fn <= 5; fn++) { - action(fn, session); - } - } - + // Open File, expectedProjectActualFiles, action, openFileLastLine + type DocumentPositionMapperVerifier = [File, ReadonlyArray, (fn: number, session: TestSession) => void, number]; function verifyDocumentPositionMapperUpdates( mainScenario: string, - openFile: File, - expectedProjectActualFiles: ReadonlyArray, - action: (fn: number, session: TestSession) => void, - closedInfos: ReadonlyArray, - openFileLastLine: number) { - const configFile = `${getDirectoryPath(openFile.path)}/tsconfig.json`; - const openInfos = [openFile.path]; - const otherWatchedFiles = [configFile]; + verifier: ReadonlyArray, + closedInfos: ReadonlyArray) { + + const openFiles = verifier.map(v => v[0]); + const expectedProjectActualFiles = verifier.map(v => v[1]); + const actions = verifier.map(v => v[2]); + const openFileLastLines = verifier.map(v => v[3]); + + const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); + const openInfos = openFiles.map(f => f.path); + const otherWatchedFiles = configFiles; function openTsFile() { const host = createHost(files, [mainConfig.path]); const session = createSession(host); const service = session.getProjectService(); - openFilesForSession([openFile, randomFile], session); + openFilesForSession([...openFiles, randomFile], session); return { host, session, service }; } function checkProject(service: server.ProjectService) { - checkNumberOfProjects(service, { configuredProjects: 2 }); - checkProjectActualFiles(service.configuredProjects.get(configFile)!, expectedProjectActualFiles); + checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); + configFiles.forEach((configFile, index) => { + checkProjectActualFiles(service.configuredProjects.get(configFile)!, expectedProjectActualFiles[index]); + }); } function verifyInfos(service: server.ProjectService, host: TestServerHost) { @@ -10801,27 +10802,74 @@ fn5(); } } - function verifyScenarioWithChanges( + function verifyAllFnAction( + session: TestSession, + service: server.ProjectService, + host: TestServerHost, + firstDocumentPositionMapperNotEquals?: true, + dependencyMap?: server.ScriptInfo, + documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] + ) { + // action + let isFirst = true; + for (const action of actions) { + for (let fn = 1; fn <= 5; fn++) { + action(fn, session); + verifyInfos(service, host); + if (isFirst) { + isFirst = false; + if (dependencyMap) { + verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); + documentPositionMapper = dependencyMap.documentPositionMapper; + } + else { + dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; + documentPositionMapper = dependencyMap.documentPositionMapper; + } + } + else { + verifyDocumentPositionMapper(service, dependencyMap!, documentPositionMapper); + } + } + } + return { dependencyMap: dependencyMap!, documentPositionMapper }; + } + + function verifyScenarioWithChangesWorker( change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals?: true + afterActionDocumentPositionMapperNotEquals: true | undefined, + timeoutBeforeAction: boolean ) { const { host, session, service } = openTsFile(); // Create DocumentPositionMapper - action(1, session); + actions.forEach(action => action(1, session)); const dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; const documentPositionMapper = dependencyMap.documentPositionMapper; // change change(host, session); - host.runQueuedTimeoutCallbacks(); - checkProject(service); - verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); + if (timeoutBeforeAction) { + host.runQueuedTimeoutCallbacks(); + checkProject(service); + verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); + } // action - verifyAllFnAction(action, session); - verifyInfos(service, host); - verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper, afterActionDocumentPositionMapperNotEquals); + verifyAllFnAction(session, service, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); + } + + function verifyScenarioWithChanges( + change: (host: TestServerHost, session: TestSession) => void, + afterActionDocumentPositionMapperNotEquals?: true + ) { + it("when timeout occurs before request", () => { + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); + }); + + it("when timeout does not occur before request", () => { + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); + }); } it(mainScenario, () => { @@ -10829,11 +10877,9 @@ fn5(); checkProject(service); // Main scenario action - verifyAllFnAction(action, session); + const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, service, host); checkProject(service); verifyInfos(service, host); - const dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; - const documentPositionMapper = dependencyMap.documentPositionMapper; // Collecting at this point retains dependency.d.ts and map closeFilesForSession([randomFile], session); @@ -10842,20 +10888,20 @@ fn5(); verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); // Closing open file, removes dependencies too - closeFilesForSession([openFile, randomFile], session); + closeFilesForSession([...openFiles, randomFile], session); openFilesForSession([randomFile], session); verifyOnlyRandomInfos(service, host); }); - it("when usage file changes, document position mapper doesnt change", () => { + describe("when usage file changes, document position mapper doesnt change", () => { // Edit - verifyScenarioWithChanges((_host, session) => session.executeCommandSeq({ + verifyScenarioWithChanges((_host, session) => openFiles.forEach((openFile, index) => session.executeCommandSeq({ command: protocol.CommandTypes.Change, - arguments: { file: openFile.path, line: openFileLastLine, offset: 1, endLine: openFileLastLine, endOffset: 1, insertString: "const x = 10;" } - })); + arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } + }))); }); - it("when dependency file changes, document position mapper doesnt change", () => { + describe("when dependency file changes, document position mapper doesnt change", () => { // Edit dts to add new fn verifyScenarioWithChanges(host => host.writeFile( `${dependecyLocation}/fns.d.ts`, @@ -10867,7 +10913,7 @@ fn5(); )); }); - it("when dependency file's map changes", () => { + describe("when dependency file's map changes", () => { // Edit map file to represent added new line verifyScenarioWithChanges(host => host.writeFile( `${dependecyLocation}/FnS.d.ts.map`, @@ -10876,32 +10922,44 @@ fn5(); }); } + const usageVerifier: DocumentPositionMapperVerifier = [ + /*openFile*/ mainTs, + /*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`], + /*action*/ verifyGotoDefintinionFromMainTs, + /*openFileLastLine*/ 10 + ]; describe("from project that uses dependency", () => { const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; verifyDocumentPositionMapperUpdates( "can go to definition correctly", - /*openFile*/ mainTs, - /*expectedProjectActualFiles*/ [mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`], - /*action*/ verifyGotoDefintinionFromMainTs, - closedInfos, - /*openFileLastLine*/ 10 + [usageVerifier], + closedInfos ); }); + const definingVerifier: DocumentPositionMapperVerifier = [ + /*openFile*/ dependencyTs, + /*expectedProjectActualFiles*/[dependencyTs.path, libFile.path, dependencyConfig.path], + /*action*/ verifyRenameFromDependencyTs, + /*openFileLastLine*/ 6 + ]; describe("from defining project", () => { const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; verifyDocumentPositionMapperUpdates( "rename locations from dependency", - /*openFile*/ dependencyTs, - /*expectedProjectActualFiles*/ [dependencyTs.path, libFile.path, dependencyConfig.path], - /*action*/ verifyRenameFromDependencyTs, - closedInfos, - /*openFileLastLine*/ 6 + [definingVerifier], + closedInfos ); }); - // TODO: test project changes when both projects are open - // TODO: project change when dependency is not built + describe("when opening depedency and usage project", () => { + const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + verifyDocumentPositionMapperUpdates( + "goto Definition in usage and rename locations from defining project", + [definingVerifier], + closedInfos + ); + }); }); }); From 34a12839ba62511ddd33f29e9bd2078708deddde Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 4 Dec 2018 14:42:48 -0800 Subject: [PATCH 018/120] Test that when map file is created the changes are reflected. --- .../unittests/tsserverProjectSystem.ts | 206 ++++++++++++------ 1 file changed, 135 insertions(+), 71 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 4c611ea735c..8f7328e6fc4 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10718,48 +10718,72 @@ fn5(); path: `${projectLocation}/random/tsconfig.json`, content: "{}" }; + const dtsMapLocation = `${dependecyLocation}/FnS.d.ts.map`; + const dtsMapPath = dtsMapLocation.toLowerCase() as Path; const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - function verifyScriptInfos(service: server.ProjectService, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - checkScriptInfos(service, openInfos.concat(closedInfos)); + function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos)); checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); } - function verifyInfosWithRandom(service: server.ProjectService, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - verifyScriptInfos(service, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); + function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); } - function verifyOnlyRandomInfos(service: server.ProjectService, host: TestServerHost) { - verifyScriptInfos(service, host, [randomFile.path], [libFile.path], [randomConfig.path]); + function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { + verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]); } - function verifyGotoDefintinionFromMainTs(fn: number, session: TestSession) { + // Returns request and expected Response + type SessionAction = [Partial, Response]; + function gotoDefintinionFromMainTs(fn: number): SessionAction { const startSpan = { line: fn + 4, offset: 1 }; - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...startSpan } - }).response as protocol.DefinitionInfoAndBoundSpan; - assert.deepEqual(response, { - definitions: [{ file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }], - textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } - }); + return [ + { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...startSpan } + }, + { + definitions: [{ file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }], + textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } + } + ]; } - function verifyRenameFromDependencyTs(fn: number, session: TestSession) { + function renameFromDependencyTs(fn: number): SessionAction { const startSpan = { line: fn, offset: 17 }; - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...startSpan } - }).response as protocol.RenameResponseBody; - assert.deepEqual(response.locs, [{ - file: dependencyTs.path, - locs: [{ start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }] - }]); + const triggerSpan = { + start: startSpan, + end: { line: startSpan.line, offset: startSpan.offset + 3 } + }; + return [ + { + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...startSpan } + }, + { + info: { + canRename: true, + fileToRename: undefined, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + triggerSpan + }, + locs: [ + { file: dependencyTs.path, locs: [triggerSpan] } + ] + } + ]; } - // Open File, expectedProjectActualFiles, action, openFileLastLine - type DocumentPositionMapperVerifier = [File, ReadonlyArray, (fn: number, session: TestSession) => void, number]; + // Returns request and expected Response + type SessionActionGetter = (fn: number) => SessionAction; + // Open File, expectedProjectActualFiles, actionGetter, openFileLastLine + type DocumentPositionMapperVerifier = [File, ReadonlyArray, SessionActionGetter, number]; function verifyDocumentPositionMapperUpdates( mainScenario: string, verifier: ReadonlyArray, @@ -10767,7 +10791,7 @@ fn5(); const openFiles = verifier.map(v => v[0]); const expectedProjectActualFiles = verifier.map(v => v[1]); - const actions = verifier.map(v => v[2]); + const actionGetters = verifier.map(v => v[2]); const openFileLastLines = verifier.map(v => v[3]); const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); @@ -10776,24 +10800,24 @@ fn5(); function openTsFile() { const host = createHost(files, [mainConfig.path]); const session = createSession(host); - const service = session.getProjectService(); openFilesForSession([...openFiles, randomFile], session); - return { host, session, service }; + return { host, session }; } - function checkProject(service: server.ProjectService) { + function checkProject(session: TestSession) { + const service = session.getProjectService(); checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); configFiles.forEach((configFile, index) => { checkProjectActualFiles(service.configuredProjects.get(configFile)!, expectedProjectActualFiles[index]); }); } - function verifyInfos(service: server.ProjectService, host: TestServerHost) { - verifyInfosWithRandom(service, host, openInfos, closedInfos, otherWatchedFiles); + function verifyInfos(session: TestSession, host: TestServerHost, minusDtsMap?: true) { + verifyInfosWithRandom(session, host, openInfos, minusDtsMap ? closedInfos.filter(f => f.toLowerCase() !== dtsMapPath) : closedInfos, otherWatchedFiles); } - function verifyDocumentPositionMapper(service: server.ProjectService, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { - assert.strictEqual(service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`), dependencyMap); + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); if (notEqual) { assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); } @@ -10802,9 +10826,26 @@ fn5(); } } + function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { + const [req, expectedResponse] = actionGetter(fn); + const { response } = session.executeCommandSeq(req); + return { response, expectedResponse }; + } + + function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo) => void) { + // action + for (const actionGetter of actionGetters) { + for (let fn = 1; fn <= 5; fn++) { + const result = action(actionGetter, fn, session); + const dtsInfo = session.getProjectService().filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts`); + assert.isDefined(dtsInfo); + verifyAction(result, dtsInfo!); + } + } + } + function verifyAllFnAction( session: TestSession, - service: server.ProjectService, host: TestServerHost, firstDocumentPositionMapperNotEquals?: true, dependencyMap?: server.ScriptInfo, @@ -10812,51 +10853,63 @@ fn5(); ) { // action let isFirst = true; - for (const action of actions) { - for (let fn = 1; fn <= 5; fn++) { - action(fn, session); - verifyInfos(service, host); - if (isFirst) { - isFirst = false; - if (dependencyMap) { - verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); - documentPositionMapper = dependencyMap.documentPositionMapper; - } - else { - dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; - documentPositionMapper = dependencyMap.documentPositionMapper; - } + verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo) => { + assert.deepEqual(response, expectedResponse); + verifyInfos(session, host); + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); + if (isFirst) { + isFirst = false; + if (dependencyMap) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); + documentPositionMapper = dependencyMap.documentPositionMapper; } else { - verifyDocumentPositionMapper(service, dependencyMap!, documentPositionMapper); + dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; + documentPositionMapper = dependencyMap.documentPositionMapper; } } - } + else { + verifyDocumentPositionMapper(session, dependencyMap!, documentPositionMapper); + } + }); return { dependencyMap: dependencyMap!, documentPositionMapper }; } + function verifyAllFnActionWithNoMap( + session: TestSession, + host: TestServerHost + ) { + // action + verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo) => { + assert.deepEqual(response, expectedResponse); + verifyInfos(session, host); + assert.isFalse(dtsInfo.sourceMapFilePath); + assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); + }); + } + function verifyScenarioWithChangesWorker( change: (host: TestServerHost, session: TestSession) => void, afterActionDocumentPositionMapperNotEquals: true | undefined, timeoutBeforeAction: boolean ) { - const { host, session, service } = openTsFile(); + const { host, session } = openTsFile(); // Create DocumentPositionMapper - actions.forEach(action => action(1, session)); - const dependencyMap = service.filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts.map`)!; + actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; const documentPositionMapper = dependencyMap.documentPositionMapper; // change change(host, session); if (timeoutBeforeAction) { host.runQueuedTimeoutCallbacks(); - checkProject(service); - verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); + checkProject(session); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); } // action - verifyAllFnAction(session, service, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); + verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); } function verifyScenarioWithChanges( @@ -10873,24 +10926,24 @@ fn5(); } it(mainScenario, () => { - const { host, session, service } = openTsFile(); - checkProject(service); + const { host, session } = openTsFile(); + checkProject(session); // Main scenario action - const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, service, host); - checkProject(service); - verifyInfos(service, host); + const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host); + checkProject(session); + verifyInfos(session, host); // Collecting at this point retains dependency.d.ts and map closeFilesForSession([randomFile], session); openFilesForSession([randomFile], session); - verifyInfos(service, host); - verifyDocumentPositionMapper(service, dependencyMap, documentPositionMapper); + verifyInfos(session, host); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); // Closing open file, removes dependencies too closeFilesForSession([...openFiles, randomFile], session); openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(service, host); + verifyOnlyRandomInfos(session, host); }); describe("when usage file changes, document position mapper doesnt change", () => { @@ -10916,20 +10969,31 @@ fn5(); describe("when dependency file's map changes", () => { // Edit map file to represent added new line verifyScenarioWithChanges(host => host.writeFile( - `${dependecyLocation}/FnS.d.ts.map`, + dtsMapLocation, `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM"}` ), /*afterActionDocumentPositionMapperNotEquals*/ true); }); + + it("when map file is not present", () => { + const host = createHost(files, [mainConfig.path]); + host.deleteFile(dtsMapLocation); + + const session = createSession(host); + openFilesForSession([...openFiles, randomFile], session); + checkProject(session); + + verifyAllFnActionWithNoMap(session, host); + }); } const usageVerifier: DocumentPositionMapperVerifier = [ /*openFile*/ mainTs, /*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`], - /*action*/ verifyGotoDefintinionFromMainTs, + /*actionGetter*/ gotoDefintinionFromMainTs, /*openFileLastLine*/ 10 ]; describe("from project that uses dependency", () => { - const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "can go to definition correctly", [usageVerifier], @@ -10940,11 +11004,11 @@ fn5(); const definingVerifier: DocumentPositionMapperVerifier = [ /*openFile*/ dependencyTs, /*expectedProjectActualFiles*/[dependencyTs.path, libFile.path, dependencyConfig.path], - /*action*/ verifyRenameFromDependencyTs, + /*actionGetter*/ renameFromDependencyTs, /*openFileLastLine*/ 6 ]; describe("from defining project", () => { - const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "rename locations from dependency", [definingVerifier], @@ -10953,7 +11017,7 @@ fn5(); }); describe("when opening depedency and usage project", () => { - const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, `${dependecyLocation}/FnS.d.ts.map`]; + const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "goto Definition in usage and rename locations from defining project", [definingVerifier], From 8f3d2d9f76ed1ebc7cea9ab0e4dd6905ef53c703 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 4 Dec 2018 15:42:10 -0800 Subject: [PATCH 019/120] Goto defintion will not go to source if map is present --- .../unittests/tsserverProjectSystem.ts | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 8f7328e6fc4..077f0128adc 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10718,7 +10718,9 @@ fn5(); path: `${projectLocation}/random/tsconfig.json`, content: "{}" }; - const dtsMapLocation = `${dependecyLocation}/FnS.d.ts.map`; + const dtsLocation = `${dependecyLocation}/FnS.d.ts`; + const dtsPath = dtsLocation.toLowerCase() as Path; + const dtsMapLocation = `${dtsLocation}.map`; const dtsMapPath = dtsMapLocation.toLowerCase() as Path; const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; @@ -10736,18 +10738,25 @@ fn5(); verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]); } - // Returns request and expected Response - type SessionAction = [Partial, Response]; + // Returns request and expected Response, expected response when no map file + type SessionAction = [Partial, Response, Response?]; function gotoDefintinionFromMainTs(fn: number): SessionAction { const startSpan = { line: fn + 4, offset: 1 }; + const textSpan: protocol.TextSpan = { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }; + const definitionSpan: protocol.FileSpan = { file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }; + const declareSpaceLength = "declare ".length; return [ { command: protocol.CommandTypes.DefinitionAndBoundSpan, arguments: { file: mainTs.path, ...startSpan } }, { - definitions: [{ file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }], - textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } + definitions: [definitionSpan], + textSpan + }, + { + definitions: [{ file: dtsPath, start: { line: fn, offset: definitionSpan.start.offset + declareSpaceLength }, end: { line: fn, offset: definitionSpan.end.offset + declareSpaceLength } }], + textSpan } ]; } @@ -10812,8 +10821,8 @@ fn5(); }); } - function verifyInfos(session: TestSession, host: TestServerHost, minusDtsMap?: true) { - verifyInfosWithRandom(session, host, openInfos, minusDtsMap ? closedInfos.filter(f => f.toLowerCase() !== dtsMapPath) : closedInfos, otherWatchedFiles); + function verifyInfos(session: TestSession, host: TestServerHost, minusDtsMapAndSource?: true) { + verifyInfosWithRandom(session, host, openInfos, minusDtsMapAndSource ? closedInfos.filter(f => f !== dependencyTs.path && f.toLowerCase() !== dtsMapPath) : closedInfos, otherWatchedFiles); } function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { @@ -10827,9 +10836,9 @@ fn5(); } function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { - const [req, expectedResponse] = actionGetter(fn); + const [req, expectedResponse, expectedNoMapResponse] = actionGetter(fn); const { response } = session.executeCommandSeq(req); - return { response, expectedResponse }; + return { response, expectedResponse, expectedNoMapResponse }; } function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo) => void) { @@ -10837,7 +10846,7 @@ fn5(); for (const actionGetter of actionGetters) { for (let fn = 1; fn <= 5; fn++) { const result = action(actionGetter, fn, session); - const dtsInfo = session.getProjectService().filenameToScriptInfo.get(`${dependecyLocation}/fns.d.ts`); + const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); assert.isDefined(dtsInfo); verifyAction(result, dtsInfo!); } @@ -10880,9 +10889,9 @@ fn5(); host: TestServerHost ) { // action - verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo) => { - assert.deepEqual(response, expectedResponse); - verifyInfos(session, host); + verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoMapResponse }, dtsInfo) => { + assert.deepEqual(response, expectedNoMapResponse && verifier.length ? expectedNoMapResponse : expectedResponse); + verifyInfos(session, host, /*minusDtsMapAndSource*/ true); assert.isFalse(dtsInfo.sourceMapFilePath); assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); }); @@ -10957,8 +10966,8 @@ fn5(); describe("when dependency file changes, document position mapper doesnt change", () => { // Edit dts to add new fn verifyScenarioWithChanges(host => host.writeFile( - `${dependecyLocation}/fns.d.ts`, - host.readFile(`${dependecyLocation}/fns.d.ts`)!.replace( + dtsLocation, + host.readFile(dtsLocation)!.replace( "//# sourceMappingURL=FnS.d.ts.map", `export declare function fn6(): void; //# sourceMappingURL=FnS.d.ts.map` @@ -10988,12 +10997,12 @@ fn5(); const usageVerifier: DocumentPositionMapperVerifier = [ /*openFile*/ mainTs, - /*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`], + /*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, dtsPath], /*actionGetter*/ gotoDefintinionFromMainTs, /*openFileLastLine*/ 10 ]; describe("from project that uses dependency", () => { - const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, `${dependecyLocation}/fns.d.ts`, dtsMapLocation]; + const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "can go to definition correctly", [usageVerifier], @@ -11008,7 +11017,7 @@ fn5(); /*openFileLastLine*/ 6 ]; describe("from defining project", () => { - const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, dtsMapLocation]; + const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "rename locations from dependency", [definingVerifier], @@ -11017,7 +11026,7 @@ fn5(); }); describe("when opening depedency and usage project", () => { - const closedInfos = [libFile.path, `${dependecyLocation}/FnS.d.ts`, dtsMapLocation]; + const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "goto Definition in usage and rename locations from defining project", [definingVerifier], From 3dc0d5a77c950e4757c17fd4a17959bb5f36b495 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 5 Dec 2018 12:18:14 -0800 Subject: [PATCH 020/120] Watch missing map file and update the source mapping accordingly --- src/server/editorServices.ts | 150 +++++++++++++----- src/server/scriptInfo.ts | 18 ++- src/services/sourcemaps.ts | 5 +- .../unittests/tsserverProjectSystem.ts | 117 +++++++++++--- 4 files changed, 224 insertions(+), 66 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a3ce0989b0c..b970a8fc716 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -342,6 +342,7 @@ namespace ts.server { FailedLookupLocation = "Directory of Failed lookup locations in module resolution", TypeRoots = "Type root directory", NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", + MissingSourceMapFile = "Missing source map file" } const enum ConfigFileWatcherStatus { @@ -953,22 +954,25 @@ namespace ts.server { private handleSourceMapProjects(info: ScriptInfo) { // Change in d.ts, update source projects as well if (info.sourceMapFilePath) { - const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath); - if (sourceMapFileInfo && sourceMapFileInfo.sourceInfos) { - this.delayUpdateSourceInfoProjects(sourceMapFileInfo.sourceInfos); + if (isString(info.sourceMapFilePath)) { + const sourceMapFileInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + this.delayUpdateSourceInfoProjects(sourceMapFileInfo && sourceMapFileInfo.sourceInfos); + } + else { + this.delayUpdateSourceInfoProjects(info.sourceMapFilePath.sourceInfos); } } // Change in mapInfo, update declarationProjects and source projects - if (info.sourceInfos) { - this.delayUpdateSourceInfoProjects(info.sourceInfos); - } + this.delayUpdateSourceInfoProjects(info.sourceInfos); if (info.declarationInfoPath) { this.delayUpdateProjectsOfScriptInfoPath(info.declarationInfoPath); } } - private delayUpdateSourceInfoProjects(sourceInfos: Map) { - sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path)); + private delayUpdateSourceInfoProjects(sourceInfos: Map | undefined) { + if (sourceInfos) { + sourceInfos.forEach((_value, path) => this.delayUpdateProjectsOfScriptInfoPath(path as Path)); + } } private delayUpdateProjectsOfScriptInfoPath(path: Path) { @@ -992,6 +996,14 @@ namespace ts.server { // update projects to make sure that set of referenced files is correct this.delayUpdateProjectGraphs(containingProjects); this.handleSourceMapProjects(info); + info.closeSourceMapFileWatcher(); + // need to recalculate source map from declaration file + if (info.declarationInfoPath) { + const declarationInfo = this.getScriptInfoForPath(info.declarationInfoPath); + if (declarationInfo) { + declarationInfo.sourceMapFilePath = undefined; + } + } } } @@ -2228,32 +2240,43 @@ namespace ts.server { /*@internal*/ getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined { - const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, project.directoryStructureHost); + // Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host + const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host); if (!declarationInfo) return undefined; // Try to get from cache declarationInfo.getSnapshot(); // Ensure synchronized - if (declarationInfo.sourceMapFilePath !== undefined) { - // Doesnt have sourceMap - if (!declarationInfo.sourceMapFilePath) return undefined; - + if (isString(declarationInfo.sourceMapFilePath)) { // Ensure mapper is synchronized - const mapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); - if (mapFileInfo) { - mapFileInfo.getSnapshot(); - if (mapFileInfo.documentPositionMapper !== undefined) { - return mapFileInfo.documentPositionMapper ? mapFileInfo.documentPositionMapper : undefined; + const sourceMapFileInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); + if (sourceMapFileInfo) { + sourceMapFileInfo.getSnapshot(); + if (sourceMapFileInfo.documentPositionMapper !== undefined) { + sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos); + return sourceMapFileInfo.documentPositionMapper ? sourceMapFileInfo.documentPositionMapper : undefined; } } + declarationInfo.sourceMapFilePath = undefined; + } + else if (declarationInfo.sourceMapFilePath) { + declarationInfo.sourceMapFilePath.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, declarationInfo.sourceMapFilePath.sourceInfos); + return undefined; + } + else if (declarationInfo.sourceMapFilePath !== undefined) { + // Doesnt have sourceMap + return undefined; } // Create the mapper - declarationInfo.sourceMapFilePath = undefined; let sourceMapFileInfo: ScriptInfo | undefined; + let mapFileNameFromDeclarationInfo: string | undefined; - let readMapFile: ReadMapFile | undefined = mapFileName => { - const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, project.directoryStructureHost); - if (!mapInfo) return undefined; + let readMapFile: ReadMapFile | undefined = (mapFileName, mapFileNameFromDts) => { + const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, this.host); + if (!mapInfo) { + mapFileNameFromDeclarationInfo = mapFileNameFromDts; + return undefined; + } sourceMapFileInfo = mapInfo; const snap = mapInfo.getSnapshot(); if (mapInfo.documentPositionMapper !== undefined) return mapInfo.documentPositionMapper; @@ -2271,11 +2294,19 @@ namespace ts.server { declarationInfo.sourceMapFilePath = sourceMapFileInfo.path; sourceMapFileInfo.declarationInfoPath = declarationInfo.path; sourceMapFileInfo.documentPositionMapper = documentPositionMapper || false; - if (sourceFileName && documentPositionMapper) { - // Attach as source - const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; - (sourceMapFileInfo.sourceInfos || (sourceMapFileInfo.sourceInfos = createMap())).set(sourceInfo.path, true); - } + sourceMapFileInfo.sourceInfos = this.addSourceInfoToSourceMap(sourceFileName, project, sourceMapFileInfo.sourceInfos); + } + else if (mapFileNameFromDeclarationInfo) { + declarationInfo.sourceMapFilePath = { + declarationInfoPath: declarationInfo.path, + watcher: this.addMissingSourceMapFile( + project.currentDirectory === this.currentDirectory ? + mapFileNameFromDeclarationInfo : + getNormalizedAbsolutePath(mapFileNameFromDeclarationInfo, project.currentDirectory), + declarationInfo.path + ), + sourceInfos: this.addSourceInfoToSourceMap(sourceFileName, project) + }; } else { declarationInfo.sourceMapFilePath = false; @@ -2283,6 +2314,34 @@ namespace ts.server { return documentPositionMapper; } + private addSourceInfoToSourceMap(sourceFileName: string | undefined, project: Project, sourceInfos?: Map) { + if (sourceFileName) { + // Attach as source + const sourceInfo = this.getOrCreateScriptInfoNotOpenedByClient(sourceFileName, project.currentDirectory, project.directoryStructureHost)!; + (sourceInfos || (sourceInfos = createMap())).set(sourceInfo.path, true); + } + return sourceInfos; + } + + private addMissingSourceMapFile(mapFileName: string, declarationInfoPath: Path) { + const fileWatcher = this.watchFactory.watchFile( + this.host, + mapFileName, + () => { + const declarationInfo = this.getScriptInfoForPath(declarationInfoPath); + if (declarationInfo && declarationInfo.sourceMapFilePath && !isString(declarationInfo.sourceMapFilePath)) { + // Update declaration and source projects + this.delayUpdateProjectGraphs(declarationInfo.containingProjects); + this.delayUpdateSourceInfoProjects(declarationInfo.sourceMapFilePath.sourceInfos); + declarationInfo.closeSourceMapFileWatcher(); + } + }, + PollingInterval.High, + WatchType.MissingSourceMapFile, + ); + return fileWatcher; + } + /*@internal*/ getSourceFileLike(fileName: string, projectNameOrProject: string | Project, declarationInfo?: ScriptInfo) { const project = (projectNameOrProject as Project).projectName ? projectNameOrProject as Project : this.findProject(projectNameOrProject as string); @@ -2297,7 +2356,7 @@ namespace ts.server { if (!info) return undefined; // Attach as source - if (declarationInfo && declarationInfo.sourceMapFilePath && info !== declarationInfo) { + if (declarationInfo && isString(declarationInfo.sourceMapFilePath) && info !== declarationInfo) { const sourceMapInfo = this.getScriptInfoForPath(declarationInfo.sourceMapFilePath); if (sourceMapInfo) { (sourceMapInfo.sourceInfos || (sourceMapInfo.sourceInfos = createMap())).set(info.path, true); @@ -2669,11 +2728,18 @@ namespace ts.server { if (!info.isScriptOpen() && info.isOrphan()) { // Otherwise if there is any source info that is alive, this alive too if (!info.sourceMapFilePath) return; - const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); - if (!sourceMapInfo || !sourceMapInfo.sourceInfos) return; - if (!forEachKey(sourceMapInfo.sourceInfos, path => { - const info = this.getScriptInfoForPath(path as Path)!; - return info.isScriptOpen() || !info.isOrphan(); + let sourceInfos: Map | undefined; + if (isString(info.sourceMapFilePath)) { + const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos; + } + else { + sourceInfos = info.sourceMapFilePath.sourceInfos; + } + if (!sourceInfos) return; + if (!forEachKey(sourceInfos, path => { + const info = this.getScriptInfoForPath(path as Path); + return !!info && (info.isScriptOpen() || !info.isOrphan()); })) { return; } @@ -2682,11 +2748,18 @@ namespace ts.server { // Retain this script info toRemoveScriptInfos.delete(info.path); if (info.sourceMapFilePath) { - // And map file info and source infos - toRemoveScriptInfos.delete(info.sourceMapFilePath); - const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); - if (sourceMapInfo && sourceMapInfo.sourceInfos) { - sourceMapInfo.sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); + let sourceInfos: Map | undefined; + if (isString(info.sourceMapFilePath)) { + // And map file info and source infos + toRemoveScriptInfos.delete(info.sourceMapFilePath); + const sourceMapInfo = this.getScriptInfoForPath(info.sourceMapFilePath); + sourceInfos = sourceMapInfo && sourceMapInfo.sourceInfos; + } + else { + sourceInfos = info.sourceMapFilePath.sourceInfos; + } + if (sourceInfos) { + sourceInfos.forEach((_value, path) => toRemoveScriptInfos.delete(path)); } } }); @@ -2695,6 +2768,7 @@ namespace ts.server { // if there are not projects that include this script info - delete it this.stopWatchingScriptInfo(info); this.deleteScriptInfo(info); + info.closeSourceMapFileWatcher(); }); } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 1cd00cdca45..21c011d7261 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -66,6 +66,7 @@ namespace ts.server { private resetSourceMapInfo() { this.info.sourceFileLike = undefined; + this.info.closeSourceMapFileWatcher(); this.info.sourceMapFilePath = undefined; this.info.declarationInfoPath = undefined; this.info.sourceInfos = undefined; @@ -280,6 +281,13 @@ namespace ts.server { sourceFile: SourceFile; } + /*@internal*/ + export interface SourceMapFileWatcher { + declarationInfoPath: Path; + watcher: FileWatcher; + sourceInfos?: Map; + } + export class ScriptInfo { /** * All projects that include this file @@ -309,7 +317,7 @@ namespace ts.server { sourceFileLike?: SourceFileLike; /*@internal*/ - sourceMapFilePath?: Path | false; + sourceMapFilePath?: Path | SourceMapFileWatcher | false; // Present on sourceMapFile info /*@internal*/ @@ -606,5 +614,13 @@ namespace ts.server { getLineInfo(): LineInfo { return this.textStorage.getLineInfo(); } + + /*@internal*/ + closeSourceMapFileWatcher() { + if (this.sourceMapFilePath && !isString(this.sourceMapFilePath)) { + closeFileWatcherOf(this.sourceMapFilePath); + this.sourceMapFilePath = undefined; + } + } } } diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 42b00b8a8d7..d07c21a9f43 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -131,7 +131,7 @@ namespace ts { * string | undefined to contents of map file to create DocumentPositionMapper from it * DocumentPositionMapper | false to give back cached DocumentPositionMapper */ - export type ReadMapFile = (mapFileName: string) => string | undefined | DocumentPositionMapper | false; + export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false; export function getDocumentPositionMapper( host: DocumentPositionMapperHost, @@ -155,9 +155,10 @@ namespace ts { possibleMapLocations.push(mapFileName); } possibleMapLocations.push(generatedFileName + ".map"); + const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName)); for (const location of possibleMapLocations) { const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); - const mapFileContents = readMapFile(mapFileName); + const mapFileContents = readMapFile(mapFileName, originalMapFileName); if (isString(mapFileContents)) { return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 077f0128adc..b5909d3b3d5 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10806,8 +10806,11 @@ fn5(); const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); const openInfos = openFiles.map(f => f.path); const otherWatchedFiles = configFiles; - function openTsFile() { + function openTsFile(onHostCreate?: (host: TestServerHost) => void) { const host = createHost(files, [mainConfig.path]); + if (onHostCreate) { + onHostCreate(host); + } const session = createSession(host); openFilesForSession([...openFiles, randomFile], session); return { host, session }; @@ -10821,8 +10824,19 @@ fn5(); }); } - function verifyInfos(session: TestSession, host: TestServerHost, minusDtsMapAndSource?: true) { - verifyInfosWithRandom(session, host, openInfos, minusDtsMapAndSource ? closedInfos.filter(f => f !== dependencyTs.path && f.toLowerCase() !== dtsMapPath) : closedInfos, otherWatchedFiles); + function verifyInfos(session: TestSession, host: TestServerHost) { + verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); + } + + function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: true) { + const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); + verifyInfosWithRandom( + session, + host, + openInfos, + closedInfos.filter(f => f !== dtsMapClosedInfo && (dependencyTsOK || f !== dependencyTs.path)), + dtsMapClosedInfo ? otherWatchedFiles.concat(dtsMapClosedInfo) : otherWatchedFiles + ); } function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { @@ -10841,14 +10855,20 @@ fn5(); return { response, expectedResponse, expectedNoMapResponse }; } - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo) => void) { + function firstAction(session: TestSession) { + actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); + } + + function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo, isFirst: boolean) => void) { // action + let isFirst = true; for (const actionGetter of actionGetters) { for (let fn = 1; fn <= 5; fn++) { const result = action(actionGetter, fn, session); const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); assert.isDefined(dtsInfo); - verifyAction(result, dtsInfo!); + verifyAction(result, dtsInfo!, isFirst); + isFirst = false; } } } @@ -10861,13 +10881,11 @@ fn5(); documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] ) { // action - let isFirst = true; - verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo) => { + verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo, isFirst) => { assert.deepEqual(response, expectedResponse); verifyInfos(session, host); assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); if (isFirst) { - isFirst = false; if (dependencyMap) { verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); documentPositionMapper = dependencyMap.documentPositionMapper; @@ -10886,15 +10904,26 @@ fn5(); function verifyAllFnActionWithNoMap( session: TestSession, - host: TestServerHost + host: TestServerHost, + dependencyTsOK?: true ) { + let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; // action - verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoMapResponse }, dtsInfo) => { + verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoMapResponse }, dtsInfo, isFirst) => { assert.deepEqual(response, expectedNoMapResponse && verifier.length ? expectedNoMapResponse : expectedResponse); - verifyInfos(session, host, /*minusDtsMapAndSource*/ true); - assert.isFalse(dtsInfo.sourceMapFilePath); + verifyInfosWhenNoMapFile(session, host, dependencyTsOK); assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); + if (isFirst) { + assert.isNotString(dtsInfo.sourceMapFilePath); + assert.isNotFalse(dtsInfo.sourceMapFilePath); + assert.isDefined(dtsInfo.sourceMapFilePath); + sourceMapFilePath = dtsInfo.sourceMapFilePath; + } + else { + assert.equal(dtsInfo.sourceMapFilePath, sourceMapFilePath); + } }); + return sourceMapFilePath; } function verifyScenarioWithChangesWorker( @@ -10905,7 +10934,7 @@ fn5(); const { host, session } = openTsFile(); // Create DocumentPositionMapper - actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); + firstAction(session); const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; const documentPositionMapper = dependencyMap.documentPositionMapper; @@ -10934,10 +10963,7 @@ fn5(); }); } - it(mainScenario, () => { - const { host, session } = openTsFile(); - checkProject(session); - + function verifyMainScenarioAndScriptInfoCollection(session: TestSession, host: TestServerHost) { // Main scenario action const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host); checkProject(session); @@ -10953,6 +10979,28 @@ fn5(); closeFilesForSession([...openFiles, randomFile], session); openFilesForSession([randomFile], session); verifyOnlyRandomInfos(session, host); + } + + function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { + // Main scenario action + verifyAllFnActionWithNoMap(session, host, dependencyTsOKInScenario); + + // Collecting at this point retains dependency.d.ts and map watcher + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfosWhenNoMapFile(session, host); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + it(mainScenario, () => { + const { host, session } = openTsFile(); + checkProject(session); + + verifyMainScenarioAndScriptInfoCollection(session, host); }); describe("when usage file changes, document position mapper doesnt change", () => { @@ -10963,7 +11011,7 @@ fn5(); }))); }); - describe("when dependency file changes, document position mapper doesnt change", () => { + describe("when dependency .d.ts changes, document position mapper doesnt change", () => { // Edit dts to add new fn verifyScenarioWithChanges(host => host.writeFile( dtsLocation, @@ -10983,15 +11031,34 @@ fn5(); ), /*afterActionDocumentPositionMapperNotEquals*/ true); }); - it("when map file is not present", () => { - const host = createHost(files, [mainConfig.path]); - host.deleteFile(dtsMapLocation); + describe("when map file is not present", () => { + it(mainScenario, () => { + const { host, session } = openTsFile(host => host.deleteFile(dtsMapLocation)); + checkProject(session); - const session = createSession(host); - openFilesForSession([...openFiles, randomFile], session); - checkProject(session); + verifyMainScenarioAndScriptInfoCollectionWithNoMap(session, host); + }); - verifyAllFnActionWithNoMap(session, host); + it("when map file is created", () => { + let dtsMapContents: string | undefined; + const { host, session } = openTsFile(host => { + dtsMapContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + firstAction(session); + + host.writeFile(dtsMapLocation, dtsMapContents!); + verifyMainScenarioAndScriptInfoCollection(session, host); + }); + + it("when map file is deleted", () => { + const { host, session } = openTsFile(); + firstAction(session); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyMainScenarioAndScriptInfoCollectionWithNoMap(session, host, /*dependencyTsOKInScenario*/ true); + }); }); } From 6cb30651944458fd7a094326d705b68a01917b29 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 5 Dec 2018 15:31:55 -0800 Subject: [PATCH 021/120] Test to verify presence of .d.ts file --- src/compiler/program.ts | 21 +++- .../unittests/tsserverProjectSystem.ts | 107 +++++++++++++++--- 2 files changed, 104 insertions(+), 24 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 1789b161e39..080b17b8cbe 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -787,7 +787,13 @@ namespace ts { // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. let redirectTargetsMap = createMultiMap(); - const filesByName = createMap(); + /** + * map with + * - SourceFile if present + * - false if sourceFile missing for source of project reference redirect + * - undefined otherwise + */ + const filesByName = createMap(); let missingFilePaths: ReadonlyArray | undefined; // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing @@ -854,7 +860,7 @@ namespace ts { } } - missingFilePaths = arrayFrom(filesByName.keys(), p => p).filter(p => !filesByName.get(p)); + missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); processingDefaultLibFiles = undefined; processingOtherFiles = undefined; @@ -1567,7 +1573,7 @@ namespace ts { } function getSourceFileByPath(path: Path): SourceFile | undefined { - return filesByName.get(path); + return filesByName.get(path) || undefined; } function getDiagnosticsHelper( @@ -2104,7 +2110,7 @@ namespace ts { /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ function getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined { - return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName))); + return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined); } function getSourceFileFromReferenceWorker( @@ -2235,7 +2241,7 @@ namespace ts { } } - return file; + return file || undefined; } let redirectedPath: Path | undefined; @@ -2327,9 +2333,12 @@ namespace ts { } function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { - filesByName.set(path, file); if (redirectedPath) { filesByName.set(redirectedPath, file); + filesByName.set(path, file || false); + } + else { + filesByName.set(path, file); } } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index b5909d3b3d5..ba7c7761886 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10692,7 +10692,11 @@ export function fn5() { } const mainTs: File = { path: `${mainLocation}/main.ts`, content: `import { - fn1, fn2, fn3, fn4, fn5 + fn1, + fn2, + fn3, + fn4, + fn5 } from '../dependency/fns' fn1(); @@ -10739,9 +10743,9 @@ fn5(); } // Returns request and expected Response, expected response when no map file - type SessionAction = [Partial, Response, Response?]; + type SessionAction = [Partial, Response, Response?, Response?]; function gotoDefintinionFromMainTs(fn: number): SessionAction { - const startSpan = { line: fn + 4, offset: 1 }; + const startSpan = { line: fn + 8, offset: 1 }; const textSpan: protocol.TextSpan = { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }; const definitionSpan: protocol.FileSpan = { file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }; const declareSpaceLength = "declare ".length; @@ -10751,12 +10755,19 @@ fn5(); arguments: { file: mainTs.path, ...startSpan } }, { + // To dependency definitions: [definitionSpan], textSpan }, { + // To the dts definitions: [{ file: dtsPath, start: { line: fn, offset: definitionSpan.start.offset + declareSpaceLength }, end: { line: fn, offset: definitionSpan.end.offset + declareSpaceLength } }], textSpan + }, + { + // To import declaration + definitions: [{ file: mainTs.path, start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } }], + textSpan } ]; } @@ -10816,11 +10827,16 @@ fn5(); return { host, session }; } - function checkProject(session: TestSession) { + function checkProject(session: TestSession, noDts?: true) { const service = session.getProjectService(); checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); configFiles.forEach((configFile, index) => { - checkProjectActualFiles(service.configuredProjects.get(configFile)!, expectedProjectActualFiles[index]); + checkProjectActualFiles( + service.configuredProjects.get(configFile)!, + noDts ? + expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) : + expectedProjectActualFiles[index] + ); }); } @@ -10839,6 +10855,21 @@ fn5(); ); } + function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost) { + const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); + const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); + verifyInfosWithRandom( + session, + host, + openInfos, + closedInfos.filter(f => f !== dtsMapClosedInfo && f !== dtsClosedInfo && f !== dependencyTs.path), + // When project actual file contains dts, it needs to be watched + dtsClosedInfo && verifier.length === 1 && expectedProjectActualFiles[0].some(f => f.toLowerCase() === dtsPath) ? + otherWatchedFiles.concat(dtsClosedInfo) : + otherWatchedFiles + ); + } + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); if (notEqual) { @@ -10850,24 +10881,29 @@ fn5(); } function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { - const [req, expectedResponse, expectedNoMapResponse] = actionGetter(fn); + const [req, expectedResponse, expectedNoMapResponse, expectedNoDtsResponse] = actionGetter(fn); const { response } = session.executeCommandSeq(req); - return { response, expectedResponse, expectedNoMapResponse }; + return { response, expectedResponse, expectedNoMapResponse, expectedNoDtsResponse }; } function firstAction(session: TestSession) { actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); } - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo, isFirst: boolean) => void) { + function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) { // action let isFirst = true; for (const actionGetter of actionGetters) { for (let fn = 1; fn <= 5; fn++) { const result = action(actionGetter, fn, session); const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); - assert.isDefined(dtsInfo); - verifyAction(result, dtsInfo!, isFirst); + if (dtsAbsent) { + assert.isUndefined(dtsInfo); + } + else { + assert.isDefined(dtsInfo); + } + verifyAction(result, dtsInfo, isFirst); isFirst = false; } } @@ -10884,7 +10920,7 @@ fn5(); verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo, isFirst) => { assert.deepEqual(response, expectedResponse); verifyInfos(session, host); - assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); + assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath); if (isFirst) { if (dependencyMap) { verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); @@ -10910,22 +10946,33 @@ fn5(); let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; // action verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoMapResponse }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedNoMapResponse && verifier.length ? expectedNoMapResponse : expectedResponse); + assert.deepEqual(response, expectedNoMapResponse || expectedResponse); verifyInfosWhenNoMapFile(session, host, dependencyTsOK); assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); if (isFirst) { - assert.isNotString(dtsInfo.sourceMapFilePath); - assert.isNotFalse(dtsInfo.sourceMapFilePath); - assert.isDefined(dtsInfo.sourceMapFilePath); - sourceMapFilePath = dtsInfo.sourceMapFilePath; + assert.isNotString(dtsInfo!.sourceMapFilePath); + assert.isNotFalse(dtsInfo!.sourceMapFilePath); + assert.isDefined(dtsInfo!.sourceMapFilePath); + sourceMapFilePath = dtsInfo!.sourceMapFilePath; } else { - assert.equal(dtsInfo.sourceMapFilePath, sourceMapFilePath); + assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); } }); return sourceMapFilePath; } + function verifyAllFnActionWithNoDts( + session: TestSession, + host: TestServerHost + ) { + // action + verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoDtsResponse }) => { + assert.deepEqual(response, expectedNoDtsResponse || expectedResponse); + verifyInfosWhenNoDtsFile(session, host); + }, /*dtsAbsent*/ true); + } + function verifyScenarioWithChangesWorker( change: (host: TestServerHost, session: TestSession) => void, afterActionDocumentPositionMapperNotEquals: true | undefined, @@ -10996,6 +11043,21 @@ fn5(); verifyOnlyRandomInfos(session, host); } + function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost) { + // Main scenario action + verifyAllFnActionWithNoDts(session, host); + + // Collecting at this point retains dependency.d.ts and map watcher + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfosWhenNoDtsFile(session, host); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + it(mainScenario, () => { const { host, session } = openTsFile(); checkProject(session); @@ -11060,13 +11122,22 @@ fn5(); verifyMainScenarioAndScriptInfoCollectionWithNoMap(session, host, /*dependencyTsOKInScenario*/ true); }); }); + + describe("when .d.ts file is not present", () => { + it(mainScenario, () => { + const { host, session } = openTsFile(host => host.deleteFile(dtsLocation)); + checkProject(session, /*noDts*/ true); + + verifyMainScenarioAndScriptInfoCollectionWithNoDts(session, host); + }); + }); } const usageVerifier: DocumentPositionMapperVerifier = [ /*openFile*/ mainTs, /*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, dtsPath], /*actionGetter*/ gotoDefintinionFromMainTs, - /*openFileLastLine*/ 10 + /*openFileLastLine*/ 14 ]; describe("from project that uses dependency", () => { const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation]; From fa7f5cb5a0ab4068235dfa186bb1ee81ab772137 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 6 Dec 2018 12:18:10 -0800 Subject: [PATCH 022/120] Actually verify when dependency and main project is open --- src/compiler/sourcemap.ts | 3 +- src/server/editorServices.ts | 2 +- src/server/project.ts | 5 +- .../unittests/tsserverProjectSystem.ts | 305 +++++++++++------- 4 files changed, 192 insertions(+), 123 deletions(-) diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 00a78b0a93c..7c47449da10 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -583,7 +583,8 @@ namespace ts { } function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { - return compareValues(left.sourceIndex, right.sourceIndex); + Debug.assert(left.sourceIndex === right.sourceIndex); + return compareValues(left.sourcePosition, right.sourcePosition); } function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b970a8fc716..0bdff39e49a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2604,7 +2604,7 @@ namespace ts.server { /** @internal */ fileExists(fileName: NormalizedPath): boolean { - return this.filenameToScriptInfo.has(fileName) || this.host.fileExists(fileName); + return !!this.getScriptInfoForNormalizedPath(fileName) || this.host.fileExists(fileName); } private findExternalProjectContainingOpenScriptInfo(info: ScriptInfo): ExternalProject | undefined { diff --git a/src/server/project.ts b/src/server/project.ts index a28e9c35be5..e3069e1d183 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -759,7 +759,10 @@ namespace ts.server { } containsScriptInfo(info: ScriptInfo): boolean { - return this.isRoot(info) || (!!this.program && this.program.getSourceFileByPath(info.path) !== undefined); + if (this.isRoot(info)) return true; + if (!this.program) return false; + const file = this.program.getSourceFileByPath(info.path); + return !!file && file.resolvedPath === info.path; } containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean { diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index ba7c7761886..e6fc1fa3726 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10743,47 +10743,60 @@ fn5(); } // Returns request and expected Response, expected response when no map file - type SessionAction = [Partial, Response, Response?, Response?]; + interface SessionAction { + reqName: string; + request: Partial; + expectedResponse: Response; + expectedResponseNoMap?: Response; + expectedResponseNoDts?: Response; + } function gotoDefintinionFromMainTs(fn: number): SessionAction { - const startSpan = { line: fn + 8, offset: 1 }; - const textSpan: protocol.TextSpan = { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } }; - const definitionSpan: protocol.FileSpan = { file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }; + const textSpan = usageSpan(fn); + const definition: protocol.FileSpan = { file: dependencyTs.path, ...definitionSpan(fn) }; const declareSpaceLength = "declare ".length; - return [ - { + return { + reqName: "goToDef", + request: { command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...startSpan } + arguments: { file: mainTs.path, ...textSpan.start } }, - { + expectedResponse: { // To dependency - definitions: [definitionSpan], + definitions: [definition], textSpan }, - { + expectedResponseNoMap: { // To the dts - definitions: [{ file: dtsPath, start: { line: fn, offset: definitionSpan.start.offset + declareSpaceLength }, end: { line: fn, offset: definitionSpan.end.offset + declareSpaceLength } }], + definitions: [{ file: dtsPath, start: { line: fn, offset: definition.start.offset + declareSpaceLength }, end: { line: fn, offset: definition.end.offset + declareSpaceLength } }], textSpan }, - { + expectedResponseNoDts: { // To import declaration - definitions: [{ file: mainTs.path, start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } }], + definitions: [{ file: mainTs.path, ...importSpan(fn) }], textSpan } - ]; + }; + } + + function definitionSpan(fn: number): protocol.TextSpan { + return { start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }; + } + function importSpan(fn: number): protocol.TextSpan { + return { start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } }; + } + function usageSpan(fn: number): protocol.TextSpan { + return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; } function renameFromDependencyTs(fn: number): SessionAction { - const startSpan = { line: fn, offset: 17 }; - const triggerSpan = { - start: startSpan, - end: { line: startSpan.line, offset: startSpan.offset + 3 } - }; - return [ - { + const triggerSpan = definitionSpan(fn); + return { + reqName: "rename", + request: { command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...startSpan } + arguments: { file: dependencyTs.path, ...triggerSpan.start } }, - { + expectedResponse: { info: { canRename: true, fileToRename: undefined, @@ -10797,26 +10810,57 @@ fn5(); { file: dependencyTs.path, locs: [triggerSpan] } ] } - ]; + }; + } + + function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction { + const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, + locs: [ + locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + }, + // Only dependency result + expectedResponseNoMap: expectedResponse, + expectedResponseNoDts: expectedResponse + }; } // Returns request and expected Response type SessionActionGetter = (fn: number) => SessionAction; // Open File, expectedProjectActualFiles, actionGetter, openFileLastLine - type DocumentPositionMapperVerifier = [File, ReadonlyArray, SessionActionGetter, number]; + interface DocumentPositionMapperVerifier { + openFile: File; + expectedProjectActualFiles: ReadonlyArray; + actionGetter: SessionActionGetter; + openFileLastLine: number; + } function verifyDocumentPositionMapperUpdates( mainScenario: string, verifier: ReadonlyArray, closedInfos: ReadonlyArray) { - const openFiles = verifier.map(v => v[0]); - const expectedProjectActualFiles = verifier.map(v => v[1]); - const actionGetters = verifier.map(v => v[2]); - const openFileLastLines = verifier.map(v => v[3]); + const openFiles = verifier.map(v => v.openFile); + const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles); + const actionGetters = verifier.map(v => v.actionGetter); + const openFileLastLines = verifier.map(v => v.openFileLastLine); const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); const openInfos = openFiles.map(f => f.path); - const otherWatchedFiles = configFiles; + // When usage and dependency are used, dependency config is part of closedInfo so ignore + const otherWatchedFiles = verifier.length > 1 ? [configFiles[0]] : configFiles; function openTsFile(onHostCreate?: (host: TestServerHost) => void) { const host = createHost(files, [mainConfig.path]); if (onHostCreate) { @@ -10855,16 +10899,16 @@ fn5(); ); } - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost) { + function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); verifyInfosWithRandom( session, host, openInfos, - closedInfos.filter(f => f !== dtsMapClosedInfo && f !== dtsClosedInfo && f !== dependencyTs.path), + closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)), // When project actual file contains dts, it needs to be watched - dtsClosedInfo && verifier.length === 1 && expectedProjectActualFiles[0].some(f => f.toLowerCase() === dtsPath) ? + dtsClosedInfo && expectedProjectActualFiles.some(expectedProjectActualFiles => expectedProjectActualFiles.some(f => f.toLowerCase() === dtsPath)) ? otherWatchedFiles.concat(dtsClosedInfo) : otherWatchedFiles ); @@ -10881,9 +10925,9 @@ fn5(); } function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { - const [req, expectedResponse, expectedNoMapResponse, expectedNoDtsResponse] = actionGetter(fn); - const { response } = session.executeCommandSeq(req); - return { response, expectedResponse, expectedNoMapResponse, expectedNoDtsResponse }; + const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = actionGetter(fn); + const { response } = session.executeCommandSeq(request); + return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts }; } function firstAction(session: TestSession) { @@ -10917,8 +10961,8 @@ fn5(); documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] ) { // action - verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponse); + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }, dtsInfo, isFirst) => { + assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); verifyInfos(session, host); assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath); if (isFirst) { @@ -10945,8 +10989,8 @@ fn5(); ) { let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; // action - verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoMapResponse }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedNoMapResponse || expectedResponse); + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { + assert.deepEqual(response, expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); verifyInfosWhenNoMapFile(session, host, dependencyTsOK); assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); if (isFirst) { @@ -10964,12 +11008,13 @@ fn5(); function verifyAllFnActionWithNoDts( session: TestSession, - host: TestServerHost + host: TestServerHost, + dependencyTsAndMapOk?: true ) { // action - verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoDtsResponse }) => { - assert.deepEqual(response, expectedNoDtsResponse || expectedResponse); - verifyInfosWhenNoDtsFile(session, host); + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts }) => { + assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); + verifyInfosWhenNoDtsFile(session, host, dependencyTsAndMapOk); }, /*dtsAbsent*/ true); } @@ -10998,15 +11043,18 @@ fn5(); } function verifyScenarioWithChanges( + scenarioName: string, change: (host: TestServerHost, session: TestSession) => void, afterActionDocumentPositionMapperNotEquals?: true ) { - it("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); - }); + describe(scenarioName, () => { + it("when timeout occurs before request", () => { + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); + }); - it("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); + it("when timeout does not occur before request", () => { + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); + }); }); } @@ -11043,9 +11091,9 @@ fn5(); verifyOnlyRandomInfos(session, host); } - function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost) { + function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { // Main scenario action - verifyAllFnActionWithNoDts(session, host); + verifyAllFnActionWithNoDts(session, host, dependencyTsAndMapOk); // Collecting at this point retains dependency.d.ts and map watcher closeFilesForSession([randomFile], session); @@ -11058,6 +11106,43 @@ fn5(); verifyOnlyRandomInfos(session, host); } + function verifyScenarioWhenFileNotPresent( + scenarioName: string, + fileLocation: string, + verifyScenarioAndScriptInfoCollection: (session: TestSession, host: TestServerHost, dependencyTsOk?: true) => void, + noDts?: true + ) { + describe(scenarioName, () => { + it(mainScenario, () => { + const { host, session } = openTsFile(host => host.deleteFile(fileLocation)); + checkProject(session, noDts); + + verifyScenarioAndScriptInfoCollection(session, host); + }); + + it("when file is created", () => { + let fileContents: string | undefined; + const { host, session } = openTsFile(host => { + fileContents = host.readFile(fileLocation); + host.deleteFile(fileLocation); + }); + firstAction(session); + + host.writeFile(fileLocation, fileContents!); + verifyMainScenarioAndScriptInfoCollection(session, host); + }); + + it("when file is deleted", () => { + const { host, session } = openTsFile(); + firstAction(session); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(fileLocation); + verifyScenarioAndScriptInfoCollection(session, host, /*dependencyTsOk*/ true); + }); + }); + } + it(mainScenario, () => { const { host, session } = openTsFile(); checkProject(session); @@ -11065,80 +11150,60 @@ fn5(); verifyMainScenarioAndScriptInfoCollection(session, host); }); - describe("when usage file changes, document position mapper doesnt change", () => { - // Edit - verifyScenarioWithChanges((_host, session) => openFiles.forEach((openFile, index) => session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } - }))); - }); + // Edit + verifyScenarioWithChanges( + "when usage file changes, document position mapper doesnt change", + (_host, session) => openFiles.forEach( + (openFile, index) => session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } + }) + ) + ); - describe("when dependency .d.ts changes, document position mapper doesnt change", () => { - // Edit dts to add new fn - verifyScenarioWithChanges(host => host.writeFile( + // Edit dts to add new fn + verifyScenarioWithChanges( + "when dependency .d.ts changes, document position mapper doesnt change", + host => host.writeFile( dtsLocation, host.readFile(dtsLocation)!.replace( "//# sourceMappingURL=FnS.d.ts.map", `export declare function fn6(): void; //# sourceMappingURL=FnS.d.ts.map` ) - )); - }); + ) + ); - describe("when dependency file's map changes", () => { - // Edit map file to represent added new line - verifyScenarioWithChanges(host => host.writeFile( + // Edit map file to represent added new line + verifyScenarioWithChanges( + "when dependency file's map changes", + host => host.writeFile( dtsMapLocation, - `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM"}` - ), /*afterActionDocumentPositionMapperNotEquals*/ true); - }); + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` + ), + /*afterActionDocumentPositionMapperNotEquals*/ true + ); - describe("when map file is not present", () => { - it(mainScenario, () => { - const { host, session } = openTsFile(host => host.deleteFile(dtsMapLocation)); - checkProject(session); + verifyScenarioWhenFileNotPresent( + "when map file is not present", + dtsMapLocation, + verifyMainScenarioAndScriptInfoCollectionWithNoMap + ); - verifyMainScenarioAndScriptInfoCollectionWithNoMap(session, host); - }); - - it("when map file is created", () => { - let dtsMapContents: string | undefined; - const { host, session } = openTsFile(host => { - dtsMapContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - firstAction(session); - - host.writeFile(dtsMapLocation, dtsMapContents!); - verifyMainScenarioAndScriptInfoCollection(session, host); - }); - - it("when map file is deleted", () => { - const { host, session } = openTsFile(); - firstAction(session); - - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsMapLocation); - verifyMainScenarioAndScriptInfoCollectionWithNoMap(session, host, /*dependencyTsOKInScenario*/ true); - }); - }); - - describe("when .d.ts file is not present", () => { - it(mainScenario, () => { - const { host, session } = openTsFile(host => host.deleteFile(dtsLocation)); - checkProject(session, /*noDts*/ true); - - verifyMainScenarioAndScriptInfoCollectionWithNoDts(session, host); - }); - }); + verifyScenarioWhenFileNotPresent( + "when .d.ts file is not present", + dtsLocation, + verifyMainScenarioAndScriptInfoCollectionWithNoDts, + /*noDts*/ true + ); } - const usageVerifier: DocumentPositionMapperVerifier = [ - /*openFile*/ mainTs, - /*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, dtsPath], - /*actionGetter*/ gotoDefintinionFromMainTs, - /*openFileLastLine*/ 14 - ]; + const usageVerifier: DocumentPositionMapperVerifier = { + openFile: mainTs, + expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], + actionGetter: gotoDefintinionFromMainTs, + openFileLastLine: 14 + }; describe("from project that uses dependency", () => { const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation]; verifyDocumentPositionMapperUpdates( @@ -11148,12 +11213,12 @@ fn5(); ); }); - const definingVerifier: DocumentPositionMapperVerifier = [ - /*openFile*/ dependencyTs, - /*expectedProjectActualFiles*/[dependencyTs.path, libFile.path, dependencyConfig.path], - /*actionGetter*/ renameFromDependencyTs, - /*openFileLastLine*/ 6 - ]; + const definingVerifier: DocumentPositionMapperVerifier = { + openFile: dependencyTs, + expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], + actionGetter: renameFromDependencyTs, + openFileLastLine: 6 + }; describe("from defining project", () => { const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; verifyDocumentPositionMapperUpdates( @@ -11164,10 +11229,10 @@ fn5(); }); describe("when opening depedency and usage project", () => { - const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; + const closedInfos = [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path]; verifyDocumentPositionMapperUpdates( "goto Definition in usage and rename locations from defining project", - [definingVerifier], + [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], closedInfos ); }); From 1ed67c0ebd47800646b46783426c715f98e74e3d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 6 Dec 2018 15:05:23 -0800 Subject: [PATCH 023/120] Accept test changes --- src/testRunner/unittests/textStorage.ts | 18 +++++++++++------- .../reference/api/tsserverlibrary.d.ts | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/testRunner/unittests/textStorage.ts b/src/testRunner/unittests/textStorage.ts index 724dc8e4ed3..0bb14aa2d90 100644 --- a/src/testRunner/unittests/textStorage.ts +++ b/src/testRunner/unittests/textStorage.ts @@ -10,12 +10,16 @@ namespace ts.textStorage { }` }; + function getDummyScriptInfo() { + return { closeSourceMapFileWatcher: noop } as server.ScriptInfo; + } + it("text based storage should be have exactly the same as script version cache", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); - const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, getDummyScriptInfo()); + const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, getDummyScriptInfo()); ts1.useScriptVersionCache_TestOnly(); ts2.useText(); @@ -49,7 +53,7 @@ namespace ts.textStorage { it("should switch to script version cache if necessary", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, getDummyScriptInfo()); ts1.getSnapshot(); assert.isFalse(ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1"); @@ -67,7 +71,7 @@ namespace ts.textStorage { it("should be able to return the file size immediately after construction", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, getDummyScriptInfo()); assert.strictEqual(f.content.length, ts1.getTelemetryFileSize()); }); @@ -75,7 +79,7 @@ namespace ts.textStorage { it("should be able to return the file size when backed by text", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, getDummyScriptInfo()); ts1.useText(f.content); assert.isFalse(ts1.hasScriptVersionCache_TestOnly()); @@ -86,7 +90,7 @@ namespace ts.textStorage { it("should be able to return the file size when backed by a script version cache", () => { const host = projectSystem.createServerHost([f]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, getDummyScriptInfo()); ts1.useScriptVersionCache_TestOnly(); assert.isTrue(ts1.hasScriptVersionCache_TestOnly()); @@ -126,7 +130,7 @@ namespace ts.textStorage { const host = projectSystem.createServerHost([changingFile]); // Since script info is not used in these tests, just cheat by passing undefined - const ts1 = new server.TextStorage(host, server.asNormalizedPath(changingFile.path), /*initialVersion*/ undefined, {} as server.ScriptInfo); + const ts1 = new server.TextStorage(host, server.asNormalizedPath(changingFile.path), /*initialVersion*/ undefined, getDummyScriptInfo()); assert.isTrue(ts1.reloadFromDisk()); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index fa6990c07f4..91f492ddd1d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8688,6 +8688,8 @@ declare namespace ts.server { */ getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined; getScriptInfoForPath(fileName: Path): ScriptInfo | undefined; + private addSourceInfoToSourceMap; + private addMissingSourceMapFile; setHostConfiguration(args: protocol.ConfigureRequestArguments): void; closeLog(): void; /** From 2f84741725a1db41db794163fed6215259cfd57b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 6 Dec 2018 15:13:21 -0800 Subject: [PATCH 024/120] Remove unnecessary fields --- src/server/editorServices.ts | 1 - src/server/scriptInfo.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0bdff39e49a..3050c976231 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2298,7 +2298,6 @@ namespace ts.server { } else if (mapFileNameFromDeclarationInfo) { declarationInfo.sourceMapFilePath = { - declarationInfoPath: declarationInfo.path, watcher: this.addMissingSourceMapFile( project.currentDirectory === this.currentDirectory ? mapFileNameFromDeclarationInfo : diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 21c011d7261..68c448a7e76 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -283,7 +283,6 @@ namespace ts.server { /*@internal*/ export interface SourceMapFileWatcher { - declarationInfoPath: Path; watcher: FileWatcher; sourceInfos?: Map; } From 6fcc815aa8591211fe2134976b0c2a9cb961fac1 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 6 Dec 2018 18:06:59 -0800 Subject: [PATCH 025/120] Check if the expression had any trailing comments. --- src/compiler/emitter.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 15b6540900f..9c09ed79a77 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1651,11 +1651,17 @@ namespace ts { function emitPropertyAccessExpression(node: PropertyAccessExpression) { let indentBeforeDot = false; let indentAfterDot = false; + const dotRangeFirstCommentStart = skipTrivia( + currentSourceFile!.text, + node.expression.end, + /*stopAfterLineBreak*/ false, + /*stopAtComments*/ true + ); + const dotRangeStart = skipTrivia(currentSourceFile!.text, dotRangeFirstCommentStart); + const dotRangeEnd = dotRangeStart + 1; if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) { - const dotRangeStart = node.expression.end; - const dotRangeEnd = skipTrivia(currentSourceFile!.text, node.expression.end) + 1; const dotToken = createToken(SyntaxKind.DotToken); - dotToken.pos = dotRangeStart; + dotToken.pos = node.expression.end; dotToken.end = dotRangeEnd; indentBeforeDot = needsIndentation(node, node.expression, dotToken); indentAfterDot = needsIndentation(node, dotToken, node.name); @@ -1664,7 +1670,8 @@ namespace ts { emitExpression(node.expression); increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false); - const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression); + const dotHasCommentTrivia = dotRangeFirstCommentStart !== dotRangeStart; + const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression, dotHasCommentTrivia); if (shouldEmitDotDot) { writePunctuation("."); } @@ -1677,13 +1684,15 @@ namespace ts { // 1..toString is a valid property access, emit a dot after the literal // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal - function needsDotDotForPropertyAccess(expression: Expression) { + function needsDotDotForPropertyAccess(expression: Expression, dotHasTrivia: boolean) { expression = skipPartiallyEmittedExpressions(expression); if (isNumericLiteral(expression)) { // check if numeric literal is a decimal literal that was originally written with a dot const text = getLiteralTextOfNode(expression, /*neverAsciiEscape*/ true); - return !expression.numericLiteralFlags - && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); + // If he number will be printed verbatim and it doesn't already contain a dot, add one + // if the expression doesn't have any comments that will be emitted. + return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!) && + (!dotHasTrivia || printerOptions.removeComments); } else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) { // check if constant enum value is integer From 75c51f50bbe35ed51f98a4b7e4776c2fcc142089 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 6 Dec 2018 18:07:10 -0800 Subject: [PATCH 026/120] Accepted baselines. --- .../reference/numericLiteralsWithTrailingDecimalPoints01.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js index 62e40a94e22..4ea5c5c7bd2 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js @@ -38,4 +38,4 @@ var test7 = 3 .toString(); var test8 = new Number(4).toString(); var test9 = 3. + 3.; -var test10 = 0 /* any comment */..toString(); +var test10 = 0 /* any comment */.toString(); From 773c50f05ae876dacb65e2a6d2b7e9c662279ec6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 7 Dec 2018 00:40:13 -0800 Subject: [PATCH 027/120] Better error message for invalid bigint literals ending with n Helps with the cases of decimals and exponentials, though not with numbers incorrectly starting with `0`. Fixes #28887. --- src/compiler/diagnosticMessages.json | 8 ++++++++ src/compiler/scanner.ts | 18 +++++++++++++++--- .../baselines/reference/parseBigInt.errors.txt | 12 ++++++------ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index cde7516f047..ed7c25d5919 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1015,6 +1015,14 @@ "category": "Error", "code": 1351 }, + "A bigint literal cannot use exponential notation.": { + "category": "Error", + "code": 1352 + }, + "A bigint literal must be an integer.": { + "category": "Error", + "code": 1353 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index a9438110593..1a6da02bbd0 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -976,7 +976,7 @@ namespace ts { } if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { - checkForIdentifierStartAfterNumericLiteral(); + checkForIdentifierStartAfterNumericLiteral(decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); return { type: SyntaxKind.NumericLiteral, value: "" + +result // if value is not an integer, it can be safely coerced to a number @@ -990,14 +990,26 @@ namespace ts { } } - function checkForIdentifierStartAfterNumericLiteral() { + function checkForIdentifierStartAfterNumericLiteral(isScientific?: boolean) { if (!isIdentifierStart(text.charCodeAt(pos), languageVersion)) { return; } const identifierStart = pos; const { length } = scanIdentifierParts(); - error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); + + if (length === 1 && text[identifierStart] === "n") { + if (isScientific) { + error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, identifierStart, length); + } + else { + error(Diagnostics.A_bigint_literal_must_be_an_integer, identifierStart, length); + } + } + else { + error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); + } + pos = identifierStart; } diff --git a/tests/baselines/reference/parseBigInt.errors.txt b/tests/baselines/reference/parseBigInt.errors.txt index 16878e75fe9..72f98ee0f46 100644 --- a/tests/baselines/reference/parseBigInt.errors.txt +++ b/tests/baselines/reference/parseBigInt.errors.txt @@ -1,9 +1,9 @@ tests/cases/compiler/parseBigInt.ts(51,20): error TS2736: Operator '+' cannot be applied to type '123n'. tests/cases/compiler/parseBigInt.ts(52,23): error TS2736: Operator '+' cannot be applied to type '291n'. tests/cases/compiler/parseBigInt.ts(56,25): error TS1005: ',' expected. -tests/cases/compiler/parseBigInt.ts(57,25): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. -tests/cases/compiler/parseBigInt.ts(58,22): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. -tests/cases/compiler/parseBigInt.ts(59,28): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. +tests/cases/compiler/parseBigInt.ts(57,25): error TS1352: A bigint literal cannot use exponential notation. +tests/cases/compiler/parseBigInt.ts(58,22): error TS1353: A bigint literal must be an integer. +tests/cases/compiler/parseBigInt.ts(59,28): error TS1353: A bigint literal must be an integer. tests/cases/compiler/parseBigInt.ts(60,23): error TS1177: Binary digit expected. tests/cases/compiler/parseBigInt.ts(61,20): error TS1178: Octal digit expected. tests/cases/compiler/parseBigInt.ts(62,20): error TS1125: Hexadecimal digit expected. @@ -82,13 +82,13 @@ tests/cases/compiler/parseBigInt.ts(70,72): error TS2345: Argument of type '3' i !!! error TS1005: ',' expected. { const scientific = 1e2n; } ~ -!!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. +!!! error TS1352: A bigint literal cannot use exponential notation. { const decimal = 4.1n; } ~ -!!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. +!!! error TS1353: A bigint literal must be an integer. { const leadingDecimal = .1n; } ~ -!!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. +!!! error TS1353: A bigint literal must be an integer. const emptyBinary = 0bn; // should error but infer 0n !!! error TS1177: Binary digit expected. From c156c9377a410ce50457ed8f5243bfbe063445d7 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 08:56:22 -0800 Subject: [PATCH 028/120] Add note about comparing source positions --- src/compiler/sourcemap.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 7c47449da10..c2597d688d8 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -583,6 +583,8 @@ namespace ts { } function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { + // Compares sourcePosition without comparing sourceIndex + // since the mappings are grouped by sourceIndex Debug.assert(left.sourceIndex === right.sourceIndex); return compareValues(left.sourcePosition, right.sourcePosition); } From 2e9be8c05bf4cac83e5cc7a90c03440d07c48d48 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 10 Dec 2018 21:39:17 -0800 Subject: [PATCH 029/120] Added more tests, including some in 'removeComments' mode. --- ...ericLiteralsWithTrailingDecimalPoints01.ts | 26 ++++++++++--- ...ericLiteralsWithTrailingDecimalPoints02.ts | 37 +++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts diff --git a/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts b/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts index 4e8e3529f3a..de45315d8ef 100644 --- a/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts +++ b/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts @@ -4,16 +4,32 @@ 1.toString(); 1.+2.0 + 3. ; -// Preserve whitespace where important for JS compatibility +// Preserve whitespace and comments where important for JS compatibility var i: number = 1; -var test1 = i.toString(); +var test1 = i.toString(); var test2 = 2.toString(); var test3 = 3 .toString(); var test4 = 3 .toString(); -var test5 = 3 .toString(); +var test5 = 3 .toString(); var test6 = 3.['toString'](); var test7 = 3 .toString(); var test8 = new Number(4).toString(); -var test9 = 3. + 3. -var test10 = 0 /* any comment */.toString(); +var test9 = 3. + 3.; +var test10 = 0 /* comment */.toString(); +var test11 = 3. /* comment */ .toString(); +var test12 = 3 + /* comment */ .toString(); +var test13 = 3. + /* comment */ .toString(); +var test14 = 3 + // comment + .toString(); +var test15 = 3. + // comment + .toString(); +var test16 = 3 // comment time + .toString(); +var test17 = 3. // comment time again + .toString(); + diff --git a/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts b/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts new file mode 100644 index 00000000000..775175f70c0 --- /dev/null +++ b/tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts @@ -0,0 +1,37 @@ +// @lib: es5 +// @removeComments: true + +1..toString(); +1.0.toString(); +1.toString(); +1.+2.0 + 3. ; + +// Preserve whitespace where important for JS compatibility +var i: number = 1; +var test1 = i.toString(); +var test2 = 2.toString(); +var test3 = 3 .toString(); +var test4 = 3 .toString(); +var test5 = 3 .toString(); +var test6 = 3.['toString'](); +var test7 = 3 +.toString(); +var test8 = new Number(4).toString(); +var test9 = 3. + 3.; +var test10 = 0 /* comment */.toString(); +var test11 = 3. /* comment */ .toString(); +var test12 = 3 + /* comment */ .toString(); +var test13 = 3. + /* comment */ .toString(); +var test14 = 3 + // comment + .toString(); +var test15 = 3. + // comment + .toString(); +var test16 = 3 // comment time + .toString(); +var test17 = 3. // comment time again + .toString(); + From 9a6d2ee664b1bf2f79035cf007c24d8d6be02309 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 10 Dec 2018 22:17:09 -0800 Subject: [PATCH 030/120] Accepted baselines. --- ...ralsWithTrailingDecimalPoints01.errors.txt | 26 ++- ...ericLiteralsWithTrailingDecimalPoints01.js | 45 ++++- ...iteralsWithTrailingDecimalPoints01.symbols | 62 ++++++- ...cLiteralsWithTrailingDecimalPoints01.types | 78 +++++++- ...ralsWithTrailingDecimalPoints02.errors.txt | 50 ++++++ ...ericLiteralsWithTrailingDecimalPoints02.js | 69 ++++++++ ...iteralsWithTrailingDecimalPoints02.symbols | 118 +++++++++++++ ...cLiteralsWithTrailingDecimalPoints02.types | 167 ++++++++++++++++++ 8 files changed, 590 insertions(+), 25 deletions(-) create mode 100644 tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt create mode 100644 tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.js create mode 100644 tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.symbols create mode 100644 tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.types diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt index c1484b2da66..d89255a9568 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.errors.txt @@ -12,9 +12,9 @@ tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts(9,24): error !!! error TS1005: ';' expected. 1.+2.0 + 3. ; - // Preserve whitespace where important for JS compatibility + // Preserve whitespace and comments where important for JS compatibility var i: number = 1; - var test1 = i.toString(); + var test1 = i.toString(); var test2 = 2.toString(); ~~~~~~~~ !!! error TS1005: ',' expected. @@ -24,11 +24,27 @@ tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints01.ts(9,24): error !!! error TS1109: Expression expected. var test3 = 3 .toString(); var test4 = 3 .toString(); - var test5 = 3 .toString(); + var test5 = 3 .toString(); var test6 = 3.['toString'](); var test7 = 3 .toString(); var test8 = new Number(4).toString(); - var test9 = 3. + 3. - var test10 = 0 /* any comment */.toString(); + var test9 = 3. + 3.; + var test10 = 0 /* comment */.toString(); + var test11 = 3. /* comment */ .toString(); + var test12 = 3 + /* comment */ .toString(); + var test13 = 3. + /* comment */ .toString(); + var test14 = 3 + // comment + .toString(); + var test15 = 3. + // comment + .toString(); + var test16 = 3 // comment time + .toString(); + var test17 = 3. // comment time again + .toString(); + \ No newline at end of file diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js index 4ea5c5c7bd2..ab2f0cbbdc7 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.js @@ -4,19 +4,35 @@ 1.toString(); 1.+2.0 + 3. ; -// Preserve whitespace where important for JS compatibility +// Preserve whitespace and comments where important for JS compatibility var i: number = 1; -var test1 = i.toString(); +var test1 = i.toString(); var test2 = 2.toString(); var test3 = 3 .toString(); var test4 = 3 .toString(); -var test5 = 3 .toString(); +var test5 = 3 .toString(); var test6 = 3.['toString'](); var test7 = 3 .toString(); var test8 = new Number(4).toString(); -var test9 = 3. + 3. -var test10 = 0 /* any comment */.toString(); +var test9 = 3. + 3.; +var test10 = 0 /* comment */.toString(); +var test11 = 3. /* comment */ .toString(); +var test12 = 3 + /* comment */ .toString(); +var test13 = 3. + /* comment */ .toString(); +var test14 = 3 + // comment + .toString(); +var test15 = 3. + // comment + .toString(); +var test16 = 3 // comment time + .toString(); +var test17 = 3. // comment time again + .toString(); + //// [numericLiteralsWithTrailingDecimalPoints01.js] @@ -25,7 +41,7 @@ var test10 = 0 /* any comment */.toString(); 1.; toString(); 1. + 2.0 + 3.; -// Preserve whitespace where important for JS compatibility +// Preserve whitespace and comments where important for JS compatibility var i = 1; var test1 = i.toString(); var test2 = 2., toString; @@ -38,4 +54,19 @@ var test7 = 3 .toString(); var test8 = new Number(4).toString(); var test9 = 3. + 3.; -var test10 = 0 /* any comment */.toString(); +var test10 = 0 /* comment */.toString(); +var test11 = 3. /* comment */.toString(); +var test12 = 3 + /* comment */ .toString(); +var test13 = 3. + /* comment */ .toString(); +var test14 = 3 + // comment + .toString(); +var test15 = 3. + // comment + .toString(); +var test16 = 3 // comment time + .toString(); +var test17 = 3. // comment time again + .toString(); diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols index 39ef3b1c0be..c4d3aa4b02a 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.symbols @@ -12,11 +12,11 @@ 1.+2.0 + 3. ; -// Preserve whitespace where important for JS compatibility +// Preserve whitespace and comments where important for JS compatibility var i: number = 1; >i : Symbol(i, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 6, 3)) -var test1 = i.toString(); +var test1 = i.toString(); >test1 : Symbol(test1, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 7, 3)) >i.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) >i : Symbol(i, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 6, 3)) @@ -36,7 +36,7 @@ var test4 = 3 .toString(); >3 .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) >toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) -var test5 = 3 .toString(); +var test5 = 3 .toString(); >test5 : Symbol(test5, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 11, 3)) >3 .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) >toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) @@ -58,11 +58,61 @@ var test8 = new Number(4).toString(); >Number : Symbol(Number, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) -var test9 = 3. + 3. +var test9 = 3. + 3.; >test9 : Symbol(test9, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 16, 3)) -var test10 = 0 /* any comment */.toString(); +var test10 = 0 /* comment */.toString(); >test10 : Symbol(test10, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 17, 3)) ->0 /* any comment */.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>0 /* comment */.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) >toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +var test11 = 3. /* comment */ .toString(); +>test11 : Symbol(test11, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 18, 3)) +>3. /* comment */ .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test12 = 3 +>test12 : Symbol(test12, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 19, 3)) +>3 /* comment */ .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + /* comment */ .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test13 = 3. +>test13 : Symbol(test13, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 21, 3)) +>3. /* comment */ .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + /* comment */ .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test14 = 3 +>test14 : Symbol(test14, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 23, 3)) +>3 // comment .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + // comment + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test15 = 3. +>test15 : Symbol(test15, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 26, 3)) +>3. // comment .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + // comment + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test16 = 3 // comment time +>test16 : Symbol(test16, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 29, 3)) +>3 // comment time .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test17 = 3. // comment time again +>test17 : Symbol(test17, Decl(numericLiteralsWithTrailingDecimalPoints01.ts, 31, 3)) +>3. // comment time again .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types index 839f03ce420..6228ec38d81 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints01.types @@ -23,12 +23,12 @@ >2.0 : 2 >3. : 3 -// Preserve whitespace where important for JS compatibility +// Preserve whitespace and comments where important for JS compatibility var i: number = 1; >i : number >1 : 1 -var test1 = i.toString(); +var test1 = i.toString(); >test1 : string >i.toString() : string >i.toString : (radix?: number) => string @@ -56,7 +56,7 @@ var test4 = 3 .toString(); >3 : 3 >toString : (radix?: number) => string -var test5 = 3 .toString(); +var test5 = 3 .toString(); >test5 : string >3 .toString() : string >3 .toString : (radix?: number) => string @@ -88,16 +88,80 @@ var test8 = new Number(4).toString(); >4 : 4 >toString : (radix?: number) => string -var test9 = 3. + 3. +var test9 = 3. + 3.; >test9 : number >3. + 3. : number >3. : 3 >3. : 3 -var test10 = 0 /* any comment */.toString(); +var test10 = 0 /* comment */.toString(); >test10 : string ->0 /* any comment */.toString() : string ->0 /* any comment */.toString : (radix?: number) => string +>0 /* comment */.toString() : string +>0 /* comment */.toString : (radix?: number) => string >0 : 0 >toString : (radix?: number) => string +var test11 = 3. /* comment */ .toString(); +>test11 : string +>3. /* comment */ .toString() : string +>3. /* comment */ .toString : (radix?: number) => string +>3. : 3 +>toString : (radix?: number) => string + +var test12 = 3 +>test12 : string +>3 /* comment */ .toString() : string +>3 /* comment */ .toString : (radix?: number) => string +>3 : 3 + + /* comment */ .toString(); +>toString : (radix?: number) => string + +var test13 = 3. +>test13 : string +>3. /* comment */ .toString() : string +>3. /* comment */ .toString : (radix?: number) => string +>3. : 3 + + /* comment */ .toString(); +>toString : (radix?: number) => string + +var test14 = 3 +>test14 : string +>3 // comment .toString() : string +>3 // comment .toString : (radix?: number) => string +>3 : 3 + + // comment + .toString(); +>toString : (radix?: number) => string + +var test15 = 3. +>test15 : string +>3. // comment .toString() : string +>3. // comment .toString : (radix?: number) => string +>3. : 3 + + // comment + .toString(); +>toString : (radix?: number) => string + +var test16 = 3 // comment time +>test16 : string +>3 // comment time .toString() : string +>3 // comment time .toString : (radix?: number) => string +>3 : 3 + + .toString(); +>toString : (radix?: number) => string + +var test17 = 3. // comment time again +>test17 : string +>3. // comment time again .toString() : string +>3. // comment time again .toString : (radix?: number) => string +>3. : 3 + + .toString(); +>toString : (radix?: number) => string + + diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt new file mode 100644 index 00000000000..4f83c9d031f --- /dev/null +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt @@ -0,0 +1,50 @@ +tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(3,3): error TS1005: ';' expected. +tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,15): error TS1005: ',' expected. +tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,23): error TS1005: ',' expected. +tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,24): error TS1109: Expression expected. + + +==== tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts (4 errors) ==== + 1..toString(); + 1.0.toString(); + 1.toString(); + ~~~~~~~~ +!!! error TS1005: ';' expected. + 1.+2.0 + 3. ; + + // Preserve whitespace where important for JS compatibility + var i: number = 1; + var test1 = i.toString(); + var test2 = 2.toString(); + ~~~~~~~~ +!!! error TS1005: ',' expected. + ~ +!!! error TS1005: ',' expected. + ~ +!!! error TS1109: Expression expected. + var test3 = 3 .toString(); + var test4 = 3 .toString(); + var test5 = 3 .toString(); + var test6 = 3.['toString'](); + var test7 = 3 + .toString(); + var test8 = new Number(4).toString(); + var test9 = 3. + 3.; + var test10 = 0 /* comment */.toString(); + var test11 = 3. /* comment */ .toString(); + var test12 = 3 + /* comment */ .toString(); + var test13 = 3. + /* comment */ .toString(); + var test14 = 3 + // comment + .toString(); + var test15 = 3. + // comment + .toString(); + var test16 = 3 // comment time + .toString(); + var test17 = 3. // comment time again + .toString(); + + \ No newline at end of file diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.js b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.js new file mode 100644 index 00000000000..b7aff7379d5 --- /dev/null +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.js @@ -0,0 +1,69 @@ +//// [numericLiteralsWithTrailingDecimalPoints02.ts] +1..toString(); +1.0.toString(); +1.toString(); +1.+2.0 + 3. ; + +// Preserve whitespace where important for JS compatibility +var i: number = 1; +var test1 = i.toString(); +var test2 = 2.toString(); +var test3 = 3 .toString(); +var test4 = 3 .toString(); +var test5 = 3 .toString(); +var test6 = 3.['toString'](); +var test7 = 3 +.toString(); +var test8 = new Number(4).toString(); +var test9 = 3. + 3.; +var test10 = 0 /* comment */.toString(); +var test11 = 3. /* comment */ .toString(); +var test12 = 3 + /* comment */ .toString(); +var test13 = 3. + /* comment */ .toString(); +var test14 = 3 + // comment + .toString(); +var test15 = 3. + // comment + .toString(); +var test16 = 3 // comment time + .toString(); +var test17 = 3. // comment time again + .toString(); + + + +//// [numericLiteralsWithTrailingDecimalPoints02.js] +1..toString(); +1.0.toString(); +1.; +toString(); +1. + 2.0 + 3.; +var i = 1; +var test1 = i.toString(); +var test2 = 2., toString; +(); +var test3 = 3..toString(); +var test4 = 3..toString(); +var test5 = 3..toString(); +var test6 = 3.['toString'](); +var test7 = 3 + .toString(); +var test8 = new Number(4).toString(); +var test9 = 3. + 3.; +var test10 = 0..toString(); +var test11 = 3..toString(); +var test12 = 3 + .toString(); +var test13 = 3. + .toString(); +var test14 = 3 + .toString(); +var test15 = 3. + .toString(); +var test16 = 3 + .toString(); +var test17 = 3. + .toString(); diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.symbols b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.symbols new file mode 100644 index 00000000000..9d2121ef444 --- /dev/null +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.symbols @@ -0,0 +1,118 @@ +=== tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts === +1..toString(); +>1..toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +1.0.toString(); +>1.0.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +1.toString(); +>toString : Symbol(toString, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 8, 14)) + +1.+2.0 + 3. ; + +// Preserve whitespace where important for JS compatibility +var i: number = 1; +>i : Symbol(i, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 6, 3)) + +var test1 = i.toString(); +>test1 : Symbol(test1, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 7, 3)) +>i.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>i : Symbol(i, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 6, 3)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test2 = 2.toString(); +>test2 : Symbol(test2, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 8, 3)) +>toString : Symbol(toString, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 8, 14)) + +var test3 = 3 .toString(); +>test3 : Symbol(test3, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 9, 3)) +>3 .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test4 = 3 .toString(); +>test4 : Symbol(test4, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 10, 3)) +>3 .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test5 = 3 .toString(); +>test5 : Symbol(test5, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 11, 3)) +>3 .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test6 = 3.['toString'](); +>test6 : Symbol(test6, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 12, 3)) +>'toString' : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test7 = 3 +>test7 : Symbol(test7, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 13, 3)) +>3.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +.toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test8 = new Number(4).toString(); +>test8 : Symbol(test8, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 15, 3)) +>new Number(4).toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>Number : Symbol(Number, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test9 = 3. + 3.; +>test9 : Symbol(test9, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 16, 3)) + +var test10 = 0 /* comment */.toString(); +>test10 : Symbol(test10, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 17, 3)) +>0 /* comment */.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test11 = 3. /* comment */ .toString(); +>test11 : Symbol(test11, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 18, 3)) +>3. /* comment */ .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test12 = 3 +>test12 : Symbol(test12, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 19, 3)) +>3 /* comment */ .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + /* comment */ .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test13 = 3. +>test13 : Symbol(test13, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 21, 3)) +>3. /* comment */ .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + /* comment */ .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test14 = 3 +>test14 : Symbol(test14, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 23, 3)) +>3 // comment .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + // comment + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test15 = 3. +>test15 : Symbol(test15, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 26, 3)) +>3. // comment .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + // comment + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test16 = 3 // comment time +>test16 : Symbol(test16, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 29, 3)) +>3 // comment time .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +var test17 = 3. // comment time again +>test17 : Symbol(test17, Decl(numericLiteralsWithTrailingDecimalPoints02.ts, 31, 3)) +>3. // comment time again .toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + .toString(); +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.types b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.types new file mode 100644 index 00000000000..3aacb64fea0 --- /dev/null +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.types @@ -0,0 +1,167 @@ +=== tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts === +1..toString(); +>1..toString() : string +>1..toString : (radix?: number) => string +>1. : 1 +>toString : (radix?: number) => string + +1.0.toString(); +>1.0.toString() : string +>1.0.toString : (radix?: number) => string +>1.0 : 1 +>toString : (radix?: number) => string + +1.toString(); +>1. : 1 +>toString() : any +>toString : any + +1.+2.0 + 3. ; +>1.+2.0 + 3. : number +>1.+2.0 : number +>1. : 1 +>2.0 : 2 +>3. : 3 + +// Preserve whitespace where important for JS compatibility +var i: number = 1; +>i : number +>1 : 1 + +var test1 = i.toString(); +>test1 : string +>i.toString() : string +>i.toString : (radix?: number) => string +>i : number +>toString : (radix?: number) => string + +var test2 = 2.toString(); +>test2 : number +>2. : 2 +>toString : any +>() : any +> : any + +var test3 = 3 .toString(); +>test3 : string +>3 .toString() : string +>3 .toString : (radix?: number) => string +>3 : 3 +>toString : (radix?: number) => string + +var test4 = 3 .toString(); +>test4 : string +>3 .toString() : string +>3 .toString : (radix?: number) => string +>3 : 3 +>toString : (radix?: number) => string + +var test5 = 3 .toString(); +>test5 : string +>3 .toString() : string +>3 .toString : (radix?: number) => string +>3 : 3 +>toString : (radix?: number) => string + +var test6 = 3.['toString'](); +>test6 : string +>3.['toString']() : string +>3.['toString'] : (radix?: number) => string +>3. : 3 +>'toString' : "toString" + +var test7 = 3 +>test7 : string +>3.toString() : string +>3.toString : (radix?: number) => string +>3 : 3 + +.toString(); +>toString : (radix?: number) => string + +var test8 = new Number(4).toString(); +>test8 : string +>new Number(4).toString() : string +>new Number(4).toString : (radix?: number) => string +>new Number(4) : Number +>Number : NumberConstructor +>4 : 4 +>toString : (radix?: number) => string + +var test9 = 3. + 3.; +>test9 : number +>3. + 3. : number +>3. : 3 +>3. : 3 + +var test10 = 0 /* comment */.toString(); +>test10 : string +>0 /* comment */.toString() : string +>0 /* comment */.toString : (radix?: number) => string +>0 : 0 +>toString : (radix?: number) => string + +var test11 = 3. /* comment */ .toString(); +>test11 : string +>3. /* comment */ .toString() : string +>3. /* comment */ .toString : (radix?: number) => string +>3. : 3 +>toString : (radix?: number) => string + +var test12 = 3 +>test12 : string +>3 /* comment */ .toString() : string +>3 /* comment */ .toString : (radix?: number) => string +>3 : 3 + + /* comment */ .toString(); +>toString : (radix?: number) => string + +var test13 = 3. +>test13 : string +>3. /* comment */ .toString() : string +>3. /* comment */ .toString : (radix?: number) => string +>3. : 3 + + /* comment */ .toString(); +>toString : (radix?: number) => string + +var test14 = 3 +>test14 : string +>3 // comment .toString() : string +>3 // comment .toString : (radix?: number) => string +>3 : 3 + + // comment + .toString(); +>toString : (radix?: number) => string + +var test15 = 3. +>test15 : string +>3. // comment .toString() : string +>3. // comment .toString : (radix?: number) => string +>3. : 3 + + // comment + .toString(); +>toString : (radix?: number) => string + +var test16 = 3 // comment time +>test16 : string +>3 // comment time .toString() : string +>3 // comment time .toString : (radix?: number) => string +>3 : 3 + + .toString(); +>toString : (radix?: number) => string + +var test17 = 3. // comment time again +>test17 : string +>3. // comment time again .toString() : string +>3. // comment time again .toString : (radix?: number) => string +>3. : 3 + + .toString(); +>toString : (radix?: number) => string + + From caa7441b1f8df613b7dd23b2ad7b7bcd1b00886b Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 11 Dec 2018 16:56:45 -0800 Subject: [PATCH 031/120] Accepted baselines. --- .../numericLiteralsWithTrailingDecimalPoints02.errors.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt index 4f83c9d031f..777a36031d4 100644 --- a/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt +++ b/tests/baselines/reference/numericLiteralsWithTrailingDecimalPoints02.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(3,3): error TS1005: ';' expected. -tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,15): error TS1005: ',' expected. +tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(3,3): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. +tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,15): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,23): error TS1005: ',' expected. tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,24): error TS1109: Expression expected. @@ -9,7 +9,7 @@ tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,24): error 1.0.toString(); 1.toString(); ~~~~~~~~ -!!! error TS1005: ';' expected. +!!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. 1.+2.0 + 3. ; // Preserve whitespace where important for JS compatibility @@ -17,7 +17,7 @@ tests/cases/compiler/numericLiteralsWithTrailingDecimalPoints02.ts(9,24): error var test1 = i.toString(); var test2 = 2.toString(); ~~~~~~~~ -!!! error TS1005: ',' expected. +!!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. ~ !!! error TS1005: ',' expected. ~ From 5788446aa1757042a15d25b550e7a9bb95e58d97 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 12 Dec 2018 15:39:32 -0800 Subject: [PATCH 032/120] Use contextual parameter types over binding pattern initializer types (#28967) * Use contextual parameter types over binding pattern initializer types * Remove unneeded check --- src/compiler/checker.ts | 55 ++++++++++-------- ...ingArrayBindingPatternAndAssignment3.types | 4 +- ...ingInitializerContextualTypeFromContext.js | 50 ++++++++++++++++ ...itializerContextualTypeFromContext.symbols | 58 +++++++++++++++++++ ...InitializerContextualTypeFromContext.types | 57 ++++++++++++++++++ ...ameterInDestructuringWithInitializer.types | 2 +- ...tedObjectBindingPatternDefaultValues.types | 4 +- ...ingInitializerContextualTypeFromContext.ts | 19 ++++++ 8 files changed, 220 insertions(+), 29 deletions(-) create mode 100644 tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js create mode 100644 tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols create mode 100644 tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types create mode 100644 tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index acf5125aa9b..356ddc2a28e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4849,7 +4849,7 @@ namespace ts { if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) { type = getTypeWithFacts(type, TypeFacts.NEUndefined); } - return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ? + return declaration.initializer && !getContextualTypeForVariableLikeDeclaration(walkUpBindingElementsAndPatterns(declaration)) ? getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) : type; } @@ -17009,6 +17009,32 @@ namespace ts { } } + function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + switch (declaration.kind) { + case SyntaxKind.Parameter: + return getContextuallyTypedParameterType(declaration); + case SyntaxKind.BindingElement: + return getContextualTypeForBindingElement(declaration); + // By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent + } + } + + function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined { + const parentDeclaration = declaration.parent.parent; + const name = declaration.propertyName || declaration.name; + const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration); + if (parentType && !isBindingPattern(name)) { + const text = getTextOfPropertyName(name); + if (text !== undefined) { + return getTypeOfPropertyOfType(parentType, text); + } + } + } + // In a variable, parameter or property declaration with a type annotation, // the contextual type of an initializer expression is the type of the variable, parameter or property. // Otherwise, in a parameter declaration of a contextually typed function expression, @@ -17020,32 +17046,13 @@ namespace ts { function getContextualTypeForInitializerExpression(node: Expression): Type | undefined { const declaration = node.parent; if (hasInitializer(declaration) && node === declaration.initializer) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); + const result = getContextualTypeForVariableLikeDeclaration(declaration); + if (result) { + return result; } - if (declaration.kind === SyntaxKind.Parameter) { - const type = getContextuallyTypedParameterType(declaration); - if (type) { - return type; - } - } - if (isBindingPattern(declaration.name)) { + if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); } - if (isBindingPattern(declaration.parent)) { - const parentDeclaration = declaration.parent.parent; - const name = (declaration as BindingElement).propertyName || declaration.name; - if (parentDeclaration.kind !== SyntaxKind.BindingElement) { - const parentTypeNode = getEffectiveTypeAnnotationNode(parentDeclaration); - if (parentTypeNode && !isBindingPattern(name)) { - const text = getTextOfPropertyName(name); - if (text) { - return getTypeOfPropertyOfType(getTypeFromTypeNode(parentTypeNode), text); - } - } - } - } } return undefined; } diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types index 0fb81ba6aa3..389b6b5e5f5 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types @@ -45,8 +45,8 @@ const [f, g = f, h = i, i = f] = [1]; // error for h = i >c : number >d : number >c : number ->e : any ->e : any +>e : number +>e : number })([1]); >[1] : number[] diff --git a/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js new file mode 100644 index 00000000000..2dd6b4ea11a --- /dev/null +++ b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js @@ -0,0 +1,50 @@ +//// [destructuringInitializerContextualTypeFromContext.ts] +interface SFC

{ + (props: P & { children?: any }): any | null; +} + +interface Props { + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +} + +const Parent: SFC = ({ + children, + name = "Artemis", + ...props +}) => Child({name, ...props}); + +const Child: SFC = ({ + children, + name = "Artemis", + ...props +}) => `name: ${name} props: ${JSON.stringify(props)}`; + +//// [destructuringInitializerContextualTypeFromContext.js] +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +}; +var Parent = function (_a) { + var children = _a.children, _b = _a.name, name = _b === void 0 ? "Artemis" : _b, props = __rest(_a, ["children", "name"]); + return Child(__assign({ name: name }, props)); +}; +var Child = function (_a) { + var children = _a.children, _b = _a.name, name = _b === void 0 ? "Artemis" : _b, props = __rest(_a, ["children", "name"]); + return "name: " + name + " props: " + JSON.stringify(props); +}; diff --git a/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols new file mode 100644 index 00000000000..317a09ee105 --- /dev/null +++ b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols @@ -0,0 +1,58 @@ +=== tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts === +interface SFC

{ +>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0)) +>P : Symbol(P, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 14)) + + (props: P & { children?: any }): any | null; +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 1, 5)) +>P : Symbol(P, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 14)) +>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 1, 17)) +} + +interface Props { +>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1)) + + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +>name : Symbol(Props.name, Decl(destructuringInitializerContextualTypeFromContext.ts, 4, 17)) +} + +const Parent: SFC = ({ +>Parent : Symbol(Parent, Decl(destructuringInitializerContextualTypeFromContext.ts, 8, 5)) +>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0)) +>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1)) + + children, +>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 8, 29)) + + name = "Artemis", +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 9, 13)) + + ...props +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 10, 21)) + +}) => Child({name, ...props}); +>Child : Symbol(Child, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 5)) +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 12, 13)) +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 10, 21)) + +const Child: SFC = ({ +>Child : Symbol(Child, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 5)) +>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0)) +>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1)) + + children, +>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 28)) + + name = "Artemis", +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 15, 13)) + + ...props +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 16, 21)) + +}) => `name: ${name} props: ${JSON.stringify(props)}`; +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 15, 13)) +>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 16, 21)) + diff --git a/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types new file mode 100644 index 00000000000..fa76e2eef25 --- /dev/null +++ b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types @@ -0,0 +1,57 @@ +=== tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts === +interface SFC

{ + (props: P & { children?: any }): any | null; +>props : P & { children?: any; } +>children : any +>null : null +} + +interface Props { + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +} + +const Parent: SFC = ({ +>Parent : SFC +>({ children, name = "Artemis", ...props}) => Child({name, ...props}) : ({ children, name, ...props }: Props & { children?: any; }) => any + + children, +>children : any + + name = "Artemis", +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>"Artemis" : "Artemis" + + ...props +>props : {} + +}) => Child({name, ...props}); +>Child({name, ...props}) : any +>Child : SFC +>{name, ...props} : { name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; } +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>props : {} + +const Child: SFC = ({ +>Child : SFC +>({ children, name = "Artemis", ...props}) => `name: ${name} props: ${JSON.stringify(props)}` : ({ children, name, ...props }: Props & { children?: any; }) => string + + children, +>children : any + + name = "Artemis", +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>"Artemis" : "Artemis" + + ...props +>props : {} + +}) => `name: ${name} props: ${JSON.stringify(props)}`; +>`name: ${name} props: ${JSON.stringify(props)}` : string +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>JSON.stringify(props) : string +>JSON.stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (string | number)[], space?: string | number): string; } +>JSON : JSON +>stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (string | number)[], space?: string | number): string; } +>props : {} + diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types index 497bae6993a..7ffbc43b86a 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types @@ -167,7 +167,7 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, >b : number >c : number >6 : 6 ->{b: 4, c: 5} : { b: number; c?: number; } +>{b: 4, c: 5} : { b: number; c: number; } >b : number >4 : 4 >c : number diff --git a/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types b/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types index 82c3c8d35a6..8bfc93c3db7 100644 --- a/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types +++ b/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types @@ -50,7 +50,7 @@ function foo1( >"secondary" : "secondary" } = { primary: "SomeSkill", secondary: "someSkill" } ->{ primary: "SomeSkill", secondary: "someSkill" } : { primary?: string; secondary?: string; } +>{ primary: "SomeSkill", secondary: "someSkill" } : { primary: string; secondary: string; } >primary : string >"SomeSkill" : "SomeSkill" >secondary : string @@ -88,7 +88,7 @@ function foo2( >"secondary" : "secondary" } = { primary: "SomeSkill", secondary: "someSkill" } ->{ primary: "SomeSkill", secondary: "someSkill" } : { primary?: string; secondary?: string; } +>{ primary: "SomeSkill", secondary: "someSkill" } : { primary: string; secondary: string; } >primary : string >"SomeSkill" : "SomeSkill" >secondary : string diff --git a/tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts b/tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts new file mode 100644 index 00000000000..6153b1e3a80 --- /dev/null +++ b/tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts @@ -0,0 +1,19 @@ +interface SFC

{ + (props: P & { children?: any }): any | null; +} + +interface Props { + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +} + +const Parent: SFC = ({ + children, + name = "Artemis", + ...props +}) => Child({name, ...props}); + +const Child: SFC = ({ + children, + name = "Artemis", + ...props +}) => `name: ${name} props: ${JSON.stringify(props)}`; \ No newline at end of file From 1b2ca96aadb6e6bbb4e3ce4551f83f53939b256c Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 13 Dec 2018 19:11:15 +1100 Subject: [PATCH 033/120] Build - List "pretty-hrtime" as a dev dependency in root "package.json". --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3f5cb1223e9..d8c514363e5 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", "plugin-error": "latest", + "pretty-hrtime": "^1.0.3", "prex": "^0.4.3", "q": "latest", "remove-internal": "^2.9.2", From de4803658e91c794fa9f2c81758f09403392a454 Mon Sep 17 00:00:00 2001 From: Kerem Date: Thu, 13 Dec 2018 20:21:36 +0100 Subject: [PATCH 034/120] Disable truncation for codefixes in signatureToMethodDeclaration. (#28188) --- src/services/codefixes/helpers.ts | 2 +- ...ixClassExtendAbstractMethodWithLongName.ts | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/codeFixClassExtendAbstractMethodWithLongName.ts diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index eec693bbb69..461771c0b5e 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -97,7 +97,7 @@ namespace ts.codefix { optional: boolean, body: Block | undefined, ): MethodDeclaration | undefined { - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.NoTruncation | NodeBuilderFlags.SuppressAnyReturnType); if (!signatureDeclaration) { return undefined; } diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodWithLongName.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodWithLongName.ts new file mode 100644 index 00000000000..dd2eb9c1709 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodWithLongName.ts @@ -0,0 +1,50 @@ +/// + +////namespace some.really.long.generated.type.goes.here.you.know.this_.should.be.pretty.simple { +//// export interface Yah {} +////} +////namespace another.really.long.generated.type.goes.here.too.because.who.cares.about.space.do_.you.feel.me { +//// export interface Yah {} +////} +////interface this_will_be_collapsed {} +////interface this_is_fine {} +////abstract class AbstractCstVisitor { +//// abstract Node( +//// arg1: [ +//// some.really.long.generated.type.goes.here.you.know.this_.should.be.pretty.simple.Yah[], +//// another.really.long.generated.type.goes.here.too.because.who.cares.about.space.do_.you.feel.me.Yah[] +//// ], +//// arg2: [this_will_be_collapsed], +//// arg3: Set, +//// arg4: this_is_fine +//// ): Set; +////} +////class CstVisitorImplementation extends AbstractCstVisitor {} + +verify.codeFix({ + description: "Implement inherited abstract class", + newFileContent: `namespace some.really.long.generated.type.goes.here.you.know.this_.should.be.pretty.simple { + export interface Yah {} +} +namespace another.really.long.generated.type.goes.here.too.because.who.cares.about.space.do_.you.feel.me { + export interface Yah {} +} +interface this_will_be_collapsed {} +interface this_is_fine {} +abstract class AbstractCstVisitor { + abstract Node( + arg1: [ + some.really.long.generated.type.goes.here.you.know.this_.should.be.pretty.simple.Yah[], + another.really.long.generated.type.goes.here.too.because.who.cares.about.space.do_.you.feel.me.Yah[] + ], + arg2: [this_will_be_collapsed], + arg3: Set, + arg4: this_is_fine + ): Set; +} +class CstVisitorImplementation extends AbstractCstVisitor { + Node(arg1: [some.really.long.generated.type.goes.here.you.know.this_.should.be.pretty.simple.Yah[], another.really.long.generated.type.goes.here.too.because.who.cares.about.space.do_.you.feel.me.Yah[]], arg2: [this_will_be_collapsed], arg3: any, arg4: this_is_fine) { + throw new Error("Method not implemented."); + } +}` +}); From 43811dd16f0d170d7eb56747cef08639d065147a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Dec 2018 12:13:13 -0800 Subject: [PATCH 035/120] Use getIndexedAccessType when computing destructured types --- src/compiler/checker.ts | 115 ++++++++------------------- src/compiler/diagnosticMessages.json | 12 --- 2 files changed, 35 insertions(+), 92 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index acf5125aa9b..aaec0192f3d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4827,21 +4827,13 @@ namespace ts { mapType(parentType, t => sliceTupleType(t, index)) : createArrayType(elementType); } + else if (isArrayLikeType(parentType)) { + const indexType = getLiteralType(index); + const declaredType = getIndexedAccessType(parentType, indexType, createSyntheticExpression(declaration.name, indexType)); + type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name)); + } else { - // Use specific property type when parent is a tuple or numeric index type when parent is an array - const index = pattern.elements.indexOf(declaration); - type = everyType(parentType, isTupleLikeType) ? - getTupleElementType(parentType, index) || declaration.initializer && checkDeclarationInitializer(declaration) : - elementType; - if (!type) { - if (isTupleType(parentType)) { - error(declaration, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(parentType), getTypeReferenceArity(parentType), pattern.elements.length); - } - else { - error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), "" + index); - } - return errorType; - } + type = elementType; } } // In strict null checking mode, if a default value of a non-undefined type is specified, remove @@ -9523,7 +9515,7 @@ namespace ts { return false; } - function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | undefined, cacheSymbol: boolean, missingType: Type) { + function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = isTypeUsableAsLateBoundName(indexType) ? getLateBoundNameFromType(indexType) @@ -9626,7 +9618,7 @@ namespace ts { return missingType; } - function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName) { + function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | SyntheticExpression) { return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.kind === SyntaxKind.IndexedAccessType @@ -9695,7 +9687,7 @@ namespace ts { return type.simplified = type; } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName, missingType = accessNode ? errorType : unknownType): Type { + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | SyntheticExpression, missingType = accessNode ? errorType : unknownType): Type { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } @@ -14628,7 +14620,7 @@ namespace ts { getAccessedPropertyName(source as PropertyAccessExpression | ElementAccessExpression) === getAccessedPropertyName(target) && isMatchingReference((source as PropertyAccessExpression | ElementAccessExpression).expression, target.expression); case SyntaxKind.BindingElement: - if (target.kind === SyntaxKind.PropertyAccessExpression && (target).name.escapedText === getBindingElementNameText(source)) { + if ((isPropertyAccessExpression(target) || isElementAccessExpression(target)) && getBindingElementNameText(source) === getAccessedPropertyName(target)) { const ancestor = source.parent.parent; if (ancestor.kind === SyntaxKind.BindingElement) { return isMatchingReference(ancestor, (target).expression); @@ -22010,21 +22002,17 @@ namespace ts { function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray, rightIsThis = false) { if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { const name = property.name; - if (name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(name); - } - if (isComputedNonLiteralName(name)) { - return undefined; - } - - const type = getTypeOfObjectLiteralDestructuringProperty(objectLiteralType, name, property, rightIsThis); - if (type) { - // non-shorthand property assignments should always have initializers - return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); - } - else { - error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(objectLiteralType), declarationNameToString(name)); + const text = getTextOfPropertyName(name); + if (text) { + const prop = getPropertyOfType(objectLiteralType, text); + if (prop) { + markPropertyAsReferenced(prop, property, rightIsThis); + checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop); + } } + const exprType = getLiteralTypeFromPropertyName(name); + const type = getIndexedAccessType(objectLiteralType, exprType, name); + return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } else if (property.kind === SyntaxKind.SpreadAssignment) { if (languageVersion < ScriptTarget.ESNext) { @@ -22045,31 +22033,11 @@ namespace ts { } } - function getTypeOfObjectLiteralDestructuringProperty(objectLiteralType: Type, name: PropertyName, property: PropertyAssignment | ShorthandPropertyAssignment, rightIsThis: boolean) { - if (isTypeAny(objectLiteralType)) { - return objectLiteralType; - } - - let type: Type | undefined; - const text = getTextOfPropertyName(name); - if (text) { // TODO: GH#26379 - const prop = getPropertyOfType(objectLiteralType, text); - if (prop) { - markPropertyAsReferenced(prop, property, rightIsThis); - checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop); - type = getTypeOfSymbol(prop); - } - type = type || (isNumericLiteralName(text) ? getIndexTypeOfType(objectLiteralType, IndexKind.Number) : undefined); - } - return type || getIndexTypeOfType(objectLiteralType, IndexKind.String); - } - function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { const elements = node.elements; if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } - // This elementType will be used if the specific property corresponding to this index is not // present (aka the tuple element property). This call also checks that the parentType is in // fact an iterable or array (depending on target language). @@ -22086,39 +22054,26 @@ namespace ts { const element = elements[elementIndex]; if (element.kind !== SyntaxKind.OmittedExpression) { if (element.kind !== SyntaxKind.SpreadElement) { - const propName = "" + elementIndex as __String; - const type = isTypeAny(sourceType) ? sourceType : - everyType(sourceType, isTupleLikeType) ? getTupleElementType(sourceType, elementIndex) : + const indexType = getLiteralType(elementIndex); + const type = isArrayLikeType(sourceType) ? + getIndexedAccessType(sourceType, indexType, createSyntheticExpression(element, indexType)) : elementType; - if (type) { - return checkDestructuringAssignment(element, type, checkMode); - } - // We still need to check element expression here because we may need to set appropriate flag on the expression - // such as NodeCheckFlags.LexicalThis on "this"expression. - checkExpression(element); - if (isTupleType(sourceType)) { - error(element, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(sourceType), getTypeReferenceArity(sourceType), elements.length); - } - else { - error(element, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName as string); - } + return checkDestructuringAssignment(element, type, checkMode); + } + if (elementIndex < elements.length - 1) { + error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } else { - if (elementIndex < elements.length - 1) { - error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + const restExpression = (element).expression; + if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + error((restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); } else { - const restExpression = (element).expression; - if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - error((restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); - } - else { - checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - const type = everyType(sourceType, isTupleType) ? - mapType(sourceType, t => sliceTupleType(t, elementIndex)) : - createArrayType(elementType); - return checkDestructuringAssignment(restExpression, type, checkMode); - } + checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + const type = everyType(sourceType, isTupleType) ? + mapType(sourceType, t => sliceTupleType(t, elementIndex)) : + createArrayType(elementType); + return checkDestructuringAssignment(restExpression, type, checkMode); } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2cbf5784d07..73ae0b69022 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1636,14 +1636,6 @@ "category": "Error", "code": 2458 }, - "Type '{0}' has no property '{1}' and no string index signature.": { - "category": "Error", - "code": 2459 - }, - "Type '{0}' has no property '{1}'.": { - "category": "Error", - "code": 2460 - }, "Type '{0}' is not an array type.": { "category": "Error", "code": 2461 @@ -1760,10 +1752,6 @@ "category": "Error", "code": 2492 }, - "Tuple type '{0}' with length '{1}' cannot be assigned to tuple with length '{2}'.": { - "category": "Error", - "code": 2493 - }, "Using a string in a 'for...of' statement is only supported in ECMAScript 5 and higher.": { "category": "Error", "code": 2494 From 57ed26409f04d8bafd89238c4d5f010d9d6c7650 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Dec 2018 12:16:24 -0800 Subject: [PATCH 036/120] Accept new baselines --- .../reference/ES5For-of31.errors.txt | 8 ++++---- .../arityAndOrderCompatibility01.errors.txt | 12 +++-------- .../arityAndOrderCompatibility01.types | 8 ++++---- ...putedPropertiesInDestructuring1.errors.txt | 20 ++++++++++++++++++- ...dPropertiesInDestructuring1_ES6.errors.txt | 20 ++++++++++++++++++- .../declarationsAndAssignments.types | 4 ++-- ...ingArrayBindingPatternAndAssignment2.types | 2 +- ...turingThisInTupleDestructuring1.errors.txt | 8 ++++---- ...turingThisInTupleDestructuring2.errors.txt | 4 ++-- .../reference/iterableArrayPattern21.types | 4 ++-- .../baselines/reference/objectRest.errors.txt | 8 +++++++- ...stElementWithAssignmentPattern2.errors.txt | 4 ++-- ...stElementWithAssignmentPattern4.errors.txt | 4 ++-- ...pertyAssignmentsInDestructuring.errors.txt | 8 ++++---- ...yAssignmentsInDestructuring_ES6.errors.txt | 8 ++++---- .../reference/unionsOfTupleTypes1.errors.txt | 16 +++++++-------- .../reference/unionsOfTupleTypes1.types | 12 +++++------ 17 files changed, 93 insertions(+), 57 deletions(-) diff --git a/tests/baselines/reference/ES5For-of31.errors.txt b/tests/baselines/reference/ES5For-of31.errors.txt index 4a49e1fdf92..4424a870071 100644 --- a/tests/baselines/reference/ES5For-of31.errors.txt +++ b/tests/baselines/reference/ES5For-of31.errors.txt @@ -1,5 +1,5 @@ -tests/cases/conformance/statements/for-ofStatements/ES5For-of31.ts(3,8): error TS2459: Type 'undefined' has no property 'a' and no string index signature. -tests/cases/conformance/statements/for-ofStatements/ES5For-of31.ts(3,18): error TS2459: Type 'undefined' has no property 'b' and no string index signature. +tests/cases/conformance/statements/for-ofStatements/ES5For-of31.ts(3,8): error TS2339: Property 'a' does not exist on type 'undefined'. +tests/cases/conformance/statements/for-ofStatements/ES5For-of31.ts(3,18): error TS2339: Property 'b' does not exist on type 'undefined'. ==== tests/cases/conformance/statements/for-ofStatements/ES5For-of31.ts (2 errors) ==== @@ -7,9 +7,9 @@ tests/cases/conformance/statements/for-ofStatements/ES5For-of31.ts(3,18): error for ({ a: b = 1, b: a = ""} of []) { ~ -!!! error TS2459: Type 'undefined' has no property 'a' and no string index signature. +!!! error TS2339: Property 'a' does not exist on type 'undefined'. ~ -!!! error TS2459: Type 'undefined' has no property 'b' and no string index signature. +!!! error TS2339: Property 'b' does not exist on type 'undefined'. a; b; } \ No newline at end of file diff --git a/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt b/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt index 50678ea4bb3..5eeaef51566 100644 --- a/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt +++ b/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt @@ -1,7 +1,5 @@ -tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(15,12): error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. -tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(16,12): error TS2460: Type 'StrNum' has no property '2'. +tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(15,12): error TS2339: Property '2' does not exist on type '[string, number]'. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(17,5): error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type. -tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(17,12): error TS2460: Type '{ 0: string; 1: number; length: 2; }' has no property '2'. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(18,5): error TS2741: Property '2' is missing in type '[string, number]' but required in type '[number, number, number]'. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(19,5): error TS2741: Property '2' is missing in type 'StrNum' but required in type '[number, number, number]'. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(20,5): error TS2740: Type '{ 0: string; 1: number; length: 2; }' is missing the following properties from type '[number, number, number]': 2, pop, push, concat, and 16 more. @@ -30,7 +28,7 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(31,5): error tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error TS2740: Type '{ 0: string; 1: number; length: 2; }' is missing the following properties from type '[number, string]': pop, push, concat, join, and 15 more. -==== tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts (19 errors) ==== +==== tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts (17 errors) ==== interface StrNum extends Array { 0: string; 1: number; @@ -47,15 +45,11 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error var [a, b, c] = x; ~ -!!! error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. +!!! error TS2339: Property '2' does not exist on type '[string, number]'. var [d, e, f] = y; - ~ -!!! error TS2460: Type 'StrNum' has no property '2'. var [g, h, i] = z; ~~~~~~~~~ !!! error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type. - ~ -!!! error TS2460: Type '{ 0: string; 1: number; length: 2; }' has no property '2'. var j1: [number, number, number] = x; ~~ !!! error TS2741: Property '2' is missing in type '[string, number]' but required in type '[number, number, number]'. diff --git a/tests/baselines/reference/arityAndOrderCompatibility01.types b/tests/baselines/reference/arityAndOrderCompatibility01.types index d7a0923d964..aa658ba7629 100644 --- a/tests/baselines/reference/arityAndOrderCompatibility01.types +++ b/tests/baselines/reference/arityAndOrderCompatibility01.types @@ -32,18 +32,18 @@ var z: { var [a, b, c] = x; >a : string >b : number ->c : any +>c : undefined >x : [string, number] var [d, e, f] = y; >d : string >e : number ->f : any +>f : string | number >y : StrNum var [g, h, i] = z; ->g : string ->h : number +>g : any +>h : any >i : any >z : { 0: string; 1: number; length: 2; } diff --git a/tests/baselines/reference/computedPropertiesInDestructuring1.errors.txt b/tests/baselines/reference/computedPropertiesInDestructuring1.errors.txt index 31b938fb3fb..dc71d916a36 100644 --- a/tests/baselines/reference/computedPropertiesInDestructuring1.errors.txt +++ b/tests/baselines/reference/computedPropertiesInDestructuring1.errors.txt @@ -10,11 +10,17 @@ tests/cases/compiler/computedPropertiesInDestructuring1.ts(20,8): error TS2349: tests/cases/compiler/computedPropertiesInDestructuring1.ts(20,8): error TS2538: Type 'any' cannot be used as an index type. tests/cases/compiler/computedPropertiesInDestructuring1.ts(21,8): error TS2538: Type 'any' cannot be used as an index type. tests/cases/compiler/computedPropertiesInDestructuring1.ts(21,12): error TS2339: Property 'toExponential' does not exist on type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1.ts(24,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1.ts(28,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1.ts(30,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1.ts(31,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. tests/cases/compiler/computedPropertiesInDestructuring1.ts(33,4): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures. +tests/cases/compiler/computedPropertiesInDestructuring1.ts(33,4): error TS2538: Type 'any' cannot be used as an index type. +tests/cases/compiler/computedPropertiesInDestructuring1.ts(34,4): error TS2538: Type 'any' cannot be used as an index type. tests/cases/compiler/computedPropertiesInDestructuring1.ts(34,5): error TS2365: Operator '+' cannot be applied to types '1' and '{}'. -==== tests/cases/compiler/computedPropertiesInDestructuring1.ts (14 errors) ==== +==== tests/cases/compiler/computedPropertiesInDestructuring1.ts (20 errors) ==== // destructuring in variable declarations let foo = "bar"; let {[foo]: bar} = {bar: "bar"}; @@ -63,18 +69,30 @@ tests/cases/compiler/computedPropertiesInDestructuring1.ts(34,5): error TS2365: // destructuring assignment ({[foo]: bar} = {bar: "bar"}); + ~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. ({["bar"]: bar2} = {bar: "bar"}); ({[foo2()]: bar3} = {bar: "bar"}); + ~~~~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. [{[foo]: bar4}] = [{bar: "bar"}]; + ~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. [{[foo2()]: bar5}] = [{bar: "bar"}]; + ~~~~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. [{[foo()]: bar4}] = [{bar: "bar"}]; ~~~~~ !!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures. + ~~~~~ +!!! error TS2538: Type 'any' cannot be used as an index type. [{[(1 + {})]: bar4}] = [{bar: "bar"}]; + ~~~~~~~~ +!!! error TS2538: Type 'any' cannot be used as an index type. ~~~~~~ !!! error TS2365: Operator '+' cannot be applied to types '1' and '{}'. diff --git a/tests/baselines/reference/computedPropertiesInDestructuring1_ES6.errors.txt b/tests/baselines/reference/computedPropertiesInDestructuring1_ES6.errors.txt index 610b8e56ab3..b839a209743 100644 --- a/tests/baselines/reference/computedPropertiesInDestructuring1_ES6.errors.txt +++ b/tests/baselines/reference/computedPropertiesInDestructuring1_ES6.errors.txt @@ -10,11 +10,17 @@ tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(21,8): error TS23 tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(21,8): error TS2538: Type 'any' cannot be used as an index type. tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(22,8): error TS2538: Type 'any' cannot be used as an index type. tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(22,12): error TS2339: Property 'toExponential' does not exist on type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(25,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(29,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(31,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. +tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(32,4): error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(34,4): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures. +tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(34,4): error TS2538: Type 'any' cannot be used as an index type. +tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(35,4): error TS2538: Type 'any' cannot be used as an index type. tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(35,5): error TS2365: Operator '+' cannot be applied to types '1' and '{}'. -==== tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts (14 errors) ==== +==== tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts (20 errors) ==== // destructuring in variable declarations let foo = "bar"; let {[foo]: bar} = {bar: "bar"}; @@ -64,18 +70,30 @@ tests/cases/compiler/computedPropertiesInDestructuring1_ES6.ts(35,5): error TS23 // destructuring assignment ({[foo]: bar} = {bar: "bar"}); + ~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. ({["bar"]: bar2} = {bar: "bar"}); ({[foo2()]: bar3} = {bar: "bar"}); + ~~~~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. [{[foo]: bar4}] = [{bar: "bar"}]; + ~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. [{[foo2()]: bar5}] = [{bar: "bar"}]; + ~~~~~~ +!!! error TS2537: Type '{ bar: string; }' has no matching index signature for type 'string'. [{[foo()]: bar4}] = [{bar: "bar"}]; ~~~~~ !!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'String' has no compatible call signatures. + ~~~~~ +!!! error TS2538: Type 'any' cannot be used as an index type. [{[(1 + {})]: bar4}] = [{bar: "bar"}]; + ~~~~~~~~ +!!! error TS2538: Type 'any' cannot be used as an index type. ~~~~~~ !!! error TS2365: Operator '+' cannot be applied to types '1' and '{}'. \ No newline at end of file diff --git a/tests/baselines/reference/declarationsAndAssignments.types b/tests/baselines/reference/declarationsAndAssignments.types index edb00c9879b..89ddd0eb5e5 100644 --- a/tests/baselines/reference/declarationsAndAssignments.types +++ b/tests/baselines/reference/declarationsAndAssignments.types @@ -277,8 +277,8 @@ function f9() { >{} : {} var [c, d] = { 0: 10, 1: 20 }; // Error, not array type ->c : number ->d : number +>c : any +>d : any >{ 0: 10, 1: 20 } : { 0: number; 1: number; } >0 : number >10 : 10 diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment2.types b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment2.types index aa4f3a73b09..77681f8d8c8 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment2.types +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment2.types @@ -42,7 +42,7 @@ var [b3 = "string", b4, b5] = bar(); // Error >b3 : string | Number >"string" : "string" >b4 : Number ->b5 : Number +>b5 : number >bar() : J >bar : () => J diff --git a/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt b/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt index d0f11d056ca..8fba60b9cd0 100644 --- a/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt +++ b/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,17): error TS2493: Tuple type '[any]' with length '1' cannot be assigned to tuple with length '3'. -tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,29): error TS2493: Tuple type '[any]' with length '1' cannot be assigned to tuple with length '3'. +tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,17): error TS2339: Property '1' does not exist on type '[any]'. +tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,29): error TS2339: Property '2' does not exist on type '[any]'. ==== tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts (2 errors) ==== @@ -7,7 +7,7 @@ tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,29): error TS24 wrapper((array: [any]) => { [this.test, this.test1, this.test2] = array; // even though there is a compiler error, we should still emit lexical capture for "this" ~~~~~~~~~~ -!!! error TS2493: Tuple type '[any]' with length '1' cannot be assigned to tuple with length '3'. +!!! error TS2339: Property '1' does not exist on type '[any]'. ~~~~~~~~~~ -!!! error TS2493: Tuple type '[any]' with length '1' cannot be assigned to tuple with length '3'. +!!! error TS2339: Property '2' does not exist on type '[any]'. }); \ No newline at end of file diff --git a/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt b/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt index 25207e28c79..22a8e1ca63e 100644 --- a/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt +++ b/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts(8,39): error TS2493: Tuple type '[number, number]' with length '2' cannot be assigned to tuple with length '3'. +tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts(8,39): error TS2339: Property '2' does not exist on type '[number, number]'. ==== tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts (1 errors) ==== @@ -11,6 +11,6 @@ tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts(8,39): error TS24 method() { () => [this.test, this.test1, this.test2] = array1; // even though there is a compiler error, we should still emit lexical capture for "this" ~~~~~~~~~~ -!!! error TS2493: Tuple type '[number, number]' with length '2' cannot be assigned to tuple with length '3'. +!!! error TS2339: Property '2' does not exist on type '[number, number]'. } } \ No newline at end of file diff --git a/tests/baselines/reference/iterableArrayPattern21.types b/tests/baselines/reference/iterableArrayPattern21.types index aefaec14194..beaa3c48d9d 100644 --- a/tests/baselines/reference/iterableArrayPattern21.types +++ b/tests/baselines/reference/iterableArrayPattern21.types @@ -1,7 +1,7 @@ === tests/cases/conformance/es6/destructuring/iterableArrayPattern21.ts === var [a, b] = { 0: "", 1: true }; ->a : string ->b : boolean +>a : any +>b : any >{ 0: "", 1: true } : { 0: string; 1: boolean; } >0 : string >"" : "" diff --git a/tests/baselines/reference/objectRest.errors.txt b/tests/baselines/reference/objectRest.errors.txt index 0bbd4b2f84d..02f64fbd4e2 100644 --- a/tests/baselines/reference/objectRest.errors.txt +++ b/tests/baselines/reference/objectRest.errors.txt @@ -3,10 +3,12 @@ tests/cases/conformance/types/rest/objectRest.ts(7,20): error TS2339: Property ' tests/cases/conformance/types/rest/objectRest.ts(43,8): error TS2537: Type '{ a: number; b: string; }' has no matching index signature for type 'string'. tests/cases/conformance/types/rest/objectRest.ts(43,35): error TS2537: Type '{ a: number; b: string; }' has no matching index signature for type 'string'. tests/cases/conformance/types/rest/objectRest.ts(43,57): error TS2403: Subsequent variable declarations must have the same type. Variable 'o' must be of type '{ a: number; b: string; }', but here has type '{}'. +tests/cases/conformance/types/rest/objectRest.ts(44,5): error TS2537: Type '{ a: number; b: string; }' has no matching index signature for type 'string'. +tests/cases/conformance/types/rest/objectRest.ts(44,32): error TS2537: Type '{ a: number; b: string; }' has no matching index signature for type 'string'. tests/cases/conformance/types/rest/objectRest.ts(44,53): error TS2739: Type '{}' is missing the following properties from type '{ a: number; b: string; }': a, b -==== tests/cases/conformance/types/rest/objectRest.ts (6 errors) ==== +==== tests/cases/conformance/types/rest/objectRest.ts (8 errors) ==== var o = { a: 1, b: 'no' } var { ...clone } = o; var { a, ...justB } = o; @@ -61,6 +63,10 @@ tests/cases/conformance/types/rest/objectRest.ts(44,53): error TS2739: Type '{}' ~ !!! error TS2403: Subsequent variable declarations must have the same type. Variable 'o' must be of type '{ a: number; b: string; }', but here has type '{}'. ({ [computed]: stillNotGreat, [computed2]: soSo, ...o } = o); + ~~~~~~~~ +!!! error TS2537: Type '{ a: number; b: string; }' has no matching index signature for type 'string'. + ~~~~~~~~~ +!!! error TS2537: Type '{ a: number; b: string; }' has no matching index signature for type 'string'. ~ !!! error TS2739: Type '{}' is missing the following properties from type '{ a: number; b: string; }': a, b diff --git a/tests/baselines/reference/restElementWithAssignmentPattern2.errors.txt b/tests/baselines/reference/restElementWithAssignmentPattern2.errors.txt index f9b11b5694c..595e047e4e8 100644 --- a/tests/baselines/reference/restElementWithAssignmentPattern2.errors.txt +++ b/tests/baselines/reference/restElementWithAssignmentPattern2.errors.txt @@ -1,6 +1,6 @@ tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern2.ts(2,10): error TS2322: Type 'string | number' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'. -tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern2.ts(2,18): error TS2459: Type '(string | number)[]' has no property 'b' and no string index signature. +tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern2.ts(2,18): error TS2339: Property 'b' does not exist on type '(string | number)[]'. ==== tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern2.ts (2 errors) ==== @@ -10,4 +10,4 @@ tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern2.ts(2 !!! error TS2322: Type 'string | number' is not assignable to type 'string'. !!! error TS2322: Type 'number' is not assignable to type 'string'. ~ -!!! error TS2459: Type '(string | number)[]' has no property 'b' and no string index signature. \ No newline at end of file +!!! error TS2339: Property 'b' does not exist on type '(string | number)[]'. \ No newline at end of file diff --git a/tests/baselines/reference/restElementWithAssignmentPattern4.errors.txt b/tests/baselines/reference/restElementWithAssignmentPattern4.errors.txt index f455d3fc04a..5a4636cf12c 100644 --- a/tests/baselines/reference/restElementWithAssignmentPattern4.errors.txt +++ b/tests/baselines/reference/restElementWithAssignmentPattern4.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern4.ts(3,18): error TS2459: Type '[string, number]' has no property 'b' and no string index signature. +tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern4.ts(3,18): error TS2339: Property 'b' does not exist on type '[string, number]'. ==== tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern4.ts (1 errors) ==== @@ -6,4 +6,4 @@ tests/cases/conformance/es6/destructuring/restElementWithAssignmentPattern4.ts(3 var tuple: [string, number] = ["", 1]; [...{ 0: a = "", b }] = tuple; ~ -!!! error TS2459: Type '[string, number]' has no property 'b' and no string index signature. \ No newline at end of file +!!! error TS2339: Property 'b' does not exist on type '[string, number]'. \ No newline at end of file diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.errors.txt b/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.errors.txt index 67ecf208561..7d2ebd38c35 100644 --- a/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.errors.txt +++ b/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(14,9): error TS2459: Type '{}' has no property 's1' and no string index signature. -tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(20,9): error TS2459: Type '{}' has no property 's1' and no string index signature. +tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(14,9): error TS2339: Property 's1' does not exist on type '{}'. +tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(20,9): error TS2339: Property 's1' does not exist on type '{}'. tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(38,9): error TS2322: Type '5' is not assignable to type 'string'. tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(44,12): error TS2322: Type '5' is not assignable to type 'string'. tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(70,5): error TS2322: Type '5' is not assignable to type 'string'. @@ -33,7 +33,7 @@ tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(111,14): err var s1; for ({ s1 = 5 } of [{}]) { ~~ -!!! error TS2459: Type '{}' has no property 's1' and no string index signature. +!!! error TS2339: Property 's1' does not exist on type '{}'. } }); @@ -41,7 +41,7 @@ tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring.ts(111,14): err var s1; for ({ s1:s1 = 5 } of [{}]) { ~~ -!!! error TS2459: Type '{}' has no property 's1' and no string index signature. +!!! error TS2339: Property 's1' does not exist on type '{}'. } }); diff --git a/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring_ES6.errors.txt b/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring_ES6.errors.txt index c5937c9d6c4..fa821aa51b6 100644 --- a/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring_ES6.errors.txt +++ b/tests/baselines/reference/shorthandPropertyAssignmentsInDestructuring_ES6.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(14,9): error TS2459: Type '{}' has no property 's1' and no string index signature. -tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(20,9): error TS2459: Type '{}' has no property 's1' and no string index signature. +tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(14,9): error TS2339: Property 's1' does not exist on type '{}'. +tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(20,9): error TS2339: Property 's1' does not exist on type '{}'. tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(38,9): error TS2322: Type '5' is not assignable to type 'string'. tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(44,12): error TS2322: Type '5' is not assignable to type 'string'. tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(70,5): error TS2322: Type '5' is not assignable to type 'string'. @@ -33,7 +33,7 @@ tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(111,14): var s1; for ({ s1 = 5 } of [{}]) { ~~ -!!! error TS2459: Type '{}' has no property 's1' and no string index signature. +!!! error TS2339: Property 's1' does not exist on type '{}'. } }); @@ -41,7 +41,7 @@ tests/cases/compiler/shorthandPropertyAssignmentsInDestructuring_ES6.ts(111,14): var s1; for ({ s1:s1 = 5 } of [{}]) { ~~ -!!! error TS2459: Type '{}' has no property 's1' and no string index signature. +!!! error TS2339: Property 's1' does not exist on type '{}'. } }); diff --git a/tests/baselines/reference/unionsOfTupleTypes1.errors.txt b/tests/baselines/reference/unionsOfTupleTypes1.errors.txt index d9cad5f1b11..402dfcad340 100644 --- a/tests/baselines/reference/unionsOfTupleTypes1.errors.txt +++ b/tests/baselines/reference/unionsOfTupleTypes1.errors.txt @@ -1,9 +1,9 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(8,15): error TS2339: Property '2' does not exist on type '[string, number]'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(13,15): error TS2339: Property '2' does not exist on type 'T2'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(27,20): error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(28,20): error TS2460: Type 'T2' has no property '2'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(31,16): error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(32,16): error TS2460: Type 'T2' has no property '2'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(27,20): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(28,20): error TS2339: Property '2' does not exist on type 'T2'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(31,16): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(32,16): error TS2339: Property '2' does not exist on type 'T2'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(37,18): error TS2339: Property '2' does not exist on type '[string, number]'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: Property '2' does not exist on type 'T2'. @@ -41,18 +41,18 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: function f1(t1: T1, t2: T2, t3: T3, t4: T4, x: number) { let [d10, d11, d12] = t1; // string, number ~~~ -!!! error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. +!!! error TS2339: Property '2' does not exist on type '[string, number]'. let [d20, d21, d22] = t2; // string | boolean, number | undefined ~~~ -!!! error TS2460: Type 'T2' has no property '2'. +!!! error TS2339: Property '2' does not exist on type 'T2'. let [d30, d31, d32] = t3; // string, number, number let [d40, d41, d42] = t4; // string | boolean, number | undefined, number | undefined [d10, d11, d12] = t1; ~~~ -!!! error TS2493: Tuple type '[string, number]' with length '2' cannot be assigned to tuple with length '3'. +!!! error TS2339: Property '2' does not exist on type '[string, number]'. [d20, d21, d22] = t2; ~~~ -!!! error TS2460: Type 'T2' has no property '2'. +!!! error TS2339: Property '2' does not exist on type 'T2'. [d30, d31, d32] = t3; [d40, d41, d42] = t4; let t10 = t1[0]; // string diff --git a/tests/baselines/reference/unionsOfTupleTypes1.types b/tests/baselines/reference/unionsOfTupleTypes1.types index e44114f5a6a..e0867cd1045 100644 --- a/tests/baselines/reference/unionsOfTupleTypes1.types +++ b/tests/baselines/reference/unionsOfTupleTypes1.types @@ -70,13 +70,13 @@ function f1(t1: T1, t2: T2, t3: T3, t4: T4, x: number) { let [d10, d11, d12] = t1; // string, number >d10 : string >d11 : number ->d12 : any +>d12 : undefined >t1 : [string, number] let [d20, d21, d22] = t2; // string | boolean, number | undefined >d20 : string | boolean >d21 : number | undefined ->d22 : any +>d22 : undefined >t2 : T2 let [d30, d31, d32] = t3; // string, number, number @@ -93,18 +93,18 @@ function f1(t1: T1, t2: T2, t3: T3, t4: T4, x: number) { [d10, d11, d12] = t1; >[d10, d11, d12] = t1 : [string, number] ->[d10, d11, d12] : [string, number, any] +>[d10, d11, d12] : [string, number, undefined] >d10 : string >d11 : number ->d12 : any +>d12 : undefined >t1 : [string, number] [d20, d21, d22] = t2; >[d20, d21, d22] = t2 : T2 ->[d20, d21, d22] : [string | boolean, number | undefined, any] +>[d20, d21, d22] : [string | boolean, number | undefined, undefined] >d20 : string | boolean >d21 : number | undefined ->d22 : any +>d22 : undefined >t2 : T2 [d30, d31, d32] = t3; From 99c7fc4a88660ecb6b364e74be459807672f8170 Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Fri, 14 Dec 2018 21:23:46 +0100 Subject: [PATCH 037/120] #28977 Change const enum error message --- src/compiler/checker.ts | 2 +- src/compiler/diagnosticMessages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 356ddc2a28e..140575b9436 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26903,7 +26903,7 @@ namespace ts { return 0; } else if (isConstEnum) { - error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression); + error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_const_enums_values); } else if (member.parent.flags & NodeFlags.Ambient) { error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2cbf5784d07..89498f32d42 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1696,7 +1696,7 @@ "category": "Error", "code": 2473 }, - "In 'const' enum declarations member initializer must be constant expression.": { + "const enum member initializers can only contain literal values and other const enums values.": { "category": "Error", "code": 2474 }, From aadc2cd5f78f589802f4529d26ed44628e02dff2 Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Fri, 14 Dec 2018 21:24:04 +0100 Subject: [PATCH 038/120] #28977 Fix test baselines --- tests/baselines/reference/constEnum2.errors.txt | 12 ++++++------ tests/baselines/reference/constEnumErrors.errors.txt | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/baselines/reference/constEnum2.errors.txt b/tests/baselines/reference/constEnum2.errors.txt index cfec4d9055b..536626daf65 100644 --- a/tests/baselines/reference/constEnum2.errors.txt +++ b/tests/baselines/reference/constEnum2.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/constEnums/constEnum2.ts(10,9): error TS2474: In 'const' enum declarations member initializer must be constant expression. -tests/cases/conformance/constEnums/constEnum2.ts(11,9): error TS2474: In 'const' enum declarations member initializer must be constant expression. +tests/cases/conformance/constEnums/constEnum2.ts(10,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. +tests/cases/conformance/constEnums/constEnum2.ts(11,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. tests/cases/conformance/constEnums/constEnum2.ts(12,5): error TS1005: ',' expected. -tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: In 'const' enum declarations member initializer must be constant expression. +tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. ==== tests/cases/conformance/constEnums/constEnum2.ts (4 errors) ==== @@ -16,13 +16,13 @@ tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: In 'const' d = 10, e = 199 * Math.floor(Math.random() * 1000), ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2474: In 'const' enum declarations member initializer must be constant expression. +!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. f = d - (100 * Math.floor(Math.random() % 8)) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2474: In 'const' enum declarations member initializer must be constant expression. +!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. g = CONST, ~ !!! error TS1005: ',' expected. ~~~~~ -!!! error TS2474: In 'const' enum declarations member initializer must be constant expression. +!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. } \ No newline at end of file diff --git a/tests/baselines/reference/constEnumErrors.errors.txt b/tests/baselines/reference/constEnumErrors.errors.txt index 3763a218336..07ae77cf926 100644 --- a/tests/baselines/reference/constEnumErrors.errors.txt +++ b/tests/baselines/reference/constEnumErrors.errors.txt @@ -1,8 +1,8 @@ tests/cases/compiler/constEnumErrors.ts(1,12): error TS2567: Enum declarations can only merge with namespace or other enum declarations. tests/cases/compiler/constEnumErrors.ts(5,8): error TS2567: Enum declarations can only merge with namespace or other enum declarations. tests/cases/compiler/constEnumErrors.ts(12,9): error TS2651: A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums. -tests/cases/compiler/constEnumErrors.ts(14,9): error TS2474: In 'const' enum declarations member initializer must be constant expression. -tests/cases/compiler/constEnumErrors.ts(15,10): error TS2474: In 'const' enum declarations member initializer must be constant expression. +tests/cases/compiler/constEnumErrors.ts(14,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. +tests/cases/compiler/constEnumErrors.ts(15,10): error TS2474: const enum member initializers can only contain literal values and other const enums values. tests/cases/compiler/constEnumErrors.ts(22,13): error TS2476: A const enum member can only be accessed using a string literal. tests/cases/compiler/constEnumErrors.ts(24,13): error TS2476: A const enum member can only be accessed using a string literal. tests/cases/compiler/constEnumErrors.ts(26,9): error TS2475: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query. @@ -35,10 +35,10 @@ tests/cases/compiler/constEnumErrors.ts(42,9): error TS2478: 'const' enum member // forward reference to the element of the same enum Y = E1.Z, ~~~~ -!!! error TS2474: In 'const' enum declarations member initializer must be constant expression. +!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. Y1 = E1["Z"] ~~~~~~~ -!!! error TS2474: In 'const' enum declarations member initializer must be constant expression. +!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. } const enum E2 { From 1a5ecbb6ac1f722fe2026adc79e74427eab946b6 Mon Sep 17 00:00:00 2001 From: Ali Sabzevari Date: Fri, 14 Dec 2018 23:42:59 +0100 Subject: [PATCH 039/120] #28977 Change error message according to review comments --- src/compiler/checker.ts | 2 +- src/compiler/diagnosticMessages.json | 2 +- tests/baselines/reference/constEnum2.errors.txt | 12 ++++++------ tests/baselines/reference/constEnumErrors.errors.txt | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 140575b9436..81f838e6ee5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26903,7 +26903,7 @@ namespace ts { return 0; } else if (isConstEnum) { - error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_const_enums_values); + error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values); } else if (member.parent.flags & NodeFlags.Ambient) { error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 89498f32d42..4abdf2b0bb7 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1696,7 +1696,7 @@ "category": "Error", "code": 2473 }, - "const enum member initializers can only contain literal values and other const enums values.": { + "const enum member initializers can only contain literal values and other computed enum values.": { "category": "Error", "code": 2474 }, diff --git a/tests/baselines/reference/constEnum2.errors.txt b/tests/baselines/reference/constEnum2.errors.txt index 536626daf65..4748b1840b8 100644 --- a/tests/baselines/reference/constEnum2.errors.txt +++ b/tests/baselines/reference/constEnum2.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/constEnums/constEnum2.ts(10,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. -tests/cases/conformance/constEnums/constEnum2.ts(11,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. +tests/cases/conformance/constEnums/constEnum2.ts(10,9): error TS2474: const enum member initializers can only contain literal values and other computed enum values. +tests/cases/conformance/constEnums/constEnum2.ts(11,9): error TS2474: const enum member initializers can only contain literal values and other computed enum values. tests/cases/conformance/constEnums/constEnum2.ts(12,5): error TS1005: ',' expected. -tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. +tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: const enum member initializers can only contain literal values and other computed enum values. ==== tests/cases/conformance/constEnums/constEnum2.ts (4 errors) ==== @@ -16,13 +16,13 @@ tests/cases/conformance/constEnums/constEnum2.ts(12,9): error TS2474: const enum d = 10, e = 199 * Math.floor(Math.random() * 1000), ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. +!!! error TS2474: const enum member initializers can only contain literal values and other computed enum values. f = d - (100 * Math.floor(Math.random() % 8)) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. +!!! error TS2474: const enum member initializers can only contain literal values and other computed enum values. g = CONST, ~ !!! error TS1005: ',' expected. ~~~~~ -!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. +!!! error TS2474: const enum member initializers can only contain literal values and other computed enum values. } \ No newline at end of file diff --git a/tests/baselines/reference/constEnumErrors.errors.txt b/tests/baselines/reference/constEnumErrors.errors.txt index 07ae77cf926..0d278197c9b 100644 --- a/tests/baselines/reference/constEnumErrors.errors.txt +++ b/tests/baselines/reference/constEnumErrors.errors.txt @@ -1,8 +1,8 @@ tests/cases/compiler/constEnumErrors.ts(1,12): error TS2567: Enum declarations can only merge with namespace or other enum declarations. tests/cases/compiler/constEnumErrors.ts(5,8): error TS2567: Enum declarations can only merge with namespace or other enum declarations. tests/cases/compiler/constEnumErrors.ts(12,9): error TS2651: A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums. -tests/cases/compiler/constEnumErrors.ts(14,9): error TS2474: const enum member initializers can only contain literal values and other const enums values. -tests/cases/compiler/constEnumErrors.ts(15,10): error TS2474: const enum member initializers can only contain literal values and other const enums values. +tests/cases/compiler/constEnumErrors.ts(14,9): error TS2474: const enum member initializers can only contain literal values and other computed enum values. +tests/cases/compiler/constEnumErrors.ts(15,10): error TS2474: const enum member initializers can only contain literal values and other computed enum values. tests/cases/compiler/constEnumErrors.ts(22,13): error TS2476: A const enum member can only be accessed using a string literal. tests/cases/compiler/constEnumErrors.ts(24,13): error TS2476: A const enum member can only be accessed using a string literal. tests/cases/compiler/constEnumErrors.ts(26,9): error TS2475: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query. @@ -35,10 +35,10 @@ tests/cases/compiler/constEnumErrors.ts(42,9): error TS2478: 'const' enum member // forward reference to the element of the same enum Y = E1.Z, ~~~~ -!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. +!!! error TS2474: const enum member initializers can only contain literal values and other computed enum values. Y1 = E1["Z"] ~~~~~~~ -!!! error TS2474: const enum member initializers can only contain literal values and other const enums values. +!!! error TS2474: const enum member initializers can only contain literal values and other computed enum values. } const enum E2 { From 92f47a7a7b83a8bc761e4be17426066d4c8ec3d4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 15 Dec 2018 16:49:53 -0800 Subject: [PATCH 040/120] Use synthetic access expressions for destructuring control flow analysis --- src/compiler/checker.ts | 256 ++++++++++++++++++++------------------ src/compiler/types.ts | 2 + src/compiler/utilities.ts | 4 + 3 files changed, 144 insertions(+), 118 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aaec0192f3d..17c10c01235 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4768,6 +4768,69 @@ namespace ts { return createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); } + // Determine the control flow type associated with a destructuring declaration or assignment. The following + // forms of destructuring are possible: + // let { x } = obj; // BindingElement + // let [ x ] = obj; // BindingElement + // { x } = obj; // ShorthandPropertyAssignment + // { x: v } = obj; // PropertyAssignment + // [ x ] = obj; // Expression + // We construct a synthetic element access expression corresponding to 'obj.x' such that the control + // flow analyzer doesn't have to handle all the different syntactic forms. + function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { + const reference = getSyntheticElementAccess(node); + return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; + } + + function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { + const parentAccess = getParentElementAccess(node); + if (parentAccess && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const result = createNode(SyntaxKind.ElementAccessExpression, node.pos, node.end); + result.parent = node; + result.expression = parentAccess; + const literal = createNode(SyntaxKind.StringLiteral, node.pos, node.end); + literal.parent = result; + literal.text = propName; + result.argumentExpression = literal; + result.flowNode = parentAccess.flowNode; + return result; + } + } + } + + function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const ancestor = node.parent.parent; + switch (ancestor.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + return getSyntheticElementAccess(ancestor); + case SyntaxKind.ArrayLiteralExpression: + return getSyntheticElementAccess(node.parent); + case SyntaxKind.VariableDeclaration: + return (ancestor).initializer; + case SyntaxKind.BinaryExpression: + return (ancestor).right; + } + } + + function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const parent = node.parent; + if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { + return getLiteralPropertyNameText((node).propertyName || (node).name); + } + if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { + return getLiteralPropertyNameText((node).name); + } + return "" + (>(parent).elements).indexOf(node); + } + + function getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type).value : undefined; + } + /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type | undefined { const pattern = declaration.parent; @@ -4808,9 +4871,9 @@ namespace ts { else { // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) const name = declaration.propertyName || declaration.name; - const exprType = getLiteralTypeFromPropertyName(name); - const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(parentType, exprType, name), name); - type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name)); + const indexType = getLiteralTypeFromPropertyName(name); + const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, name), declaration.name); + type = getFlowTypeOfDestructuring(declaration, declaredType); } } else { @@ -4829,8 +4892,8 @@ namespace ts { } else if (isArrayLikeType(parentType)) { const indexType = getLiteralType(index); - const declaredType = getIndexedAccessType(parentType, indexType, createSyntheticExpression(declaration.name, indexType)); - type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name)); + const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, declaration.name), declaration.name); + type = getFlowTypeOfDestructuring(declaration, declaredType); } else { type = elementType; @@ -9515,16 +9578,16 @@ namespace ts { return false; } - function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) { + function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - const propName = isTypeUsableAsLateBoundName(indexType) - ? getLateBoundNameFromType(indexType) - : accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) - ? getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) - : accessNode && isPropertyName(accessNode) + const propName = isTypeUsableAsLateBoundName(indexType) ? + getLateBoundNameFromType(indexType) : + accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ? + getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : + accessNode && isPropertyName(accessNode) ? // late bound names are handled in the first branch, so here we only need to handle normal names - ? getPropertyNameForPropertyNameNode(accessNode) - : undefined; + getPropertyNameForPropertyNameNode(accessNode) : + undefined; if (propName !== undefined) { const prop = getPropertyOfType(objectType, propName); if (prop) { @@ -9618,7 +9681,7 @@ namespace ts { return missingType; } - function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | SyntheticExpression) { + function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.kind === SyntaxKind.IndexedAccessType @@ -9687,7 +9750,7 @@ namespace ts { return type.simplified = type; } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | SyntheticExpression, missingType = accessNode ? errorType : unknownType): Type { + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, missingType = accessNode ? errorType : unknownType): Type { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } @@ -14561,49 +14624,23 @@ namespace ts { // occurring in an apparent type position with '@' because the control flow type // of such nodes may be based on the apparent type instead of the declared type. function getFlowCacheKey(node: Node): string | undefined { - if (node.kind === SyntaxKind.Identifier) { - const symbol = getResolvedSymbol(node); - return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined; - } - if (node.kind === SyntaxKind.ThisKeyword) { - return "0"; - } - if (node.kind === SyntaxKind.PropertyAccessExpression) { - const key = getFlowCacheKey((node).expression); - return key && key + "." + idText((node).name); - } - if (node.kind === SyntaxKind.BindingElement) { - const container = (node as BindingElement).parent.parent; - const key = container.kind === SyntaxKind.BindingElement ? getFlowCacheKey(container) : (container.initializer && getFlowCacheKey(container.initializer)); - const text = getBindingElementNameText(node as BindingElement); - const result = key && text && (key + "." + text); - return result; + switch (node.kind) { + case SyntaxKind.Identifier: + const symbol = getResolvedSymbol(node); + return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined; + case SyntaxKind.ThisKeyword: + return "0"; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const propName = getAccessedPropertyName(node); + if (propName) { + const key = getFlowCacheKey((node).expression); + return key && key + "." + propName; + } } return undefined; } - function getBindingElementNameText(element: BindingElement): string | undefined { - const parent = element.parent; - if (parent.kind === SyntaxKind.ObjectBindingPattern) { - const name = element.propertyName || element.name; - switch (name.kind) { - case SyntaxKind.Identifier: - return idText(name); - case SyntaxKind.ComputedPropertyName: - return isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined; - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - return name.text; - default: - // Per types, array and object binding patterns remain, however they should never be present if propertyName is not defined - Debug.fail("Unexpected name kind for binding element name"); - } - } - else { - return "" + parent.elements.indexOf(element); - } - } - function isMatchingReference(source: Node, target: Node): boolean { switch (source.kind) { case SyntaxKind.Identifier: @@ -14616,49 +14653,27 @@ namespace ts { return target.kind === SyntaxKind.SuperKeyword; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: - return (isPropertyAccessExpression(target) || isElementAccessExpression(target)) && - getAccessedPropertyName(source as PropertyAccessExpression | ElementAccessExpression) === getAccessedPropertyName(target) && - isMatchingReference((source as PropertyAccessExpression | ElementAccessExpression).expression, target.expression); - case SyntaxKind.BindingElement: - if ((isPropertyAccessExpression(target) || isElementAccessExpression(target)) && getBindingElementNameText(source) === getAccessedPropertyName(target)) { - const ancestor = source.parent.parent; - if (ancestor.kind === SyntaxKind.BindingElement) { - return isMatchingReference(ancestor, (target).expression); - } - if (ancestor.kind === SyntaxKind.VariableDeclaration) { - const initializer = (ancestor).initializer; - return !!initializer && isMatchingReference(initializer, (target).expression); - } - } + return isAccessExpression(target) && + getAccessedPropertyName(source) === getAccessedPropertyName(target) && + isMatchingReference((source).expression, target.expression); } return false; } - function getAccessedPropertyName(access: PropertyAccessExpression | ElementAccessExpression): __String | undefined { - return isPropertyAccessExpression(access) ? access.name.escapedText : + function getAccessedPropertyName(access: AccessExpression): __String | undefined { + return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : undefined; } - function getReferenceParent(source: Node) { - if (source.kind === SyntaxKind.PropertyAccessExpression) { - return (source).expression; - } - if (source.kind === SyntaxKind.BindingElement) { - const ancestor = source.parent.parent; - return ancestor.kind === SyntaxKind.VariableDeclaration ? (ancestor).initializer : ancestor; - } - return undefined; - } - function containsMatchingReference(source: Node, target: Node) { - let parent = getReferenceParent(source); - while (parent) { - if (isMatchingReference(parent, target)) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { return true; } - parent = getReferenceParent(parent); } + return false; } // Return true if target is a property access xxx.yyy, source is a property access xxx.zzz, the declared @@ -14666,18 +14681,21 @@ namespace ts { // a possible discriminant if its type differs in the constituents of containing union type, and if every // choice is a unit type or a union of unit types. function containsMatchingReferenceDiscriminant(source: Node, target: Node) { - return target.kind === SyntaxKind.PropertyAccessExpression && - containsMatchingReference(source, (target).expression) && - isDiscriminantProperty(getDeclaredTypeOfReference((target).expression), (target).name.escapedText); + return isAccessExpression(target) && + containsMatchingReference(source, target.expression) && + isDiscriminantProperty(getDeclaredTypeOfReference(target.expression), getAccessedPropertyName(target)); } function getDeclaredTypeOfReference(expr: Node): Type | undefined { if (expr.kind === SyntaxKind.Identifier) { return getTypeOfSymbol(getResolvedSymbol(expr)); } - if (expr.kind === SyntaxKind.PropertyAccessExpression) { - const type = getDeclaredTypeOfReference((expr).expression); - return type && getTypeOfPropertyOfType(type, (expr).name.escapedText); + if (isAccessExpression(expr)) { + const type = getDeclaredTypeOfReference(expr.expression); + if (type) { + const propName = getAccessedPropertyName(expr); + return propName && getTypeOfPropertyOfType(type, propName); + } } return undefined; } @@ -14696,8 +14714,8 @@ namespace ts { return false; } - function isDiscriminantProperty(type: Type | undefined, name: __String) { - if (type && type.flags & TypeFlags.Union) { + function isDiscriminantProperty(type: Type | undefined, name: __String | undefined) { + if (type && name && type.flags & TypeFlags.Union) { const prop = getUnionOrIntersectionProperty(type, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { @@ -15503,7 +15521,7 @@ namespace ts { else if (isMatchingReferenceDiscriminant(expr, type)) { type = narrowTypeByDiscriminant( type, - expr as PropertyAccessExpression | ElementAccessExpression, + expr as AccessExpression, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); } else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { @@ -15623,19 +15641,17 @@ namespace ts { } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { - if (!(computedType.flags & TypeFlags.Union) || - expr.kind !== SyntaxKind.PropertyAccessExpression && expr.kind !== SyntaxKind.ElementAccessExpression) { + if (!(computedType.flags & TypeFlags.Union) || !isAccessExpression(expr)) { return false; } - const access = expr as PropertyAccessExpression | ElementAccessExpression; - const name = getAccessedPropertyName(access); + const name = getAccessedPropertyName(expr); if (!name) { return false; } - return isMatchingReference(reference, access.expression) && isDiscriminantProperty(computedType, name); + return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name); } - function narrowTypeByDiscriminant(type: Type, access: PropertyAccessExpression | ElementAccessExpression, narrowType: (t: Type) => Type): Type { + function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { const propName = getAccessedPropertyName(access); if (!propName) { return type; @@ -15650,7 +15666,7 @@ namespace ts { return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } if (isMatchingReferenceDiscriminant(expr, declaredType)) { - return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); + return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } if (containsMatchingReferenceDiscriminant(reference, expr)) { return declaredType; @@ -15701,10 +15717,10 @@ namespace ts { return narrowTypeByEquality(type, operator, left, assumeTrue); } if (isMatchingReferenceDiscriminant(left, declaredType)) { - return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); + return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } if (isMatchingReferenceDiscriminant(right, declaredType)) { - return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); + return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (containsMatchingReferenceDiscriminant(reference, left) || containsMatchingReferenceDiscriminant(reference, right)) { return declaredType; @@ -16009,9 +16025,8 @@ namespace ts { } else { const invokedExpression = skipParentheses(callExpression.expression); - if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) { - const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression; - const possibleReference = skipParentheses(accessExpression.expression); + if (isAccessExpression(invokedExpression)) { + const possibleReference = skipParentheses(invokedExpression.expression); if (isMatchingReference(reference, possibleReference)) { return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); } @@ -16954,7 +16969,7 @@ namespace ts { if (parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken) { const target = (parent).left; if (target.kind === SyntaxKind.PropertyAccessExpression || target.kind === SyntaxKind.ElementAccessExpression) { - const { expression } = target as PropertyAccessExpression | ElementAccessExpression; + const { expression } = target as AccessExpression; // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` if (inJs && isIdentifier(expression)) { const sourceFile = getSourceFileOfNode(parent); @@ -19703,7 +19718,7 @@ namespace ts { if (node.kind === SyntaxKind.CallExpression) { const callee = skipOuterExpressions(node.expression); if (callee.kind === SyntaxKind.PropertyAccessExpression || callee.kind === SyntaxKind.ElementAccessExpression) { - return (callee as PropertyAccessExpression | ElementAccessExpression).expression; + return (callee as AccessExpression).expression; } } } @@ -21735,7 +21750,7 @@ namespace ts { // Allow assignments to readonly properties within constructors of the same class declaration. if (symbol.flags & SymbolFlags.Property && (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) && - (expr as PropertyAccessExpression | ElementAccessExpression).expression.kind === SyntaxKind.ThisKeyword) { + (expr as AccessExpression).expression.kind === SyntaxKind.ThisKeyword) { // Look for if this is the constructor for the class that `symbol` is a property of. const func = getContainingFunction(expr); if (!(func && func.kind === SyntaxKind.Constructor)) { @@ -21753,7 +21768,7 @@ namespace ts { function isReferenceThroughNamespaceImport(expr: Expression): boolean { if (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) { - const node = skipParentheses((expr as PropertyAccessExpression | ElementAccessExpression).expression); + const node = skipParentheses((expr as AccessExpression).expression); if (node.kind === SyntaxKind.Identifier) { const symbol = getNodeLinks(node).resolvedSymbol!; if (symbol.flags & SymbolFlags.Alias) { @@ -22011,7 +22026,8 @@ namespace ts { } } const exprType = getLiteralTypeFromPropertyName(name); - const type = getIndexedAccessType(objectLiteralType, exprType, name); + const elementType = getIndexedAccessType(objectLiteralType, exprType, name); + const type = getFlowTypeOfDestructuring(property, elementType); return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } else if (property.kind === SyntaxKind.SpreadAssignment) { @@ -22055,10 +22071,14 @@ namespace ts { if (element.kind !== SyntaxKind.OmittedExpression) { if (element.kind !== SyntaxKind.SpreadElement) { const indexType = getLiteralType(elementIndex); - const type = isArrayLikeType(sourceType) ? - getIndexedAccessType(sourceType, indexType, createSyntheticExpression(element, indexType)) : - elementType; - return checkDestructuringAssignment(element, type, checkMode); + if (isArrayLikeType(sourceType)) { + // We create a synthetic expression so that getIndexedAccessType doesn't get confused + // when the element is a SyntaxKind.ElementAccessExpression. + const elementType = getIndexedAccessType(sourceType, indexType, createSyntheticExpression(element, indexType)); + const type = getFlowTypeOfDestructuring(element, elementType); + return checkDestructuringAssignment(element, type, checkMode); + } + return checkDestructuringAssignment(element, elementType, checkMode); } if (elementIndex < elements.length - 1) { error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); @@ -26912,7 +26932,7 @@ namespace ts { return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); case SyntaxKind.ElementAccessExpression: case SyntaxKind.PropertyAccessExpression: - const ex = expr; + const ex = expr; if (isConstantMemberAccess(ex)) { const type = getTypeOfExpression(ex.expression); if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { @@ -28886,7 +28906,7 @@ namespace ts { return getNodeLinks(node).enumMemberValue; } - function canHaveConstantValue(node: Node): node is EnumMember | PropertyAccessExpression | ElementAccessExpression { + function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { switch (node.kind) { case SyntaxKind.EnumMember: case SyntaxKind.PropertyAccessExpression: @@ -28896,7 +28916,7 @@ namespace ts { return false; } - function getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined { + function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { if (node.kind === SyntaxKind.EnumMember) { return getEnumMemberValue(node); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 495465f7032..8047c31930e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1745,6 +1745,8 @@ namespace ts { export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; + export type AccessExpression = PropertyAccessExpression | ElementAccessExpression; + export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 966149f2a6c..fe0d8b84527 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5586,6 +5586,10 @@ namespace ts { return node.kind === SyntaxKind.ElementAccessExpression; } + export function isAccessExpression(node: Node): node is AccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression; + } + export function isCallExpression(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression; } From c1ab2b089a5341756831e1590cb1ce2f8ef917db Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 15 Dec 2018 16:50:22 -0800 Subject: [PATCH 041/120] Accept new baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 ++ tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2b78178587e..dfb7acf2aba 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1054,6 +1054,7 @@ declare namespace ts { } type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; + type AccessExpression = PropertyAccessExpression | ElementAccessExpression; interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; @@ -3350,6 +3351,7 @@ declare namespace ts { function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isAccessExpression(node: Node): node is AccessExpression; function isCallExpression(node: Node): node is CallExpression; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 0e693f698f2..66d71f2080b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1054,6 +1054,7 @@ declare namespace ts { } type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; + type AccessExpression = PropertyAccessExpression | ElementAccessExpression; interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; @@ -3350,6 +3351,7 @@ declare namespace ts { function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isAccessExpression(node: Node): node is AccessExpression; function isCallExpression(node: Node): node is CallExpression; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; From 307a9b66afb593e02af5100412d3aac4191610b6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 16 Dec 2018 07:23:28 -0800 Subject: [PATCH 042/120] Add tests --- .../destructuring/destructuringControlFlow.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts diff --git a/tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts b/tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts new file mode 100644 index 00000000000..033f2bdb75e --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts @@ -0,0 +1,36 @@ +// @strict: true + +function f1(obj: { a?: string }) { + if (obj.a) { + obj = {}; + let a1 = obj["a"]; // string | undefined + let a2 = obj.a; // string | undefined + } +} + +function f2(obj: [number, string] | null[]) { + let a0 = obj[0]; // number | null + let a1 = obj[1]; // string | null + let [b0, b1] = obj; + ([a0, a1] = obj); + if (obj[0] && obj[1]) { + let c0 = obj[0]; // number + let c1 = obj[1]; // string + let [d0, d1] = obj; + ([c0, c1] = obj); + } +} + +function f3(obj: { a?: number, b?: string }) { + if (obj.a && obj.b) { + let { a, b } = obj; // number, string + ({ a, b } = obj); + } +} + +function f4() { + let x: boolean; + ({ x } = 0); // Error + ({ ["x"]: x } = 0); // Error + ({ ["x" + ""]: x } = 0); // Errpr +} From 519e19ae03e033d19ffa89d55a88ead6a59a5b9d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 16 Dec 2018 07:23:37 -0800 Subject: [PATCH 043/120] Accept new baselines --- .../destructuringControlFlow.errors.txt | 47 +++++ .../reference/destructuringControlFlow.js | 71 ++++++++ .../destructuringControlFlow.symbols | 124 ++++++++++++++ .../reference/destructuringControlFlow.types | 160 ++++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 tests/baselines/reference/destructuringControlFlow.errors.txt create mode 100644 tests/baselines/reference/destructuringControlFlow.js create mode 100644 tests/baselines/reference/destructuringControlFlow.symbols create mode 100644 tests/baselines/reference/destructuringControlFlow.types diff --git a/tests/baselines/reference/destructuringControlFlow.errors.txt b/tests/baselines/reference/destructuringControlFlow.errors.txt new file mode 100644 index 00000000000..2bc85fdd638 --- /dev/null +++ b/tests/baselines/reference/destructuringControlFlow.errors.txt @@ -0,0 +1,47 @@ +tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts(31,8): error TS2339: Property 'x' does not exist on type 'Number'. +tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts(32,9): error TS2339: Property 'x' does not exist on type 'Number'. +tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts(33,9): error TS2537: Type 'Number' has no matching index signature for type 'string'. + + +==== tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts (3 errors) ==== + function f1(obj: { a?: string }) { + if (obj.a) { + obj = {}; + let a1 = obj["a"]; // string | undefined + let a2 = obj.a; // string | undefined + } + } + + function f2(obj: [number, string] | null[]) { + let a0 = obj[0]; // number | null + let a1 = obj[1]; // string | null + let [b0, b1] = obj; + ([a0, a1] = obj); + if (obj[0] && obj[1]) { + let c0 = obj[0]; // number + let c1 = obj[1]; // string + let [d0, d1] = obj; + ([c0, c1] = obj); + } + } + + function f3(obj: { a?: number, b?: string }) { + if (obj.a && obj.b) { + let { a, b } = obj; // number, string + ({ a, b } = obj); + } + } + + function f4() { + let x: boolean; + ({ x } = 0); // Error + ~ +!!! error TS2339: Property 'x' does not exist on type 'Number'. + ({ ["x"]: x } = 0); // Error + ~~~ +!!! error TS2339: Property 'x' does not exist on type 'Number'. + ({ ["x" + ""]: x } = 0); // Errpr + ~~~~~~~~ +!!! error TS2537: Type 'Number' has no matching index signature for type 'string'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/destructuringControlFlow.js b/tests/baselines/reference/destructuringControlFlow.js new file mode 100644 index 00000000000..2d02fb5a40f --- /dev/null +++ b/tests/baselines/reference/destructuringControlFlow.js @@ -0,0 +1,71 @@ +//// [destructuringControlFlow.ts] +function f1(obj: { a?: string }) { + if (obj.a) { + obj = {}; + let a1 = obj["a"]; // string | undefined + let a2 = obj.a; // string | undefined + } +} + +function f2(obj: [number, string] | null[]) { + let a0 = obj[0]; // number | null + let a1 = obj[1]; // string | null + let [b0, b1] = obj; + ([a0, a1] = obj); + if (obj[0] && obj[1]) { + let c0 = obj[0]; // number + let c1 = obj[1]; // string + let [d0, d1] = obj; + ([c0, c1] = obj); + } +} + +function f3(obj: { a?: number, b?: string }) { + if (obj.a && obj.b) { + let { a, b } = obj; // number, string + ({ a, b } = obj); + } +} + +function f4() { + let x: boolean; + ({ x } = 0); // Error + ({ ["x"]: x } = 0); // Error + ({ ["x" + ""]: x } = 0); // Errpr +} + + +//// [destructuringControlFlow.js] +"use strict"; +function f1(obj) { + if (obj.a) { + obj = {}; + var a1 = obj["a"]; // string | undefined + var a2 = obj.a; // string | undefined + } +} +function f2(obj) { + var a0 = obj[0]; // number | null + var a1 = obj[1]; // string | null + var b0 = obj[0], b1 = obj[1]; + (a0 = obj[0], a1 = obj[1]); + if (obj[0] && obj[1]) { + var c0 = obj[0]; // number + var c1 = obj[1]; // string + var d0 = obj[0], d1 = obj[1]; + (c0 = obj[0], c1 = obj[1]); + } +} +function f3(obj) { + if (obj.a && obj.b) { + var a = obj.a, b = obj.b; // number, string + (a = obj.a, b = obj.b); + } +} +function f4() { + var _a; + var x; + (x = 0..x); // Error + (x = 0["x"]); // Error + (_a = "x" + "", x = 0[_a]); // Errpr +} diff --git a/tests/baselines/reference/destructuringControlFlow.symbols b/tests/baselines/reference/destructuringControlFlow.symbols new file mode 100644 index 00000000000..75cbfcdb481 --- /dev/null +++ b/tests/baselines/reference/destructuringControlFlow.symbols @@ -0,0 +1,124 @@ +=== tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts === +function f1(obj: { a?: string }) { +>f1 : Symbol(f1, Decl(destructuringControlFlow.ts, 0, 0)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 0, 12)) +>a : Symbol(a, Decl(destructuringControlFlow.ts, 0, 18)) + + if (obj.a) { +>obj.a : Symbol(a, Decl(destructuringControlFlow.ts, 0, 18)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 0, 12)) +>a : Symbol(a, Decl(destructuringControlFlow.ts, 0, 18)) + + obj = {}; +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 0, 12)) + + let a1 = obj["a"]; // string | undefined +>a1 : Symbol(a1, Decl(destructuringControlFlow.ts, 3, 11)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 0, 12)) +>"a" : Symbol(a, Decl(destructuringControlFlow.ts, 0, 18)) + + let a2 = obj.a; // string | undefined +>a2 : Symbol(a2, Decl(destructuringControlFlow.ts, 4, 11)) +>obj.a : Symbol(a, Decl(destructuringControlFlow.ts, 0, 18)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 0, 12)) +>a : Symbol(a, Decl(destructuringControlFlow.ts, 0, 18)) + } +} + +function f2(obj: [number, string] | null[]) { +>f2 : Symbol(f2, Decl(destructuringControlFlow.ts, 6, 1)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) + + let a0 = obj[0]; // number | null +>a0 : Symbol(a0, Decl(destructuringControlFlow.ts, 9, 7)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) +>0 : Symbol(0) + + let a1 = obj[1]; // string | null +>a1 : Symbol(a1, Decl(destructuringControlFlow.ts, 10, 7)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) +>1 : Symbol(1) + + let [b0, b1] = obj; +>b0 : Symbol(b0, Decl(destructuringControlFlow.ts, 11, 9)) +>b1 : Symbol(b1, Decl(destructuringControlFlow.ts, 11, 12)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) + + ([a0, a1] = obj); +>a0 : Symbol(a0, Decl(destructuringControlFlow.ts, 9, 7)) +>a1 : Symbol(a1, Decl(destructuringControlFlow.ts, 10, 7)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) + + if (obj[0] && obj[1]) { +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) +>0 : Symbol(0) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) +>1 : Symbol(1) + + let c0 = obj[0]; // number +>c0 : Symbol(c0, Decl(destructuringControlFlow.ts, 14, 11)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) +>0 : Symbol(0) + + let c1 = obj[1]; // string +>c1 : Symbol(c1, Decl(destructuringControlFlow.ts, 15, 11)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) +>1 : Symbol(1) + + let [d0, d1] = obj; +>d0 : Symbol(d0, Decl(destructuringControlFlow.ts, 16, 13)) +>d1 : Symbol(d1, Decl(destructuringControlFlow.ts, 16, 16)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) + + ([c0, c1] = obj); +>c0 : Symbol(c0, Decl(destructuringControlFlow.ts, 14, 11)) +>c1 : Symbol(c1, Decl(destructuringControlFlow.ts, 15, 11)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 8, 12)) + } +} + +function f3(obj: { a?: number, b?: string }) { +>f3 : Symbol(f3, Decl(destructuringControlFlow.ts, 19, 1)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 21, 12)) +>a : Symbol(a, Decl(destructuringControlFlow.ts, 21, 18)) +>b : Symbol(b, Decl(destructuringControlFlow.ts, 21, 30)) + + if (obj.a && obj.b) { +>obj.a : Symbol(a, Decl(destructuringControlFlow.ts, 21, 18)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 21, 12)) +>a : Symbol(a, Decl(destructuringControlFlow.ts, 21, 18)) +>obj.b : Symbol(b, Decl(destructuringControlFlow.ts, 21, 30)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 21, 12)) +>b : Symbol(b, Decl(destructuringControlFlow.ts, 21, 30)) + + let { a, b } = obj; // number, string +>a : Symbol(a, Decl(destructuringControlFlow.ts, 23, 13)) +>b : Symbol(b, Decl(destructuringControlFlow.ts, 23, 16)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 21, 12)) + + ({ a, b } = obj); +>a : Symbol(a, Decl(destructuringControlFlow.ts, 24, 10)) +>b : Symbol(b, Decl(destructuringControlFlow.ts, 24, 13)) +>obj : Symbol(obj, Decl(destructuringControlFlow.ts, 21, 12)) + } +} + +function f4() { +>f4 : Symbol(f4, Decl(destructuringControlFlow.ts, 26, 1)) + + let x: boolean; +>x : Symbol(x, Decl(destructuringControlFlow.ts, 29, 7)) + + ({ x } = 0); // Error +>x : Symbol(x, Decl(destructuringControlFlow.ts, 30, 6)) + + ({ ["x"]: x } = 0); // Error +>["x"] : Symbol(["x"], Decl(destructuringControlFlow.ts, 31, 6)) +>"x" : Symbol(["x"], Decl(destructuringControlFlow.ts, 31, 6)) +>x : Symbol(x, Decl(destructuringControlFlow.ts, 29, 7)) + + ({ ["x" + ""]: x } = 0); // Errpr +>["x" + ""] : Symbol(["x" + ""], Decl(destructuringControlFlow.ts, 32, 6)) +>x : Symbol(x, Decl(destructuringControlFlow.ts, 29, 7)) +} + diff --git a/tests/baselines/reference/destructuringControlFlow.types b/tests/baselines/reference/destructuringControlFlow.types new file mode 100644 index 00000000000..b831d116740 --- /dev/null +++ b/tests/baselines/reference/destructuringControlFlow.types @@ -0,0 +1,160 @@ +=== tests/cases/conformance/es6/destructuring/destructuringControlFlow.ts === +function f1(obj: { a?: string }) { +>f1 : (obj: { a?: string | undefined; }) => void +>obj : { a?: string | undefined; } +>a : string | undefined + + if (obj.a) { +>obj.a : string | undefined +>obj : { a?: string | undefined; } +>a : string | undefined + + obj = {}; +>obj = {} : {} +>obj : { a?: string | undefined; } +>{} : {} + + let a1 = obj["a"]; // string | undefined +>a1 : string | undefined +>obj["a"] : string | undefined +>obj : { a?: string | undefined; } +>"a" : "a" + + let a2 = obj.a; // string | undefined +>a2 : string | undefined +>obj.a : string | undefined +>obj : { a?: string | undefined; } +>a : string | undefined + } +} + +function f2(obj: [number, string] | null[]) { +>f2 : (obj: [number, string] | null[]) => void +>obj : [number, string] | null[] +>null : null + + let a0 = obj[0]; // number | null +>a0 : number | null +>obj[0] : number | null +>obj : [number, string] | null[] +>0 : 0 + + let a1 = obj[1]; // string | null +>a1 : string | null +>obj[1] : string | null +>obj : [number, string] | null[] +>1 : 1 + + let [b0, b1] = obj; +>b0 : number | null +>b1 : string | null +>obj : [number, string] | null[] + + ([a0, a1] = obj); +>([a0, a1] = obj) : [number, string] | null[] +>[a0, a1] = obj : [number, string] | null[] +>[a0, a1] : [number | null, string | null] +>a0 : number | null +>a1 : string | null +>obj : [number, string] | null[] + + if (obj[0] && obj[1]) { +>obj[0] && obj[1] : string | 0 | null +>obj[0] : number | null +>obj : [number, string] | null[] +>0 : 0 +>obj[1] : string | null +>obj : [number, string] | null[] +>1 : 1 + + let c0 = obj[0]; // number +>c0 : number +>obj[0] : number +>obj : [number, string] | null[] +>0 : 0 + + let c1 = obj[1]; // string +>c1 : string +>obj[1] : string +>obj : [number, string] | null[] +>1 : 1 + + let [d0, d1] = obj; +>d0 : number +>d1 : string +>obj : [number, string] | null[] + + ([c0, c1] = obj); +>([c0, c1] = obj) : [number, string] | null[] +>[c0, c1] = obj : [number, string] | null[] +>[c0, c1] : [number, string] +>c0 : number +>c1 : string +>obj : [number, string] | null[] + } +} + +function f3(obj: { a?: number, b?: string }) { +>f3 : (obj: { a?: number | undefined; b?: string | undefined; }) => void +>obj : { a?: number | undefined; b?: string | undefined; } +>a : number | undefined +>b : string | undefined + + if (obj.a && obj.b) { +>obj.a && obj.b : string | 0 | undefined +>obj.a : number | undefined +>obj : { a?: number | undefined; b?: string | undefined; } +>a : number | undefined +>obj.b : string | undefined +>obj : { a?: number | undefined; b?: string | undefined; } +>b : string | undefined + + let { a, b } = obj; // number, string +>a : number +>b : string +>obj : { a?: number | undefined; b?: string | undefined; } + + ({ a, b } = obj); +>({ a, b } = obj) : { a?: number | undefined; b?: string | undefined; } +>{ a, b } = obj : { a?: number | undefined; b?: string | undefined; } +>{ a, b } : { a: number; b: string; } +>a : number +>b : string +>obj : { a?: number | undefined; b?: string | undefined; } + } +} + +function f4() { +>f4 : () => void + + let x: boolean; +>x : boolean + + ({ x } = 0); // Error +>({ x } = 0) : 0 +>{ x } = 0 : 0 +>{ x } : { x: boolean; } +>x : boolean +>0 : 0 + + ({ ["x"]: x } = 0); // Error +>({ ["x"]: x } = 0) : 0 +>{ ["x"]: x } = 0 : 0 +>{ ["x"]: x } : { ["x"]: boolean; } +>["x"] : boolean +>"x" : "x" +>x : boolean +>0 : 0 + + ({ ["x" + ""]: x } = 0); // Errpr +>({ ["x" + ""]: x } = 0) : 0 +>{ ["x" + ""]: x } = 0 : 0 +>{ ["x" + ""]: x } : { [x: string]: boolean; } +>["x" + ""] : boolean +>"x" + "" : string +>"x" : "x" +>"" : "" +>x : boolean +>0 : 0 +} + From 93c0f0ca0fd8d1996e11eccff19d12b29ce8b338 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 17 Dec 2018 14:08:01 -0800 Subject: [PATCH 044/120] isEmptyObjectType should check that argument is not generic mapped type --- 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 356ddc2a28e..3f129802194 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11386,7 +11386,7 @@ namespace ts { } function isEmptyObjectType(type: Type): boolean { - return type.flags & TypeFlags.Object ? isEmptyResolvedType(resolveStructuredTypeMembers(type)) : + return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type)) : type.flags & TypeFlags.NonPrimitive ? true : type.flags & TypeFlags.Union ? some((type).types, isEmptyObjectType) : type.flags & TypeFlags.Intersection ? every((type).types, isEmptyObjectType) : @@ -12361,7 +12361,7 @@ namespace ts { } else { // An empty object type is related to any mapped type that includes a '?' modifier. - if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) { + if (isPartialMappedType(target) && isEmptyObjectType(source)) { return Ternary.True; } if (isGenericMappedType(target)) { From ab797df977398445cd07bec647b055fb52fc8702 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 17 Dec 2018 14:14:54 -0800 Subject: [PATCH 045/120] Add regression test --- tests/cases/compiler/genericIsNeverEmptyObject.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/cases/compiler/genericIsNeverEmptyObject.ts diff --git a/tests/cases/compiler/genericIsNeverEmptyObject.ts b/tests/cases/compiler/genericIsNeverEmptyObject.ts new file mode 100644 index 00000000000..4431e743e95 --- /dev/null +++ b/tests/cases/compiler/genericIsNeverEmptyObject.ts @@ -0,0 +1,11 @@ +// @strict: true + +// Repro from #29067 + +function test(obj: T) { + let { a, ...rest } = obj; + return { ...rest, b: a }; +} + +let o1 = { a: 'hello', x: 42 }; +let o2: { b: string, x: number } = test(o1); From b4545987719e62019e44e1311b70b8beef55b5d1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 17 Dec 2018 14:15:03 -0800 Subject: [PATCH 046/120] Accept new baselines --- .../reference/genericIsNeverEmptyObject.js | 41 +++++++++++++++++++ .../genericIsNeverEmptyObject.symbols | 33 +++++++++++++++ .../reference/genericIsNeverEmptyObject.types | 36 ++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 tests/baselines/reference/genericIsNeverEmptyObject.js create mode 100644 tests/baselines/reference/genericIsNeverEmptyObject.symbols create mode 100644 tests/baselines/reference/genericIsNeverEmptyObject.types diff --git a/tests/baselines/reference/genericIsNeverEmptyObject.js b/tests/baselines/reference/genericIsNeverEmptyObject.js new file mode 100644 index 00000000000..d914813ed99 --- /dev/null +++ b/tests/baselines/reference/genericIsNeverEmptyObject.js @@ -0,0 +1,41 @@ +//// [genericIsNeverEmptyObject.ts] +// Repro from #29067 + +function test(obj: T) { + let { a, ...rest } = obj; + return { ...rest, b: a }; +} + +let o1 = { a: 'hello', x: 42 }; +let o2: { b: string, x: number } = test(o1); + + +//// [genericIsNeverEmptyObject.js] +"use strict"; +// Repro from #29067 +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +}; +function test(obj) { + var a = obj.a, rest = __rest(obj, ["a"]); + return __assign({}, rest, { b: a }); +} +var o1 = { a: 'hello', x: 42 }; +var o2 = test(o1); diff --git a/tests/baselines/reference/genericIsNeverEmptyObject.symbols b/tests/baselines/reference/genericIsNeverEmptyObject.symbols new file mode 100644 index 00000000000..21cc6a21ca6 --- /dev/null +++ b/tests/baselines/reference/genericIsNeverEmptyObject.symbols @@ -0,0 +1,33 @@ +=== tests/cases/compiler/genericIsNeverEmptyObject.ts === +// Repro from #29067 + +function test(obj: T) { +>test : Symbol(test, Decl(genericIsNeverEmptyObject.ts, 0, 0)) +>T : Symbol(T, Decl(genericIsNeverEmptyObject.ts, 2, 14)) +>a : Symbol(a, Decl(genericIsNeverEmptyObject.ts, 2, 25)) +>obj : Symbol(obj, Decl(genericIsNeverEmptyObject.ts, 2, 39)) +>T : Symbol(T, Decl(genericIsNeverEmptyObject.ts, 2, 14)) + + let { a, ...rest } = obj; +>a : Symbol(a, Decl(genericIsNeverEmptyObject.ts, 3, 9)) +>rest : Symbol(rest, Decl(genericIsNeverEmptyObject.ts, 3, 12)) +>obj : Symbol(obj, Decl(genericIsNeverEmptyObject.ts, 2, 39)) + + return { ...rest, b: a }; +>rest : Symbol(rest, Decl(genericIsNeverEmptyObject.ts, 3, 12)) +>b : Symbol(b, Decl(genericIsNeverEmptyObject.ts, 4, 21)) +>a : Symbol(a, Decl(genericIsNeverEmptyObject.ts, 3, 9)) +} + +let o1 = { a: 'hello', x: 42 }; +>o1 : Symbol(o1, Decl(genericIsNeverEmptyObject.ts, 7, 3)) +>a : Symbol(a, Decl(genericIsNeverEmptyObject.ts, 7, 10)) +>x : Symbol(x, Decl(genericIsNeverEmptyObject.ts, 7, 22)) + +let o2: { b: string, x: number } = test(o1); +>o2 : Symbol(o2, Decl(genericIsNeverEmptyObject.ts, 8, 3)) +>b : Symbol(b, Decl(genericIsNeverEmptyObject.ts, 8, 9)) +>x : Symbol(x, Decl(genericIsNeverEmptyObject.ts, 8, 20)) +>test : Symbol(test, Decl(genericIsNeverEmptyObject.ts, 0, 0)) +>o1 : Symbol(o1, Decl(genericIsNeverEmptyObject.ts, 7, 3)) + diff --git a/tests/baselines/reference/genericIsNeverEmptyObject.types b/tests/baselines/reference/genericIsNeverEmptyObject.types new file mode 100644 index 00000000000..5d2b4d9d57f --- /dev/null +++ b/tests/baselines/reference/genericIsNeverEmptyObject.types @@ -0,0 +1,36 @@ +=== tests/cases/compiler/genericIsNeverEmptyObject.ts === +// Repro from #29067 + +function test(obj: T) { +>test : (obj: T) => Pick> & { b: string; } +>a : string +>obj : T + + let { a, ...rest } = obj; +>a : string +>rest : Pick> +>obj : T + + return { ...rest, b: a }; +>{ ...rest, b: a } : Pick> & { b: string; } +>rest : Pick> +>b : string +>a : string +} + +let o1 = { a: 'hello', x: 42 }; +>o1 : { a: string; x: number; } +>{ a: 'hello', x: 42 } : { a: string; x: number; } +>a : string +>'hello' : "hello" +>x : number +>42 : 42 + +let o2: { b: string, x: number } = test(o1); +>o2 : { b: string; x: number; } +>b : string +>x : number +>test(o1) : Pick<{ a: string; x: number; }, "x"> & { b: string; } +>test : (obj: T) => Pick> & { b: string; } +>o1 : { a: string; x: number; } + From d2885eab4a94dd52c54b4df004c3706ddde93ad7 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 17 Dec 2018 14:56:48 -0800 Subject: [PATCH 047/120] Add regression test for #29047 (#29066) --- ...eventEmitterPatternWithRecordOfFunction.js | 15 +++++++ ...EmitterPatternWithRecordOfFunction.symbols | 40 +++++++++++++++++++ ...ntEmitterPatternWithRecordOfFunction.types | 21 ++++++++++ ...eventEmitterPatternWithRecordOfFunction.ts | 12 ++++++ 4 files changed, 88 insertions(+) create mode 100644 tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.js create mode 100644 tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.symbols create mode 100644 tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.types create mode 100644 tests/cases/compiler/eventEmitterPatternWithRecordOfFunction.ts diff --git a/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.js b/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.js new file mode 100644 index 00000000000..9fa1d2b3989 --- /dev/null +++ b/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.js @@ -0,0 +1,15 @@ +//// [eventEmitterPatternWithRecordOfFunction.ts] +interface A { + emit(event: string, ...args: any[]): boolean; +} + +type Args = F extends (...args: infer A) => void ? A : never; + +type EventMap = Record; + +interface B extends A { + emit(event: Event, ...args: Args): boolean; +} + +//// [eventEmitterPatternWithRecordOfFunction.js] +"use strict"; diff --git a/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.symbols b/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.symbols new file mode 100644 index 00000000000..003cb298e18 --- /dev/null +++ b/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.symbols @@ -0,0 +1,40 @@ +=== tests/cases/compiler/eventEmitterPatternWithRecordOfFunction.ts === +interface A { +>A : Symbol(A, Decl(eventEmitterPatternWithRecordOfFunction.ts, 0, 0)) + + emit(event: string, ...args: any[]): boolean; +>emit : Symbol(A.emit, Decl(eventEmitterPatternWithRecordOfFunction.ts, 0, 13)) +>event : Symbol(event, Decl(eventEmitterPatternWithRecordOfFunction.ts, 1, 9)) +>args : Symbol(args, Decl(eventEmitterPatternWithRecordOfFunction.ts, 1, 23)) +} + +type Args = F extends (...args: infer A) => void ? A : never; +>Args : Symbol(Args, Decl(eventEmitterPatternWithRecordOfFunction.ts, 2, 1)) +>F : Symbol(F, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 10)) +>F : Symbol(F, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 10)) +>args : Symbol(args, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 26)) +>A : Symbol(A, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 40)) +>A : Symbol(A, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 40)) + +type EventMap = Record; +>EventMap : Symbol(EventMap, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 64)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +interface B extends A { +>B : Symbol(B, Decl(eventEmitterPatternWithRecordOfFunction.ts, 6, 41)) +>M : Symbol(M, Decl(eventEmitterPatternWithRecordOfFunction.ts, 8, 12)) +>EventMap : Symbol(EventMap, Decl(eventEmitterPatternWithRecordOfFunction.ts, 4, 64)) +>A : Symbol(A, Decl(eventEmitterPatternWithRecordOfFunction.ts, 0, 0)) + + emit(event: Event, ...args: Args): boolean; +>emit : Symbol(B.emit, Decl(eventEmitterPatternWithRecordOfFunction.ts, 8, 43)) +>Event : Symbol(Event, Decl(eventEmitterPatternWithRecordOfFunction.ts, 9, 9)) +>M : Symbol(M, Decl(eventEmitterPatternWithRecordOfFunction.ts, 8, 12)) +>event : Symbol(event, Decl(eventEmitterPatternWithRecordOfFunction.ts, 9, 32)) +>Event : Symbol(Event, Decl(eventEmitterPatternWithRecordOfFunction.ts, 9, 9)) +>args : Symbol(args, Decl(eventEmitterPatternWithRecordOfFunction.ts, 9, 45)) +>Args : Symbol(Args, Decl(eventEmitterPatternWithRecordOfFunction.ts, 2, 1)) +>M : Symbol(M, Decl(eventEmitterPatternWithRecordOfFunction.ts, 8, 12)) +>Event : Symbol(Event, Decl(eventEmitterPatternWithRecordOfFunction.ts, 9, 9)) +} diff --git a/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.types b/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.types new file mode 100644 index 00000000000..d2d1895780f --- /dev/null +++ b/tests/baselines/reference/eventEmitterPatternWithRecordOfFunction.types @@ -0,0 +1,21 @@ +=== tests/cases/compiler/eventEmitterPatternWithRecordOfFunction.ts === +interface A { + emit(event: string, ...args: any[]): boolean; +>emit : (event: string, ...args: any[]) => boolean +>event : string +>args : any[] +} + +type Args = F extends (...args: infer A) => void ? A : never; +>Args : Args +>args : A + +type EventMap = Record; +>EventMap : Record + +interface B extends A { + emit(event: Event, ...args: Args): boolean; +>emit : (event: Event, ...args: Args) => boolean +>event : Event +>args : Args +} diff --git a/tests/cases/compiler/eventEmitterPatternWithRecordOfFunction.ts b/tests/cases/compiler/eventEmitterPatternWithRecordOfFunction.ts new file mode 100644 index 00000000000..389b0553b22 --- /dev/null +++ b/tests/cases/compiler/eventEmitterPatternWithRecordOfFunction.ts @@ -0,0 +1,12 @@ +// @strict: true +interface A { + emit(event: string, ...args: any[]): boolean; +} + +type Args = F extends (...args: infer A) => void ? A : never; + +type EventMap = Record; + +interface B extends A { + emit(event: Event, ...args: Args): boolean; +} \ No newline at end of file From 109fcd5d438054ab609efed546939a368de126ce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 17 Dec 2018 15:06:15 -0800 Subject: [PATCH 048/120] Address CR feedback --- src/compiler/checker.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 17c10c01235..b78d54a03be 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14633,7 +14633,7 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const propName = getAccessedPropertyName(node); - if (propName) { + if (propName !== undefined) { const key = getFlowCacheKey((node).expression); return key && key + "." + propName; } @@ -14694,7 +14694,7 @@ namespace ts { const type = getDeclaredTypeOfReference(expr.expression); if (type) { const propName = getAccessedPropertyName(expr); - return propName && getTypeOfPropertyOfType(type, propName); + return propName !== undefined ? getTypeOfPropertyOfType(type, propName) : undefined; } } return undefined; @@ -15645,7 +15645,7 @@ namespace ts { return false; } const name = getAccessedPropertyName(expr); - if (!name) { + if (name === undefined) { return false; } return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(computedType, name); @@ -15653,7 +15653,7 @@ namespace ts { function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { const propName = getAccessedPropertyName(access); - if (!propName) { + if (propName === undefined) { return type; } const propType = getTypeOfPropertyOfType(type, propName); From 55852c495e85784c384514cfbb31ee7733776338 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 17 Dec 2018 15:52:32 -0800 Subject: [PATCH 049/120] Remove duplicate function from checker --- src/compiler/checker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 356ddc2a28e..9c802bb9b82 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7158,7 +7158,7 @@ namespace ts { return list.some(property => { const name = property.name && getTextOfPropertyName(property.name); const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); - return !!expected && typeIsLiteralType(expected) && !isTypeIdenticalTo(getTypeOfNode(property), expected); + return !!expected && isLiteralType(expected) && !isTypeIdenticalTo(getTypeOfNode(property), expected); }); } @@ -30937,8 +30937,4 @@ namespace ts { export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; // tslint:enable variable-name } - - function typeIsLiteralType(type: Type): type is LiteralType { - return !!(type.flags & TypeFlags.Literal); - } } From 54b46d74b5b909e44c81b58e8dd4986a80a0d04f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 17 Dec 2018 15:59:12 -0800 Subject: [PATCH 050/120] Address CR feedback, take two --- src/compiler/checker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b78d54a03be..b5c477068a4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14681,9 +14681,11 @@ namespace ts { // a possible discriminant if its type differs in the constituents of containing union type, and if every // choice is a unit type or a union of unit types. function containsMatchingReferenceDiscriminant(source: Node, target: Node) { + let name; return isAccessExpression(target) && containsMatchingReference(source, target.expression) && - isDiscriminantProperty(getDeclaredTypeOfReference(target.expression), getAccessedPropertyName(target)); + (name = getAccessedPropertyName(target)) !== undefined && + isDiscriminantProperty(getDeclaredTypeOfReference(target.expression), name); } function getDeclaredTypeOfReference(expr: Node): Type | undefined { @@ -14714,8 +14716,8 @@ namespace ts { return false; } - function isDiscriminantProperty(type: Type | undefined, name: __String | undefined) { - if (type && name && type.flags & TypeFlags.Union) { + function isDiscriminantProperty(type: Type | undefined, name: __String) { + if (type && type.flags & TypeFlags.Union) { const prop = getUnionOrIntersectionProperty(type, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { From ec83a53273da4d75bd1fc58f4fbebdf3ac21b010 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 17 Dec 2018 21:05:06 -0800 Subject: [PATCH 051/120] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbce8186fac..e1c9490f3c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,11 +90,11 @@ Library files in `built/local/` are updated by running jake ``` -The files in `lib/` are used to bootstrap compilation and usually do not need to be updated. +**Note**: The files in `lib/` are used to bootstrap compilation and usually do not need to be updated. -#### `src/lib/dom.generated.d.ts` and `src/lib/webworker.generated.d.ts` +#### Generated library files -These two files represent the DOM typings and are auto-generated. To make any modifications to them, please submit a PR to https://github.com/Microsoft/TSJS-lib-generator +The files `src/lib/dom.generated.d.ts` and `src/lib/webworker.generated.d.ts` both represent type declarations for the DOM and are auto-generated. To make any modifications to them, you will have to direct changes to https://github.com/Microsoft/TSJS-lib-generator ## Running the Tests From a58f7dfb1e03fd4b25619777a53ba9128e3b38eb Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 17 Dec 2018 21:06:00 -0800 Subject: [PATCH 052/120] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1c9490f3c4..97926ed54a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,7 +92,7 @@ jake **Note**: The files in `lib/` are used to bootstrap compilation and usually do not need to be updated. -#### Generated library files +### Modifying generated library files The files `src/lib/dom.generated.d.ts` and `src/lib/webworker.generated.d.ts` both represent type declarations for the DOM and are auto-generated. To make any modifications to them, you will have to direct changes to https://github.com/Microsoft/TSJS-lib-generator From c5ee5ea7f2440a11886e78b48f3d3010b2aafbaf Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 17 Dec 2018 21:13:23 -0800 Subject: [PATCH 053/120] Update CONTRIBUTING.md --- CONTRIBUTING.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97926ed54a6..683c47817b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,15 +82,22 @@ Your pull request should: * To avoid line ending issues, set `autocrlf = input` and `whitespace = cr-at-eol` in your git configuration ## Contributing `lib.d.ts` fixes - -The library sources are in: [src/lib](https://github.com/Microsoft/TypeScript/tree/master/src/lib) -Library files in `built/local/` are updated by running -```Shell +There are three relevant locations to be aware of when it comes to TypeScript's library declaration files: + +* `src/lib`: the location of the sources themselves. +* `lib`: the location of the last-known-good (LKG) versions of the files which are updated periodically. +* `built/local`: the build output location, including where `src/lib` files will be copied to. + +Any changes should be made to [src/lib](https://github.com/Microsoft/TypeScript/tree/master/src/lib). **Most** of these files can be updated by hand, with the exception of any generated files (see below). + +Library files in `built/local/` are updated automatically by running the standard build task: + +```sh jake ``` -**Note**: The files in `lib/` are used to bootstrap compilation and usually do not need to be updated. +The files in `lib/` are used to bootstrap compilation and usually **should not** be updated unless publishing a new version or updating the LKG. ### Modifying generated library files From 0d7e3f82d40885a29b42a190e758fa00f194ed97 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Dec 2018 00:56:00 -0800 Subject: [PATCH 054/120] Stretched the error to the end of 'n's --- src/compiler/scanner.ts | 13 ++++++------ ...ntifierStartAfterNumericLiteral.errors.txt | 20 ++++++++++++------- .../identifierStartAfterNumericLiteral.js | 6 ++---- .../identifierStartAfterNumericLiteral.types | 10 ++++------ .../reference/parseBigInt.errors.txt | 12 +++++------ tests/baselines/reference/parseBigInt.js | 6 +++--- tests/baselines/reference/parseBigInt.symbols | 3 --- tests/baselines/reference/parseBigInt.types | 9 +++------ 8 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 1a6da02bbd0..742ff4a32f1 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -976,7 +976,7 @@ namespace ts { } if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { - checkForIdentifierStartAfterNumericLiteral(decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); + checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); return { type: SyntaxKind.NumericLiteral, value: "" + +result // if value is not an integer, it can be safely coerced to a number @@ -985,12 +985,12 @@ namespace ts { else { tokenValue = result; const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint - checkForIdentifierStartAfterNumericLiteral(); + checkForIdentifierStartAfterNumericLiteral(start); return { type, value: tokenValue }; } } - function checkForIdentifierStartAfterNumericLiteral(isScientific?: boolean) { + function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { if (!isIdentifierStart(text.charCodeAt(pos), languageVersion)) { return; } @@ -1000,17 +1000,16 @@ namespace ts { if (length === 1 && text[identifierStart] === "n") { if (isScientific) { - error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, identifierStart, length); + error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); } else { - error(Diagnostics.A_bigint_literal_must_be_an_integer, identifierStart, length); + error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); } } else { error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); + pos = identifierStart; } - - pos = identifierStart; } function scanOctalDigits(): number { diff --git a/tests/baselines/reference/identifierStartAfterNumericLiteral.errors.txt b/tests/baselines/reference/identifierStartAfterNumericLiteral.errors.txt index 2b4884f7303..7482ee62fec 100644 --- a/tests/baselines/reference/identifierStartAfterNumericLiteral.errors.txt +++ b/tests/baselines/reference/identifierStartAfterNumericLiteral.errors.txt @@ -9,10 +9,12 @@ tests/cases/compiler/identifierStartAfterNumericLiteral.ts(6,5): error TS1124: D tests/cases/compiler/identifierStartAfterNumericLiteral.ts(6,6): error TS2538: Type 'null' cannot be used as an index type. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(7,2): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(8,4): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. +tests/cases/compiler/identifierStartAfterNumericLiteral.ts(9,1): error TS1352: A bigint literal cannot use exponential notation. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(9,3): error TS1124: Digit expected. -tests/cases/compiler/identifierStartAfterNumericLiteral.ts(9,3): error TS2304: Cannot find name 'n'. +tests/cases/compiler/identifierStartAfterNumericLiteral.ts(9,5): error TS2538: Type 'null' cannot be used as an index type. +tests/cases/compiler/identifierStartAfterNumericLiteral.ts(10,1): error TS1352: A bigint literal cannot use exponential notation. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(10,5): error TS1124: Digit expected. -tests/cases/compiler/identifierStartAfterNumericLiteral.ts(10,5): error TS2304: Cannot find name 'n'. +tests/cases/compiler/identifierStartAfterNumericLiteral.ts(10,7): error TS2538: Type 'null' cannot be used as an index type. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(11,2): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(11,2): error TS2304: Cannot find name 'a'. tests/cases/compiler/identifierStartAfterNumericLiteral.ts(12,4): error TS1351: An identifier or keyword cannot immediately follow a numeric literal. @@ -35,7 +37,7 @@ tests/cases/compiler/identifierStartAfterNumericLiteral.ts(26,5): error TS1351: tests/cases/compiler/identifierStartAfterNumericLiteral.ts(26,5): error TS2304: Cannot find name 'abc'. -==== tests/cases/compiler/identifierStartAfterNumericLiteral.ts (35 errors) ==== +==== tests/cases/compiler/identifierStartAfterNumericLiteral.ts (37 errors) ==== let valueIn = 3in[null]; ~~ !!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. @@ -67,15 +69,19 @@ tests/cases/compiler/identifierStartAfterNumericLiteral.ts(26,5): error TS2304: ~~ !!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. 3en[null] + ~~~ +!!! error TS1352: A bigint literal cannot use exponential notation. !!! error TS1124: Digit expected. - ~ -!!! error TS2304: Cannot find name 'n'. + ~~~~ +!!! error TS2538: Type 'null' cannot be used as an index type. 123en[null] + ~~~~~ +!!! error TS1352: A bigint literal cannot use exponential notation. !!! error TS1124: Digit expected. - ~ -!!! error TS2304: Cannot find name 'n'. + ~~~~ +!!! error TS2538: Type 'null' cannot be used as an index type. 1a ~ !!! error TS1351: An identifier or keyword cannot immediately follow a numeric literal. diff --git a/tests/baselines/reference/identifierStartAfterNumericLiteral.js b/tests/baselines/reference/identifierStartAfterNumericLiteral.js index 59ece3e1c7e..a37524489f2 100644 --- a/tests/baselines/reference/identifierStartAfterNumericLiteral.js +++ b/tests/baselines/reference/identifierStartAfterNumericLiteral.js @@ -37,10 +37,8 @@ a[null]; 123e[null]; 3 in [null]; 123 in [null]; -3e; -n[null]; -123e; -n[null]; +3en[null]; +123en[null]; 1; a; 123; diff --git a/tests/baselines/reference/identifierStartAfterNumericLiteral.types b/tests/baselines/reference/identifierStartAfterNumericLiteral.types index af3399a6d13..41a601874fa 100644 --- a/tests/baselines/reference/identifierStartAfterNumericLiteral.types +++ b/tests/baselines/reference/identifierStartAfterNumericLiteral.types @@ -41,15 +41,13 @@ let valueIn = 3in[null]; >null : null 3en[null] ->3e : 3 ->n[null] : any ->n : any +>3en[null] : any +>3en : 3 >null : null 123en[null] ->123e : 123 ->n[null] : any ->n : any +>123en[null] : any +>123en : 123 >null : null 1a diff --git a/tests/baselines/reference/parseBigInt.errors.txt b/tests/baselines/reference/parseBigInt.errors.txt index 72f98ee0f46..dc791e3325e 100644 --- a/tests/baselines/reference/parseBigInt.errors.txt +++ b/tests/baselines/reference/parseBigInt.errors.txt @@ -1,9 +1,9 @@ tests/cases/compiler/parseBigInt.ts(51,20): error TS2736: Operator '+' cannot be applied to type '123n'. tests/cases/compiler/parseBigInt.ts(52,23): error TS2736: Operator '+' cannot be applied to type '291n'. tests/cases/compiler/parseBigInt.ts(56,25): error TS1005: ',' expected. -tests/cases/compiler/parseBigInt.ts(57,25): error TS1352: A bigint literal cannot use exponential notation. -tests/cases/compiler/parseBigInt.ts(58,22): error TS1353: A bigint literal must be an integer. -tests/cases/compiler/parseBigInt.ts(59,28): error TS1353: A bigint literal must be an integer. +tests/cases/compiler/parseBigInt.ts(57,22): error TS1352: A bigint literal cannot use exponential notation. +tests/cases/compiler/parseBigInt.ts(58,19): error TS1353: A bigint literal must be an integer. +tests/cases/compiler/parseBigInt.ts(59,26): error TS1353: A bigint literal must be an integer. tests/cases/compiler/parseBigInt.ts(60,23): error TS1177: Binary digit expected. tests/cases/compiler/parseBigInt.ts(61,20): error TS1178: Octal digit expected. tests/cases/compiler/parseBigInt.ts(62,20): error TS1125: Hexadecimal digit expected. @@ -81,13 +81,13 @@ tests/cases/compiler/parseBigInt.ts(70,72): error TS2345: Argument of type '3' i ~ !!! error TS1005: ',' expected. { const scientific = 1e2n; } - ~ + ~~~~ !!! error TS1352: A bigint literal cannot use exponential notation. { const decimal = 4.1n; } - ~ + ~~~~ !!! error TS1353: A bigint literal must be an integer. { const leadingDecimal = .1n; } - ~ + ~~~ !!! error TS1353: A bigint literal must be an integer. const emptyBinary = 0bn; // should error but infer 0n diff --git a/tests/baselines/reference/parseBigInt.js b/tests/baselines/reference/parseBigInt.js index 1e8b06743d5..1ffccb11e64 100644 --- a/tests/baselines/reference/parseBigInt.js +++ b/tests/baselines/reference/parseBigInt.js @@ -123,13 +123,13 @@ const unaryPlusHex = +0x123n; const legacyOct = 0123, n; } { - const scientific = 1e2, n; + const scientific = 1e2n; } { - const decimal = 4.1, n; + const decimal = 4.1n; } { - const leadingDecimal = .1, n; + const leadingDecimal = .1n; } const emptyBinary = 0n; // should error but infer 0n const emptyOct = 0n; // should error but infer 0n diff --git a/tests/baselines/reference/parseBigInt.symbols b/tests/baselines/reference/parseBigInt.symbols index 4649d7d678a..89ce1a8173b 100644 --- a/tests/baselines/reference/parseBigInt.symbols +++ b/tests/baselines/reference/parseBigInt.symbols @@ -131,15 +131,12 @@ const unaryPlusHex = +0x123n; { const scientific = 1e2n; } >scientific : Symbol(scientific, Decl(parseBigInt.ts, 56, 7)) ->n : Symbol(n, Decl(parseBigInt.ts, 56, 24)) { const decimal = 4.1n; } >decimal : Symbol(decimal, Decl(parseBigInt.ts, 57, 7)) ->n : Symbol(n, Decl(parseBigInt.ts, 57, 21)) { const leadingDecimal = .1n; } >leadingDecimal : Symbol(leadingDecimal, Decl(parseBigInt.ts, 58, 7)) ->n : Symbol(n, Decl(parseBigInt.ts, 58, 27)) const emptyBinary = 0bn; // should error but infer 0n >emptyBinary : Symbol(emptyBinary, Decl(parseBigInt.ts, 59, 5)) diff --git a/tests/baselines/reference/parseBigInt.types b/tests/baselines/reference/parseBigInt.types index cbf80b69db0..98a8b0d9c74 100644 --- a/tests/baselines/reference/parseBigInt.types +++ b/tests/baselines/reference/parseBigInt.types @@ -180,18 +180,15 @@ const unaryPlusHex = +0x123n; { const scientific = 1e2n; } >scientific : 100 ->1e2 : 100 ->n : any +>1e2n : 100 { const decimal = 4.1n; } >decimal : 4.1 ->4.1 : 4.1 ->n : any +>4.1n : 4.1 { const leadingDecimal = .1n; } >leadingDecimal : 0.1 ->.1 : 0.1 ->n : any +>.1n : 0.1 const emptyBinary = 0bn; // should error but infer 0n >emptyBinary : 0n From 4f7184a587bfe75c12b5af36dabb34dc42276dee Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 18 Dec 2018 11:34:02 -0800 Subject: [PATCH 055/120] Fix all new lint failures (#29080) --- src/compiler/binder.ts | 2 +- src/compiler/core.ts | 4 ++-- src/compiler/utilities.ts | 2 +- src/harness/fourslash.ts | 2 +- src/harness/harness.ts | 6 ++---- src/services/refactors/extractSymbol.ts | 6 ++---- src/tsserver/server.ts | 15 +++++---------- 7 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7cb321a95fa..21bc30cfdc8 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -143,7 +143,7 @@ namespace ts { let symbolCount = 0; - let Symbol: { new (flags: SymbolFlags, name: __String): Symbol }; // tslint:disable-line variable-name + let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; // tslint:disable-line variable-name let classifiableNames: UnderscoreEscapedMap; const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 322355117ec..cb9e385b366 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -112,13 +112,13 @@ namespace ts { } // The global Map object. This may not be available, so we must test for it. - declare const Map: { new (): Map } | undefined; + declare const Map: (new () => Map) | undefined; // Internet Explorer's Map doesn't support iteration, so don't use it. // tslint:disable-next-line no-in-operator variable-name export const MapCtr = typeof Map !== "undefined" && "entries" in Map.prototype ? Map : shimMap(); // Keep the class inside a function so it doesn't get compiled if it's not used. - function shimMap(): { new (): Map } { + function shimMap(): new () => Map { class MapIterator { private data: MapLike; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 966149f2a6c..69f63a4a303 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6925,7 +6925,7 @@ namespace ts { export interface ObjectAllocator { getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node; - getTokenConstructor(): { new (kind: TKind, pos?: number, end?: number): Token }; + getTokenConstructor(): new (kind: TKind, pos?: number, end?: number) => Token; getIdentifierConstructor(): new (kind: SyntaxKind.Identifier, pos?: number, end?: number) => Identifier; getSourceFileConstructor(): new (kind: SyntaxKind.SourceFile, pos?: number, end?: number) => SourceFile; getSymbolConstructor(): new (flags: SymbolFlags, name: __String) => Symbol; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index b02aff523d1..db1c326a504 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -600,7 +600,7 @@ namespace FourSlash { throw new Error("Expected exactly one output from emit of " + this.activeFile.fileName); } - const evaluation = new Function(`${emit.outputFiles[0].text};\r\nreturn (${expr});`)(); + const evaluation = new Function(`${emit.outputFiles[0].text};\r\nreturn (${expr});`)(); // tslint:disable-line:function-constructor if (evaluation !== value) { this.raiseError(`Expected evaluation of expression "${expr}" to equal "${value}", but got "${evaluation}"`); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index fc275529258..13ef2adbf90 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -28,12 +28,10 @@ var assert: typeof _chai.assert = _chai.assert; }; } -var global: NodeJS.Global = Function("return this").call(undefined); +var global: NodeJS.Global = Function("return this").call(undefined); // tslint:disable-line:function-constructor declare var window: {}; -declare var XMLHttpRequest: { - new(): XMLHttpRequest; -}; +declare var XMLHttpRequest: new() => XMLHttpRequest; interface XMLHttpRequest { readonly readyState: number; readonly responseText: string; diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index c1915a66431..823bd640a59 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -449,8 +449,7 @@ namespace ts.refactor.extractSymbol { case SyntaxKind.ThisKeyword: rangeFacts |= RangeFacts.UsesThis; break; - case SyntaxKind.LabeledStatement: - { + case SyntaxKind.LabeledStatement: { const label = (node).label; (seenLabels || (seenLabels = [])).push(label.escapedText); forEachChild(node, visit); @@ -458,8 +457,7 @@ namespace ts.refactor.extractSymbol { break; } case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - { + case SyntaxKind.ContinueStatement: { const label = (node).label; if (label) { if (!contains(seenLabels, label.escapedText)) { diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 66574ceaac1..e5820260d5f 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -388,8 +388,7 @@ namespace ts.server { this.inspectValuePromise!.resolve(response.result); this.inspectValuePromise = undefined; break; - case EventInitializationFailed: - { + case EventInitializationFailed: { const body: protocol.TypesInstallerInitializationFailedEventBody = { message: response.message }; @@ -397,8 +396,7 @@ namespace ts.server { this.event(body, eventName); break; } - case EventBeginInstallTypes: - { + case EventBeginInstallTypes: { const body: protocol.BeginInstallTypesEventBody = { eventId: response.eventId, packages: response.packagesToInstall, @@ -407,8 +405,7 @@ namespace ts.server { this.event(body, eventName); break; } - case EventEndInstallTypes: - { + case EventEndInstallTypes: { if (this.telemetryEnabled) { const body: protocol.TypingsInstalledTelemetryEventBody = { telemetryEventName: "typingsInstalled", @@ -431,13 +428,11 @@ namespace ts.server { this.event(body, eventName); break; } - case ActionInvalidate: - { + case ActionInvalidate: { this.projectService.updateTypingsForProject(response); break; } - case ActionSet: - { + case ActionSet: { if (this.activeRequestCount > 0) { this.activeRequestCount--; } From 67491414f1d343856c0fa8b032bd64b92566f3a5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 18 Dec 2018 11:50:29 -0800 Subject: [PATCH 056/120] Handle intersection types in getWidenedType --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5f1515b94a..c751c37c6d5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13597,6 +13597,9 @@ namespace ts { // union includes empty object types (e.g. reducing {} | string to just {}). return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((type).types, getWidenedType)); + } if (isArrayType(type) || isTupleType(type)) { return createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType)); } From 519c501ea5d4830c5d6d4458a556e2083e2c03ce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 18 Dec 2018 11:58:06 -0800 Subject: [PATCH 057/120] Add regression test --- .../cases/conformance/types/literal/literalTypeWidening.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/cases/conformance/types/literal/literalTypeWidening.ts b/tests/cases/conformance/types/literal/literalTypeWidening.ts index 4c818793142..17daedb3b0e 100644 --- a/tests/cases/conformance/types/literal/literalTypeWidening.ts +++ b/tests/cases/conformance/types/literal/literalTypeWidening.ts @@ -127,3 +127,10 @@ export type LangCode = keyof typeof langCodeSet export const langCodes = keys(langCodeSet) const arr: Obj[] = langCodes.map(code => ({ code })) + +// Repro from #29081 + +function test(obj: T): T { + let { a, ...rest } = obj; + return { a: 'hello', ...rest } as T; +} From 7c2f4b3be826ac180f0b0c4271c003062c192dad Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 18 Dec 2018 11:58:12 -0800 Subject: [PATCH 058/120] Accept new baselines --- .../reference/literalTypeWidening.js | 32 +++++++++++++++++++ .../reference/literalTypeWidening.symbols | 22 +++++++++++++ .../reference/literalTypeWidening.types | 21 ++++++++++++ 3 files changed, 75 insertions(+) diff --git a/tests/baselines/reference/literalTypeWidening.js b/tests/baselines/reference/literalTypeWidening.js index fc805ac6706..93655fe0fc9 100644 --- a/tests/baselines/reference/literalTypeWidening.js +++ b/tests/baselines/reference/literalTypeWidening.js @@ -128,11 +128,38 @@ export type LangCode = keyof typeof langCodeSet export const langCodes = keys(langCodeSet) const arr: Obj[] = langCodes.map(code => ({ code })) + +// Repro from #29081 + +function test(obj: T): T { + let { a, ...rest } = obj; + return { a: 'hello', ...rest } as T; +} //// [literalTypeWidening.js] "use strict"; // Widening vs. non-widening literal types +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +}; exports.__esModule = true; function f1() { var c1 = "hello"; // Widening type "hello" @@ -233,3 +260,8 @@ exports.keys = keys; var langCodeSet = Set('fr', 'en', 'es', 'it', 'nl'); exports.langCodes = keys(langCodeSet); var arr = exports.langCodes.map(function (code) { return ({ code: code }); }); +// Repro from #29081 +function test(obj) { + var a = obj.a, rest = __rest(obj, ["a"]); + return __assign({ a: 'hello' }, rest); +} diff --git a/tests/baselines/reference/literalTypeWidening.symbols b/tests/baselines/reference/literalTypeWidening.symbols index 09b9164c199..4ce9c9a9468 100644 --- a/tests/baselines/reference/literalTypeWidening.symbols +++ b/tests/baselines/reference/literalTypeWidening.symbols @@ -400,3 +400,25 @@ const arr: Obj[] = langCodes.map(code => ({ code })) >code : Symbol(code, Decl(literalTypeWidening.ts, 128, 33)) >code : Symbol(code, Decl(literalTypeWidening.ts, 128, 43)) +// Repro from #29081 + +function test(obj: T): T { +>test : Symbol(test, Decl(literalTypeWidening.ts, 128, 52)) +>T : Symbol(T, Decl(literalTypeWidening.ts, 132, 14)) +>a : Symbol(a, Decl(literalTypeWidening.ts, 132, 25)) +>b : Symbol(b, Decl(literalTypeWidening.ts, 132, 36)) +>obj : Symbol(obj, Decl(literalTypeWidening.ts, 132, 50)) +>T : Symbol(T, Decl(literalTypeWidening.ts, 132, 14)) +>T : Symbol(T, Decl(literalTypeWidening.ts, 132, 14)) + + let { a, ...rest } = obj; +>a : Symbol(a, Decl(literalTypeWidening.ts, 133, 9)) +>rest : Symbol(rest, Decl(literalTypeWidening.ts, 133, 12)) +>obj : Symbol(obj, Decl(literalTypeWidening.ts, 132, 50)) + + return { a: 'hello', ...rest } as T; +>a : Symbol(a, Decl(literalTypeWidening.ts, 134, 12)) +>rest : Symbol(rest, Decl(literalTypeWidening.ts, 133, 12)) +>T : Symbol(T, Decl(literalTypeWidening.ts, 132, 14)) +} + diff --git a/tests/baselines/reference/literalTypeWidening.types b/tests/baselines/reference/literalTypeWidening.types index 51813d726f1..a96ddc8c5da 100644 --- a/tests/baselines/reference/literalTypeWidening.types +++ b/tests/baselines/reference/literalTypeWidening.types @@ -433,3 +433,24 @@ const arr: Obj[] = langCodes.map(code => ({ code })) >{ code } : { code: "fr" | "en" | "es" | "it" | "nl"; } >code : "fr" | "en" | "es" | "it" | "nl" +// Repro from #29081 + +function test(obj: T): T { +>test : (obj: T) => T +>a : string +>b : string +>obj : T + + let { a, ...rest } = obj; +>a : string +>rest : Pick> +>obj : T + + return { a: 'hello', ...rest } as T; +>{ a: 'hello', ...rest } as T : T +>{ a: 'hello', ...rest } : { a: string; } & Pick> +>a : string +>'hello' : "hello" +>rest : Pick> +} + From 4e3bc9e35a155f52eef67e2ce49af46416f124b9 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 18 Dec 2018 12:31:53 -0800 Subject: [PATCH 059/120] Reset constEnumOnlyModule flag on relevant symbol merges in binder (#29079) --- src/compiler/binder.ts | 5 +++ ...tClassMergedWithConstNamespaceNotElided.js | 25 +++++++++++ ...sMergedWithConstNamespaceNotElided.symbols | 37 +++++++++++++++++ ...assMergedWithConstNamespaceNotElided.types | 41 +++++++++++++++++++ ...tClassMergedWithConstNamespaceNotElided.ts | 18 ++++++++ 5 files changed, 126 insertions(+) create mode 100644 tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.js create mode 100644 tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.symbols create mode 100644 tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.types create mode 100644 tests/cases/compiler/emitClassMergedWithConstNamespaceNotElided.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 21bc30cfdc8..7216af4e817 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -233,6 +233,11 @@ namespace ts { symbol.members = createSymbolTable(); } + // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) + if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { + symbol.constEnumOnlyModule = false; + } + if (symbolFlags & SymbolFlags.Value) { setValueDeclaration(symbol, node); } diff --git a/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.js b/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.js new file mode 100644 index 00000000000..953f5c1508d --- /dev/null +++ b/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.js @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/emitClassMergedWithConstNamespaceNotElided.ts] //// + +//// [enum.d.ts] +export namespace Clone { + const enum LOCAL { + AUTO = 0, + LOCAL = 1, + NO_LOCAL = 2, + NO_LINKS = 3 + } +} + +export class Clone { + static clone(url: string): void; +} +//// [usage.ts] +import {Clone} from "./enum"; + +Clone.clone("ok"); + +//// [usage.js] +"use strict"; +exports.__esModule = true; +var enum_1 = require("./enum"); +enum_1.Clone.clone("ok"); diff --git a/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.symbols b/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.symbols new file mode 100644 index 00000000000..a6a624d5e61 --- /dev/null +++ b/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.symbols @@ -0,0 +1,37 @@ +=== tests/cases/compiler/enum.d.ts === +export namespace Clone { +>Clone : Symbol(Clone, Decl(enum.d.ts, 0, 0), Decl(enum.d.ts, 7, 1)) + + const enum LOCAL { +>LOCAL : Symbol(LOCAL, Decl(enum.d.ts, 0, 24)) + + AUTO = 0, +>AUTO : Symbol(LOCAL.AUTO, Decl(enum.d.ts, 1, 22)) + + LOCAL = 1, +>LOCAL : Symbol(LOCAL.LOCAL, Decl(enum.d.ts, 2, 17)) + + NO_LOCAL = 2, +>NO_LOCAL : Symbol(LOCAL.NO_LOCAL, Decl(enum.d.ts, 3, 18)) + + NO_LINKS = 3 +>NO_LINKS : Symbol(LOCAL.NO_LINKS, Decl(enum.d.ts, 4, 21)) + } +} + +export class Clone { +>Clone : Symbol(Clone, Decl(enum.d.ts, 0, 0), Decl(enum.d.ts, 7, 1)) + + static clone(url: string): void; +>clone : Symbol(Clone.clone, Decl(enum.d.ts, 9, 20)) +>url : Symbol(url, Decl(enum.d.ts, 10, 17)) +} +=== tests/cases/compiler/usage.ts === +import {Clone} from "./enum"; +>Clone : Symbol(Clone, Decl(usage.ts, 0, 8)) + +Clone.clone("ok"); +>Clone.clone : Symbol(Clone.clone, Decl(enum.d.ts, 9, 20)) +>Clone : Symbol(Clone, Decl(usage.ts, 0, 8)) +>clone : Symbol(Clone.clone, Decl(enum.d.ts, 9, 20)) + diff --git a/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.types b/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.types new file mode 100644 index 00000000000..582c128d608 --- /dev/null +++ b/tests/baselines/reference/emitClassMergedWithConstNamespaceNotElided.types @@ -0,0 +1,41 @@ +=== tests/cases/compiler/enum.d.ts === +export namespace Clone { + const enum LOCAL { +>LOCAL : LOCAL + + AUTO = 0, +>AUTO : LOCAL.AUTO +>0 : 0 + + LOCAL = 1, +>LOCAL : LOCAL.LOCAL +>1 : 1 + + NO_LOCAL = 2, +>NO_LOCAL : LOCAL.NO_LOCAL +>2 : 2 + + NO_LINKS = 3 +>NO_LINKS : LOCAL.NO_LINKS +>3 : 3 + } +} + +export class Clone { +>Clone : Clone + + static clone(url: string): void; +>clone : (url: string) => void +>url : string +} +=== tests/cases/compiler/usage.ts === +import {Clone} from "./enum"; +>Clone : typeof Clone + +Clone.clone("ok"); +>Clone.clone("ok") : void +>Clone.clone : (url: string) => void +>Clone : typeof Clone +>clone : (url: string) => void +>"ok" : "ok" + diff --git a/tests/cases/compiler/emitClassMergedWithConstNamespaceNotElided.ts b/tests/cases/compiler/emitClassMergedWithConstNamespaceNotElided.ts new file mode 100644 index 00000000000..e34dcc33463 --- /dev/null +++ b/tests/cases/compiler/emitClassMergedWithConstNamespaceNotElided.ts @@ -0,0 +1,18 @@ +// @esModuleInterop: true +// @filename: enum.d.ts +export namespace Clone { + const enum LOCAL { + AUTO = 0, + LOCAL = 1, + NO_LOCAL = 2, + NO_LINKS = 3 + } +} + +export class Clone { + static clone(url: string): void; +} +// @filename: usage.ts +import {Clone} from "./enum"; + +Clone.clone("ok"); \ No newline at end of file From c05bfb774f7edc46e85d402ef64c0c959635f149 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 18 Dec 2018 15:21:44 -0800 Subject: [PATCH 060/120] Adjust travis branch spec to capture all release branches (#29086) --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 20b47dbe30c..0f720b7375e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,11 +16,7 @@ matrix: branches: only: - master - - release-2.7 - - release-2.8 - - release-2.9 - - release-3.0 - - release-3.1 + - /^release-.*/ install: - npm uninstall typescript --no-save From fd7fd133e28908b8911389a9acca53ac41a288a0 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 18 Dec 2018 16:41:02 -0800 Subject: [PATCH 061/120] Fix the up-to-date-ness checks of sucessive `gulp` invocations (#29088) --- scripts/build/project.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/build/project.js b/scripts/build/project.js index 0375faa2820..6c1ac0f3e04 100644 --- a/scripts/build/project.js +++ b/scripts/build/project.js @@ -683,11 +683,17 @@ function ensureCompileTask(projectGraph, options) { } }); } - const js = (projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) : stream.js) + + const additionalJsOutputs = projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) + .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined; + const additionalDtsOutputs = projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) + .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined; + + const js = stream.js .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - const dts = (projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) : stream.dts) + const dts = stream.dts .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - return merge2([js, dts]) + return merge2([js, dts, additionalJsOutputs, additionalDtsOutputs].filter(x => !!x)) .pipe(gulp.dest(destPath)); }); } From d23effc200cc31b464dfce5096860d484d19694d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 18 Dec 2018 17:02:34 -0800 Subject: [PATCH 062/120] Non-latebound computed property names should print as their underlying name type (#29084) --- src/compiler/checker.ts | 35 ++++++++++----- ...clarationEmitComputedNameConstEnumAlias.js | 44 +++++++++++++++++++ ...tionEmitComputedNameConstEnumAlias.symbols | 23 ++++++++++ ...rationEmitComputedNameConstEnumAlias.types | 27 ++++++++++++ .../parserComputedPropertyName35.types | 2 +- ...clarationEmitComputedNameConstEnumAlias.ts | 14 ++++++ 6 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.js create mode 100644 tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.symbols create mode 100644 tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.types create mode 100644 tests/cases/compiler/declarationEmitComputedNameConstEnumAlias.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c751c37c6d5..787604b3d41 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4426,6 +4426,22 @@ namespace ts { return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); } + function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { + const nameType = symbol.nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType).value; + if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { + return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; + } + return name; + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return `[${getNameOfSymbolAsWritten((nameType).symbol, context)}]`; + } + } + } + /** * Gets a human-readable name for a symbol. * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. @@ -4450,6 +4466,13 @@ namespace ts { if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { return symbolName(symbol); } + if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late) && symbol.nameType && symbol.nameType.flags & TypeFlags.StringOrNumberLiteral) { + // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name + const result = getNameOfSymbolFromNameType(symbol, context); + if (result !== undefined) { + return result; + } + } return declarationNameToString(name); } if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { @@ -4465,16 +4488,8 @@ namespace ts { return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; } } - const nameType = symbol.nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringLiteral && !isIdentifierText((nameType).value, compilerOptions.target)) { - return `"${escapeString((nameType).value, CharacterCodes.doubleQuote)}"`; - } - if (nameType && nameType.flags & TypeFlags.UniqueESSymbol) { - return `[${getNameOfSymbolAsWritten((nameType).symbol, context)}]`; - } - } - return symbolName(symbol); + const name = getNameOfSymbolFromNameType(symbol, context); + return name !== undefined ? name : symbolName(symbol); } function isDeclarationVisible(node: Node): boolean { diff --git a/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.js b/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.js new file mode 100644 index 00000000000..461eb21087e --- /dev/null +++ b/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/declarationEmitComputedNameConstEnumAlias.ts] //// + +//// [EnumExample.ts] +enum EnumExample { + TEST = 'TEST', +} + +export default EnumExample; + +//// [index.ts] +import EnumExample from './EnumExample'; + +export default { + [EnumExample.TEST]: {}, +}; + +//// [EnumExample.js] +"use strict"; +exports.__esModule = true; +var EnumExample; +(function (EnumExample) { + EnumExample["TEST"] = "TEST"; +})(EnumExample || (EnumExample = {})); +exports["default"] = EnumExample; +//// [index.js] +"use strict"; +exports.__esModule = true; +var _a; +var EnumExample_1 = require("./EnumExample"); +exports["default"] = (_a = {}, + _a[EnumExample_1["default"].TEST] = {}, + _a); + + +//// [EnumExample.d.ts] +declare enum EnumExample { + TEST = "TEST" +} +export default EnumExample; +//// [index.d.ts] +declare const _default: { + TEST: {}; +}; +export default _default; diff --git a/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.symbols b/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.symbols new file mode 100644 index 00000000000..fa5fc6aa482 --- /dev/null +++ b/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.symbols @@ -0,0 +1,23 @@ +=== tests/cases/compiler/EnumExample.ts === +enum EnumExample { +>EnumExample : Symbol(EnumExample, Decl(EnumExample.ts, 0, 0)) + + TEST = 'TEST', +>TEST : Symbol(EnumExample.TEST, Decl(EnumExample.ts, 0, 18)) +} + +export default EnumExample; +>EnumExample : Symbol(EnumExample, Decl(EnumExample.ts, 0, 0)) + +=== tests/cases/compiler/index.ts === +import EnumExample from './EnumExample'; +>EnumExample : Symbol(EnumExample, Decl(index.ts, 0, 6)) + +export default { + [EnumExample.TEST]: {}, +>[EnumExample.TEST] : Symbol([EnumExample.TEST], Decl(index.ts, 2, 16)) +>EnumExample.TEST : Symbol(EnumExample.TEST, Decl(EnumExample.ts, 0, 18)) +>EnumExample : Symbol(EnumExample, Decl(index.ts, 0, 6)) +>TEST : Symbol(EnumExample.TEST, Decl(EnumExample.ts, 0, 18)) + +}; diff --git a/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.types b/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.types new file mode 100644 index 00000000000..97be9ac5a1b --- /dev/null +++ b/tests/baselines/reference/declarationEmitComputedNameConstEnumAlias.types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/EnumExample.ts === +enum EnumExample { +>EnumExample : EnumExample + + TEST = 'TEST', +>TEST : EnumExample.TEST +>'TEST' : "TEST" +} + +export default EnumExample; +>EnumExample : EnumExample + +=== tests/cases/compiler/index.ts === +import EnumExample from './EnumExample'; +>EnumExample : typeof EnumExample + +export default { +>{ [EnumExample.TEST]: {},} : { [EnumExample.TEST]: {}; } + + [EnumExample.TEST]: {}, +>[EnumExample.TEST] : {} +>EnumExample.TEST : EnumExample +>EnumExample : typeof EnumExample +>TEST : EnumExample +>{} : {} + +}; diff --git a/tests/baselines/reference/parserComputedPropertyName35.types b/tests/baselines/reference/parserComputedPropertyName35.types index bfaaca1f942..6c4a9c785e5 100644 --- a/tests/baselines/reference/parserComputedPropertyName35.types +++ b/tests/baselines/reference/parserComputedPropertyName35.types @@ -1,6 +1,6 @@ === tests/cases/conformance/parser/ecmascript6/ComputedPropertyNames/parserComputedPropertyName35.ts === var x = { ->x : { [0, 1]: {}; } +>x : { 1: {}; } >{ [0, 1]: { }} : { [0, 1]: {}; } [0, 1]: { } diff --git a/tests/cases/compiler/declarationEmitComputedNameConstEnumAlias.ts b/tests/cases/compiler/declarationEmitComputedNameConstEnumAlias.ts new file mode 100644 index 00000000000..951ec0b0a9d --- /dev/null +++ b/tests/cases/compiler/declarationEmitComputedNameConstEnumAlias.ts @@ -0,0 +1,14 @@ +// @declaration: true +// @filename: EnumExample.ts +enum EnumExample { + TEST = 'TEST', +} + +export default EnumExample; + +// @filename: index.ts +import EnumExample from './EnumExample'; + +export default { + [EnumExample.TEST]: {}, +}; \ No newline at end of file From 676338971d4bd4f5adb18557486c85eab7d1aad4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 19 Dec 2018 08:45:18 -0800 Subject: [PATCH 063/120] Improve error message for out-of-bounds tuple element access --- src/compiler/checker.ts | 8 +++++++- src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b5c477068a4..b2b34d7aec1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9609,7 +9609,13 @@ namespace ts { if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { if (accessNode && everyType(objectType, t => !(t).target.hasRestElement)) { const indexNode = getIndexNodeForAccessExpression(accessNode); - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + if (isTupleType(objectType)) { + error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, + typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); + } + else { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + } } return mapType(objectType, t => getRestTypeOfTupleType(t) || undefinedType); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 73ae0b69022..94142196614 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1752,6 +1752,10 @@ "category": "Error", "code": 2492 }, + "Tuple type '{0}' of length '{1}' has no element at index '{2}'.": { + "category": "Error", + "code": 2493 + }, "Using a string in a 'for...of' statement is only supported in ECMAScript 5 and higher.": { "category": "Error", "code": 2494 From 43d92f0525e1241348ccd7027f05676f90d02b08 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 19 Dec 2018 08:45:27 -0800 Subject: [PATCH 064/120] Accept new baselines --- .../arityAndOrderCompatibility01.errors.txt | 4 ++-- .../bestCommonTypeOfTuple.errors.txt | 16 +++++++-------- .../bestCommonTypeOfTuple2.errors.txt | 20 +++++++++---------- .../reference/castingTuple.errors.txt | 4 ++-- ...turingThisInTupleDestructuring1.errors.txt | 8 ++++---- ...turingThisInTupleDestructuring2.errors.txt | 4 ++-- .../emptyTuplesTypeAssertion01.errors.txt | 4 ++-- .../emptyTuplesTypeAssertion02.errors.txt | 4 ++-- .../genericCallWithTupleType.errors.txt | 12 +++++------ .../reference/indexerWithTuple.errors.txt | 16 +++++++-------- .../reference/tupleLengthCheck.errors.txt | 8 ++++---- .../baselines/reference/tupleTypes.errors.txt | 8 ++++---- .../reference/unionsOfTupleTypes1.errors.txt | 16 +++++++-------- 13 files changed, 62 insertions(+), 62 deletions(-) diff --git a/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt b/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt index 5eeaef51566..b34b8896919 100644 --- a/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt +++ b/tests/baselines/reference/arityAndOrderCompatibility01.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(15,12): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(15,12): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(17,5): error TS2461: Type '{ 0: string; 1: number; length: 2; }' is not an array type. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(18,5): error TS2741: Property '2' is missing in type '[string, number]' but required in type '[number, number, number]'. tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(19,5): error TS2741: Property '2' is missing in type 'StrNum' but required in type '[number, number, number]'. @@ -45,7 +45,7 @@ tests/cases/conformance/types/tuple/arityAndOrderCompatibility01.ts(32,5): error var [a, b, c] = x; ~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. var [d, e, f] = y; var [g, h, i] = z; ~~~~~~~~~ diff --git a/tests/baselines/reference/bestCommonTypeOfTuple.errors.txt b/tests/baselines/reference/bestCommonTypeOfTuple.errors.txt index e3c5dcc7986..0349ce1ebe4 100644 --- a/tests/baselines/reference/bestCommonTypeOfTuple.errors.txt +++ b/tests/baselines/reference/bestCommonTypeOfTuple.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(22,13): error TS2339: Property '2' does not exist on type '[(x: number) => string, (x: number) => number]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(23,13): error TS2339: Property '2' does not exist on type '[E1, E2]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(24,13): error TS2339: Property '2' does not exist on type '[number, any]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(25,13): error TS2339: Property '3' does not exist on type '[E1, E2, number]'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(22,13): error TS2493: Tuple type '[(x: number) => string, (x: number) => number]' of length '2' has no element at index '2'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(23,13): error TS2493: Tuple type '[E1, E2]' of length '2' has no element at index '2'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(24,13): error TS2493: Tuple type '[number, any]' of length '2' has no element at index '2'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts(25,13): error TS2493: Tuple type '[E1, E2, number]' of length '3' has no element at index '3'. ==== tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple.ts (4 errors) ==== @@ -28,13 +28,13 @@ tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfT t4 = [E1.one, E2.two, 20]; var e1 = t1[2]; // {} ~ -!!! error TS2339: Property '2' does not exist on type '[(x: number) => string, (x: number) => number]'. +!!! error TS2493: Tuple type '[(x: number) => string, (x: number) => number]' of length '2' has no element at index '2'. var e2 = t2[2]; // {} ~ -!!! error TS2339: Property '2' does not exist on type '[E1, E2]'. +!!! error TS2493: Tuple type '[E1, E2]' of length '2' has no element at index '2'. var e3 = t3[2]; // any ~ -!!! error TS2339: Property '2' does not exist on type '[number, any]'. +!!! error TS2493: Tuple type '[number, any]' of length '2' has no element at index '2'. var e4 = t4[3]; // number ~ -!!! error TS2339: Property '3' does not exist on type '[E1, E2, number]'. \ No newline at end of file +!!! error TS2493: Tuple type '[E1, E2, number]' of length '3' has no element at index '3'. \ No newline at end of file diff --git a/tests/baselines/reference/bestCommonTypeOfTuple2.errors.txt b/tests/baselines/reference/bestCommonTypeOfTuple2.errors.txt index d2fd301e131..602e9fdeef2 100644 --- a/tests/baselines/reference/bestCommonTypeOfTuple2.errors.txt +++ b/tests/baselines/reference/bestCommonTypeOfTuple2.errors.txt @@ -1,8 +1,8 @@ -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(17,14): error TS2339: Property '4' does not exist on type '[C, base]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(18,14): error TS2339: Property '4' does not exist on type '[C, D]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(19,14): error TS2339: Property '4' does not exist on type '[C1, D1]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(20,14): error TS2339: Property '2' does not exist on type '[base1, C1]'. -tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(21,14): error TS2339: Property '2' does not exist on type '[C1, F]'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(17,14): error TS2493: Tuple type '[C, base]' of length '2' has no element at index '4'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(18,14): error TS2493: Tuple type '[C, D]' of length '2' has no element at index '4'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(19,14): error TS2493: Tuple type '[C1, D1]' of length '2' has no element at index '4'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(20,14): error TS2493: Tuple type '[base1, C1]' of length '2' has no element at index '2'. +tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts(21,14): error TS2493: Tuple type '[C1, F]' of length '2' has no element at index '2'. ==== tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfTuple2.ts (5 errors) ==== @@ -24,17 +24,17 @@ tests/cases/conformance/types/typeRelationships/bestCommonType/bestCommonTypeOfT var e11 = t1[4]; // base ~ -!!! error TS2339: Property '4' does not exist on type '[C, base]'. +!!! error TS2493: Tuple type '[C, base]' of length '2' has no element at index '4'. var e21 = t2[4]; // {} ~ -!!! error TS2339: Property '4' does not exist on type '[C, D]'. +!!! error TS2493: Tuple type '[C, D]' of length '2' has no element at index '4'. var e31 = t3[4]; // C1 ~ -!!! error TS2339: Property '4' does not exist on type '[C1, D1]'. +!!! error TS2493: Tuple type '[C1, D1]' of length '2' has no element at index '4'. var e41 = t4[2]; // base1 ~ -!!! error TS2339: Property '2' does not exist on type '[base1, C1]'. +!!! error TS2493: Tuple type '[base1, C1]' of length '2' has no element at index '2'. var e51 = t5[2]; // {} ~ -!!! error TS2339: Property '2' does not exist on type '[C1, F]'. +!!! error TS2493: Tuple type '[C1, F]' of length '2' has no element at index '2'. \ No newline at end of file diff --git a/tests/baselines/reference/castingTuple.errors.txt b/tests/baselines/reference/castingTuple.errors.txt index 4587612cb76..4ddb42ed2d9 100644 --- a/tests/baselines/reference/castingTuple.errors.txt +++ b/tests/baselines/reference/castingTuple.errors.txt @@ -6,7 +6,7 @@ tests/cases/conformance/types/tuple/castingTuple.ts(14,15): error TS2352: Conver tests/cases/conformance/types/tuple/castingTuple.ts(15,14): error TS2352: Conversion of type '[number, string]' to type '[number, string, boolean]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. tests/cases/conformance/types/tuple/castingTuple.ts(18,21): error TS2352: Conversion of type '[C, D]' to type '[C, D, A]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Property '2' is missing in type '[C, D]' but required in type '[C, D, A]'. -tests/cases/conformance/types/tuple/castingTuple.ts(20,33): error TS2339: Property '5' does not exist on type '[C, D, A]'. +tests/cases/conformance/types/tuple/castingTuple.ts(20,33): error TS2493: Tuple type '[C, D, A]' of length '3' has no element at index '5'. tests/cases/conformance/types/tuple/castingTuple.ts(30,10): error TS2352: Conversion of type '[number, string]' to type '[number, number]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'string' is not comparable to type 'number'. tests/cases/conformance/types/tuple/castingTuple.ts(31,10): error TS2352: Conversion of type '[C, D]' to type '[A, I]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. @@ -49,7 +49,7 @@ tests/cases/conformance/types/tuple/castingTuple.ts(33,1): error TS2304: Cannot var eleFromCDA1 = classCDATuple[2]; // A var eleFromCDA2 = classCDATuple[5]; // C | D | A ~ -!!! error TS2339: Property '5' does not exist on type '[C, D, A]'. +!!! error TS2493: Tuple type '[C, D, A]' of length '3' has no element at index '5'. var t10: [E1, E2] = [E1.one, E2.one]; var t11 = <[number, number]>t10; var array1 = <{}[]>emptyObjTuple; diff --git a/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt b/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt index 8fba60b9cd0..5b8f2346ee9 100644 --- a/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt +++ b/tests/baselines/reference/emitCapturingThisInTupleDestructuring1.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,17): error TS2339: Property '1' does not exist on type '[any]'. -tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,29): error TS2339: Property '2' does not exist on type '[any]'. +tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,17): error TS2493: Tuple type '[any]' of length '1' has no element at index '1'. +tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,29): error TS2493: Tuple type '[any]' of length '1' has no element at index '2'. ==== tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts (2 errors) ==== @@ -7,7 +7,7 @@ tests/cases/compiler/emitCapturingThisInTupleDestructuring1.ts(3,29): error TS23 wrapper((array: [any]) => { [this.test, this.test1, this.test2] = array; // even though there is a compiler error, we should still emit lexical capture for "this" ~~~~~~~~~~ -!!! error TS2339: Property '1' does not exist on type '[any]'. +!!! error TS2493: Tuple type '[any]' of length '1' has no element at index '1'. ~~~~~~~~~~ -!!! error TS2339: Property '2' does not exist on type '[any]'. +!!! error TS2493: Tuple type '[any]' of length '1' has no element at index '2'. }); \ No newline at end of file diff --git a/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt b/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt index 22a8e1ca63e..5d14e2401c4 100644 --- a/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt +++ b/tests/baselines/reference/emitCapturingThisInTupleDestructuring2.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts(8,39): error TS2339: Property '2' does not exist on type '[number, number]'. +tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts(8,39): error TS2493: Tuple type '[number, number]' of length '2' has no element at index '2'. ==== tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts (1 errors) ==== @@ -11,6 +11,6 @@ tests/cases/compiler/emitCapturingThisInTupleDestructuring2.ts(8,39): error TS23 method() { () => [this.test, this.test1, this.test2] = array1; // even though there is a compiler error, we should still emit lexical capture for "this" ~~~~~~~~~~ -!!! error TS2339: Property '2' does not exist on type '[number, number]'. +!!! error TS2493: Tuple type '[number, number]' of length '2' has no element at index '2'. } } \ No newline at end of file diff --git a/tests/baselines/reference/emptyTuplesTypeAssertion01.errors.txt b/tests/baselines/reference/emptyTuplesTypeAssertion01.errors.txt index e3b857412cc..f1c4d76fe2b 100644 --- a/tests/baselines/reference/emptyTuplesTypeAssertion01.errors.txt +++ b/tests/baselines/reference/emptyTuplesTypeAssertion01.errors.txt @@ -1,8 +1,8 @@ -tests/cases/conformance/types/tuple/emptyTuples/emptyTuplesTypeAssertion01.ts(2,11): error TS2339: Property '0' does not exist on type '[]'. +tests/cases/conformance/types/tuple/emptyTuples/emptyTuplesTypeAssertion01.ts(2,11): error TS2493: Tuple type '[]' of length '0' has no element at index '0'. ==== tests/cases/conformance/types/tuple/emptyTuples/emptyTuplesTypeAssertion01.ts (1 errors) ==== let x = <[]>[]; let y = x[0]; ~ -!!! error TS2339: Property '0' does not exist on type '[]'. \ No newline at end of file +!!! error TS2493: Tuple type '[]' of length '0' has no element at index '0'. \ No newline at end of file diff --git a/tests/baselines/reference/emptyTuplesTypeAssertion02.errors.txt b/tests/baselines/reference/emptyTuplesTypeAssertion02.errors.txt index 34d22dc673a..64663106a1e 100644 --- a/tests/baselines/reference/emptyTuplesTypeAssertion02.errors.txt +++ b/tests/baselines/reference/emptyTuplesTypeAssertion02.errors.txt @@ -1,8 +1,8 @@ -tests/cases/conformance/types/tuple/emptyTuples/emptyTuplesTypeAssertion02.ts(2,11): error TS2339: Property '0' does not exist on type '[]'. +tests/cases/conformance/types/tuple/emptyTuples/emptyTuplesTypeAssertion02.ts(2,11): error TS2493: Tuple type '[]' of length '0' has no element at index '0'. ==== tests/cases/conformance/types/tuple/emptyTuples/emptyTuplesTypeAssertion02.ts (1 errors) ==== let x = [] as []; let y = x[0]; ~ -!!! error TS2339: Property '0' does not exist on type '[]'. \ No newline at end of file +!!! error TS2493: Tuple type '[]' of length '0' has no element at index '0'. \ No newline at end of file diff --git a/tests/baselines/reference/genericCallWithTupleType.errors.txt b/tests/baselines/reference/genericCallWithTupleType.errors.txt index ec3c40ccad9..0cc4a4986db 100644 --- a/tests/baselines/reference/genericCallWithTupleType.errors.txt +++ b/tests/baselines/reference/genericCallWithTupleType.errors.txt @@ -1,10 +1,10 @@ tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(12,1): error TS2322: Type '[string, number, boolean, boolean]' is not assignable to type '[string, number]'. Types of property 'length' are incompatible. Type '4' is not assignable to type '2'. -tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(13,20): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(13,20): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(14,1): error TS2322: Type '{ a: string; }' is not assignable to type 'undefined'. -tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(14,11): error TS2339: Property '3' does not exist on type '[string, number]'. -tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(15,20): error TS2339: Property '3' does not exist on type '[string, number]'. +tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(14,11): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '3'. +tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(15,20): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '3'. tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(22,14): error TS2322: Type 'number' is not assignable to type 'string'. tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(22,17): error TS2322: Type 'string' is not assignable to type 'number'. tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTupleType.ts(23,14): error TS2322: Type '{}' is not assignable to type 'string'. @@ -31,15 +31,15 @@ tests/cases/conformance/types/typeRelationships/typeInference/genericCallWithTup !!! error TS2322: Type '4' is not assignable to type '2'. var e3 = i1.tuple1[2]; // {} ~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. i1.tuple1[3] = { a: "string" }; ~~~~~~~~~~~~ !!! error TS2322: Type '{ a: string; }' is not assignable to type 'undefined'. ~ -!!! error TS2339: Property '3' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '3'. var e4 = i1.tuple1[3]; // {} ~ -!!! error TS2339: Property '3' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '3'. i2.tuple1 = ["foo", 5]; i2.tuple1 = ["foo", "bar"]; i2.tuple1 = [5, "bar"]; diff --git a/tests/baselines/reference/indexerWithTuple.errors.txt b/tests/baselines/reference/indexerWithTuple.errors.txt index 45cddd9313f..b5b09be4c49 100644 --- a/tests/baselines/reference/indexerWithTuple.errors.txt +++ b/tests/baselines/reference/indexerWithTuple.errors.txt @@ -1,7 +1,7 @@ -tests/cases/conformance/types/tuple/indexerWithTuple.ts(11,25): error TS2339: Property '2' does not exist on type '[string, number]'. -tests/cases/conformance/types/tuple/indexerWithTuple.ts(17,27): error TS2339: Property '2' does not exist on type '[number, [string, number]]'. -tests/cases/conformance/types/tuple/indexerWithTuple.ts(20,30): error TS2339: Property '2' does not exist on type '[number, string | number]'. -tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2339: Property '2' does not exist on type '[boolean, string | number]'. +tests/cases/conformance/types/tuple/indexerWithTuple.ts(11,25): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. +tests/cases/conformance/types/tuple/indexerWithTuple.ts(17,27): error TS2493: Tuple type '[number, [string, number]]' of length '2' has no element at index '2'. +tests/cases/conformance/types/tuple/indexerWithTuple.ts(20,30): error TS2493: Tuple type '[number, string | number]' of length '2' has no element at index '2'. +tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2493: Tuple type '[boolean, string | number]' of length '2' has no element at index '2'. ==== tests/cases/conformance/types/tuple/indexerWithTuple.ts (4 errors) ==== @@ -17,7 +17,7 @@ tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2339: Pr var ele11 = strNumTuple[1]; // number var ele12 = strNumTuple[2]; // string | number ~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. var ele13 = strNumTuple[idx0]; // string | number var ele14 = strNumTuple[idx1]; // string | number var ele15 = strNumTuple["0"]; // string @@ -25,12 +25,12 @@ tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2339: Pr var strNumTuple1 = numTupleTuple[1]; //[string, number]; var ele17 = numTupleTuple[2]; // number | [string, number] ~ -!!! error TS2339: Property '2' does not exist on type '[number, [string, number]]'. +!!! error TS2493: Tuple type '[number, [string, number]]' of length '2' has no element at index '2'. var eleUnion10 = unionTuple1[0]; // number var eleUnion11 = unionTuple1[1]; // string | number var eleUnion12 = unionTuple1[2]; // string | number ~ -!!! error TS2339: Property '2' does not exist on type '[number, string | number]'. +!!! error TS2493: Tuple type '[number, string | number]' of length '2' has no element at index '2'. var eleUnion13 = unionTuple1[idx0]; // string | number var eleUnion14 = unionTuple1[idx1]; // string | number var eleUnion15 = unionTuple1["0"]; // number @@ -40,7 +40,7 @@ tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2339: Pr var eleUnion21 = unionTuple2[1]; // string | number var eleUnion22 = unionTuple2[2]; // string | number | boolean ~ -!!! error TS2339: Property '2' does not exist on type '[boolean, string | number]'. +!!! error TS2493: Tuple type '[boolean, string | number]' of length '2' has no element at index '2'. var eleUnion23 = unionTuple2[idx0]; // string | number | boolean var eleUnion24 = unionTuple2[idx1]; // string | number | boolean var eleUnion25 = unionTuple2["0"]; // boolean diff --git a/tests/baselines/reference/tupleLengthCheck.errors.txt b/tests/baselines/reference/tupleLengthCheck.errors.txt index 1c6fe909135..06127460394 100644 --- a/tests/baselines/reference/tupleLengthCheck.errors.txt +++ b/tests/baselines/reference/tupleLengthCheck.errors.txt @@ -1,5 +1,5 @@ -tests/cases/conformance/types/tuple/tupleLengthCheck.ts(5,14): error TS2339: Property '2' does not exist on type '[number, string]'. -tests/cases/conformance/types/tuple/tupleLengthCheck.ts(6,14): error TS2339: Property '1000' does not exist on type '[number, string]'. +tests/cases/conformance/types/tuple/tupleLengthCheck.ts(5,14): error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'. +tests/cases/conformance/types/tuple/tupleLengthCheck.ts(6,14): error TS2493: Tuple type '[number, string]' of length '2' has no element at index '1000'. ==== tests/cases/conformance/types/tuple/tupleLengthCheck.ts (2 errors) ==== @@ -9,10 +9,10 @@ tests/cases/conformance/types/tuple/tupleLengthCheck.ts(6,14): error TS2339: Pro const a1 = a[1] const a2 = a[2] ~ -!!! error TS2339: Property '2' does not exist on type '[number, string]'. +!!! error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'. const a3 = a[1000] ~~~~ -!!! error TS2339: Property '1000' does not exist on type '[number, string]'. +!!! error TS2493: Tuple type '[number, string]' of length '2' has no element at index '1000'. const a4 = rest[1] const a5 = rest[2] diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index 3776a92d6a1..1cf2f113d14 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/tupleTypes.ts(11,12): error TS2339: Property '2' does not exist on type '[number, string]'. +tests/cases/compiler/tupleTypes.ts(11,12): error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'. tests/cases/compiler/tupleTypes.ts(12,5): error TS2403: Subsequent variable declarations must have the same type. Variable 't2' must be of type 'undefined', but here has type 'string | number'. tests/cases/compiler/tupleTypes.ts(14,1): error TS2739: Type '[]' is missing the following properties from type '[number, string]': 0, 1 tests/cases/compiler/tupleTypes.ts(15,1): error TS2741: Property '1' is missing in type '[number]' but required in type '[number, string]'. @@ -7,7 +7,7 @@ tests/cases/compiler/tupleTypes.ts(17,15): error TS2322: Type 'number' is not as tests/cases/compiler/tupleTypes.ts(18,1): error TS2322: Type '[number, string, number]' is not assignable to type '[number, string]'. Types of property 'length' are incompatible. Type '3' is not assignable to type '2'. -tests/cases/compiler/tupleTypes.ts(35,14): error TS2339: Property '2' does not exist on type '[number, string]'. +tests/cases/compiler/tupleTypes.ts(35,14): error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'. tests/cases/compiler/tupleTypes.ts(36,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'tt2' must be of type 'undefined', but here has type 'string | number'. tests/cases/compiler/tupleTypes.ts(41,1): error TS2322: Type '[]' is not assignable to type '[number, string]'. tests/cases/compiler/tupleTypes.ts(47,1): error TS2322: Type '[number, string]' is not assignable to type 'number[]'. @@ -35,7 +35,7 @@ tests/cases/compiler/tupleTypes.ts(51,1): error TS2322: Type '[number, {}]' is n var t1: string; var t2 = t[2]; // number|string ~ -!!! error TS2339: Property '2' does not exist on type '[number, string]'. +!!! error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'. var t2: number|string; ~~ !!! error TS2403: Subsequent variable declarations must have the same type. Variable 't2' must be of type 'undefined', but here has type 'string | number'. @@ -75,7 +75,7 @@ tests/cases/compiler/tupleTypes.ts(51,1): error TS2322: Type '[number, {}]' is n var tt1: string; var tt2 = tt[2]; ~ -!!! error TS2339: Property '2' does not exist on type '[number, string]'. +!!! error TS2493: Tuple type '[number, string]' of length '2' has no element at index '2'. var tt2: number | string; ~~~ !!! error TS2403: Subsequent variable declarations must have the same type. Variable 'tt2' must be of type 'undefined', but here has type 'string | number'. diff --git a/tests/baselines/reference/unionsOfTupleTypes1.errors.txt b/tests/baselines/reference/unionsOfTupleTypes1.errors.txt index 402dfcad340..da997e988ee 100644 --- a/tests/baselines/reference/unionsOfTupleTypes1.errors.txt +++ b/tests/baselines/reference/unionsOfTupleTypes1.errors.txt @@ -1,10 +1,10 @@ -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(8,15): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(8,15): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(13,15): error TS2339: Property '2' does not exist on type 'T2'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(27,20): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(27,20): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(28,20): error TS2339: Property '2' does not exist on type 'T2'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(31,16): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(31,16): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(32,16): error TS2339: Property '2' does not exist on type 'T2'. -tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(37,18): error TS2339: Property '2' does not exist on type '[string, number]'. +tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(37,18): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: Property '2' does not exist on type 'T2'. @@ -18,7 +18,7 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: type T11 = T1[1]; // number type T12 = T1[2]; // undefined ~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. type T1N = T1[number]; // string | number type T20 = T2[0]; // string | boolean @@ -41,7 +41,7 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: function f1(t1: T1, t2: T2, t3: T3, t4: T4, x: number) { let [d10, d11, d12] = t1; // string, number ~~~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. let [d20, d21, d22] = t2; // string | boolean, number | undefined ~~~ !!! error TS2339: Property '2' does not exist on type 'T2'. @@ -49,7 +49,7 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: let [d40, d41, d42] = t4; // string | boolean, number | undefined, number | undefined [d10, d11, d12] = t1; ~~~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. [d20, d21, d22] = t2; ~~~ !!! error TS2339: Property '2' does not exist on type 'T2'. @@ -59,7 +59,7 @@ tests/cases/conformance/types/tuple/unionsOfTupleTypes1.ts(41,18): error TS2339: let t11 = t1[1]; // number let t12 = t1[2]; // undefined ~ -!!! error TS2339: Property '2' does not exist on type '[string, number]'. +!!! error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'. let t1x = t1[x]; // string | number let t20 = t2[0]; // string | boolean let t21 = t2[1]; // number | undefined From 63273394e4b58a57fd4e604b6542d9dfab5ce299 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 19 Dec 2018 10:23:13 -0800 Subject: [PATCH 065/120] No public API changes --- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8047c31930e..8110f8fd17a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1745,6 +1745,7 @@ namespace ts { export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; + /* @internal */ export type AccessExpression = PropertyAccessExpression | ElementAccessExpression; export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fe0d8b84527..710ff6a6710 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4590,6 +4590,10 @@ namespace ts { || kind === SyntaxKind.JSDocFunctionType || kind === SyntaxKind.JSDocVariadicType; } + + export function isAccessExpression(node: Node): node is AccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression; + } } namespace ts { @@ -5586,10 +5590,6 @@ namespace ts { return node.kind === SyntaxKind.ElementAccessExpression; } - export function isAccessExpression(node: Node): node is AccessExpression { - return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression; - } - export function isCallExpression(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression; } From 2e6366fd1cf3b42670481a590398ee306dbac542 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 19 Dec 2018 10:23:45 -0800 Subject: [PATCH 066/120] Accept new baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 -- tests/baselines/reference/api/typescript.d.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index dfb7acf2aba..2b78178587e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1054,7 +1054,6 @@ declare namespace ts { } type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; - type AccessExpression = PropertyAccessExpression | ElementAccessExpression; interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; @@ -3351,7 +3350,6 @@ declare namespace ts { function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; function isElementAccessExpression(node: Node): node is ElementAccessExpression; - function isAccessExpression(node: Node): node is AccessExpression; function isCallExpression(node: Node): node is CallExpression; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 66d71f2080b..0e693f698f2 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1054,7 +1054,6 @@ declare namespace ts { } type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression; type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; - type AccessExpression = PropertyAccessExpression | ElementAccessExpression; interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; @@ -3351,7 +3350,6 @@ declare namespace ts { function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; function isElementAccessExpression(node: Node): node is ElementAccessExpression; - function isAccessExpression(node: Node): node is AccessExpression; function isCallExpression(node: Node): node is CallExpression; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; From 54352dc1c9bbbe528dd6df8295ef2478bc696684 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 19 Dec 2018 15:28:21 -0800 Subject: [PATCH 067/120] Added test case. --- ...DefaultExportClassExtendingExpression01.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/cases/compiler/declarationEmitForDefaultExportClassExtendingExpression01.ts diff --git a/tests/cases/compiler/declarationEmitForDefaultExportClassExtendingExpression01.ts b/tests/cases/compiler/declarationEmitForDefaultExportClassExtendingExpression01.ts new file mode 100644 index 00000000000..3c4abc37deb --- /dev/null +++ b/tests/cases/compiler/declarationEmitForDefaultExportClassExtendingExpression01.ts @@ -0,0 +1,21 @@ +// @declaration: true + +interface Greeter { + getGreeting(): string; +} + +interface GreeterConstructor { + new (): Greeter; +} + +class A { + getGreeting() { + return 'hello'; + } +} + +const getGreeterBase = (): GreeterConstructor => A; + +export default class extends getGreeterBase() { +} + From 9277c03666d71f92a498c500cf108f2b62199468 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 19 Dec 2018 15:42:15 -0800 Subject: [PATCH 068/120] Check whether class declarations have a name, falling back to "default". --- src/compiler/transformers/declarations.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 973b9db618e..b54cf5bb858 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -1100,7 +1100,8 @@ namespace ts { if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { // We must add a temporary declaration for the extends clause expression - const newId = createOptimisticUniqueName(`${unescapeLeadingUnderscores(input.name!.escapedText)}_base`); // TODO: GH#18217 + const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; + const newId = createOptimisticUniqueName(`${oldId}_base`); getSymbolAccessibilityDiagnostic = () => ({ diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, errorNode: extendsClause, From dec3fe619904e758bee152edf2086ec75fdac02d Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 19 Dec 2018 15:50:33 -0800 Subject: [PATCH 069/120] Accepted baselines. --- ...DefaultExportClassExtendingExpression01.js | 68 +++++++++++++++++++ ...ltExportClassExtendingExpression01.symbols | 35 ++++++++++ ...aultExportClassExtendingExpression01.types | 32 +++++++++ 3 files changed, 135 insertions(+) create mode 100644 tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.js create mode 100644 tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.symbols create mode 100644 tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.types diff --git a/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.js b/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.js new file mode 100644 index 00000000000..4d0006d52e6 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.js @@ -0,0 +1,68 @@ +//// [declarationEmitForDefaultExportClassExtendingExpression01.ts] +interface Greeter { + getGreeting(): string; +} + +interface GreeterConstructor { + new (): Greeter; +} + +class A { + getGreeting() { + return 'hello'; + } +} + +const getGreeterBase = (): GreeterConstructor => A; + +export default class extends getGreeterBase() { +} + + + +//// [declarationEmitForDefaultExportClassExtendingExpression01.js] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + 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 extendStatics(d, b); + }; + 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 A = /** @class */ (function () { + function A() { + } + A.prototype.getGreeting = function () { + return 'hello'; + }; + return A; +}()); +var getGreeterBase = function () { return A; }; +var default_1 = /** @class */ (function (_super) { + __extends(default_1, _super); + function default_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + return default_1; +}(getGreeterBase())); +exports["default"] = default_1; + + +//// [declarationEmitForDefaultExportClassExtendingExpression01.d.ts] +interface Greeter { + getGreeting(): string; +} +interface GreeterConstructor { + new (): Greeter; +} +declare const default_base: GreeterConstructor; +export default class extends default_base { +} +export {}; diff --git a/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.symbols b/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.symbols new file mode 100644 index 00000000000..8d298f58037 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.symbols @@ -0,0 +1,35 @@ +=== tests/cases/compiler/declarationEmitForDefaultExportClassExtendingExpression01.ts === +interface Greeter { +>Greeter : Symbol(Greeter, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 0, 0)) + + getGreeting(): string; +>getGreeting : Symbol(Greeter.getGreeting, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 0, 19)) +} + +interface GreeterConstructor { +>GreeterConstructor : Symbol(GreeterConstructor, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 2, 1)) + + new (): Greeter; +>Greeter : Symbol(Greeter, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 0, 0)) +} + +class A { +>A : Symbol(A, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 6, 1)) + + getGreeting() { +>getGreeting : Symbol(A.getGreeting, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 8, 9)) + + return 'hello'; + } +} + +const getGreeterBase = (): GreeterConstructor => A; +>getGreeterBase : Symbol(getGreeterBase, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 14, 5)) +>GreeterConstructor : Symbol(GreeterConstructor, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 2, 1)) +>A : Symbol(A, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 6, 1)) + +export default class extends getGreeterBase() { +>getGreeterBase : Symbol(getGreeterBase, Decl(declarationEmitForDefaultExportClassExtendingExpression01.ts, 14, 5)) +} + + diff --git a/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.types b/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.types new file mode 100644 index 00000000000..f1ec49f988e --- /dev/null +++ b/tests/baselines/reference/declarationEmitForDefaultExportClassExtendingExpression01.types @@ -0,0 +1,32 @@ +=== tests/cases/compiler/declarationEmitForDefaultExportClassExtendingExpression01.ts === +interface Greeter { + getGreeting(): string; +>getGreeting : () => string +} + +interface GreeterConstructor { + new (): Greeter; +} + +class A { +>A : A + + getGreeting() { +>getGreeting : () => string + + return 'hello'; +>'hello' : "hello" + } +} + +const getGreeterBase = (): GreeterConstructor => A; +>getGreeterBase : () => GreeterConstructor +>(): GreeterConstructor => A : () => GreeterConstructor +>A : typeof A + +export default class extends getGreeterBase() { +>getGreeterBase() : Greeter +>getGreeterBase : () => GreeterConstructor +} + + From 08022d57c8c068767c6594df83e0012a8e154e3c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 19 Dec 2018 16:35:01 -0800 Subject: [PATCH 070/120] Allow calls on unions of dissimilar signatures (#29011) * Add core of new union signature logic and test - needs intersection signature logic to fully work * Add inversion of variance for class props lookup from union sig returns * Fix lints * Combine parameter names for nicer quick info * PR feedback 1 * Fix miscopy * PR feedback round 2 * Remove argument name combining because loc :( * Nit cleanup round 3 * Reinline getTupleTypeForArgumentAtPos * Remove a tad more * No step on sneky off-by-one error --- src/compiler/checker.ts | 107 +++++ .../callsOnComplexSignatures.errors.txt | 110 +++++ .../reference/callsOnComplexSignatures.js | 169 +++++++ .../callsOnComplexSignatures.symbols | 334 ++++++++++++++ .../reference/callsOnComplexSignatures.types | 416 ++++++++++++++++++ .../controlFlowArrayErrors.errors.txt | 6 +- ...onCallOnConstrainedTypeVariable.errors.txt | 22 +- .../reference/tsxUnionElementType1.errors.txt | 7 +- .../reference/tsxUnionElementType2.errors.txt | 7 +- .../reference/tsxUnionElementType3.errors.txt | 9 +- .../reference/tsxUnionElementType4.errors.txt | 9 +- .../unionTypeCallSignatures.errors.txt | 21 +- .../unionTypeConstructSignatures.errors.txt | 21 +- .../reference/unionTypeMembers.errors.txt | 10 +- .../compiler/callsOnComplexSignatures.tsx | 106 +++++ ...edUnionsOfDissimilarTyeshaveGoodDisplay.ts | 53 +++ .../tsxCompletionUnionElementType.ts | 2 +- .../tsxGoToDefinitionUnionElementType1.ts | 2 +- 18 files changed, 1364 insertions(+), 47 deletions(-) create mode 100644 tests/baselines/reference/callsOnComplexSignatures.errors.txt create mode 100644 tests/baselines/reference/callsOnComplexSignatures.js create mode 100644 tests/baselines/reference/callsOnComplexSignatures.symbols create mode 100644 tests/baselines/reference/callsOnComplexSignatures.types create mode 100644 tests/cases/compiler/callsOnComplexSignatures.tsx create mode 100644 tests/cases/fourslash/calledUnionsOfDissimilarTyeshaveGoodDisplay.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3377ae96b0d..23c5568f2e5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6799,7 +6799,12 @@ namespace ts { // type is the union of the constituent return types. function getUnionSignatures(signatureLists: ReadonlyArray>): Signature[] { let result: Signature[] | undefined; + let indexWithLengthOverOne: number | undefined; for (let i = 0; i < signatureLists.length; i++) { + if (signatureLists[i].length === 0) return emptyArray; + if (signatureLists[i].length > 1) { + indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets + } for (const signature of signatureLists[i]) { // Only process signatures with parameter lists that aren't already in the result list if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true)) { @@ -6823,9 +6828,91 @@ namespace ts { } } } + if (!length(result) && indexWithLengthOverOne !== -1) { + // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single + // signature that handles all over them. We only do this when there are overloads in only one constituent. + // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of + // signatures from the type, whose ordering would be non-obvious) + const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; + let results: Signature[] | undefined = masterList.slice(); + for (const signatures of signatureLists) { + if (signatures !== masterList) { + const signature = signatures[0]; + Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); + results = signature.typeParameters && some(results, s => !!s.typeParameters) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); + if (!results) { + break; + } + } + } + result = results; + } return result || emptyArray; } + function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined): Symbol | undefined { + if (!left || !right) { + return left || right; + } + // A signature `this` type might be a read or a write position... It's very possible that it should be invariant + // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be + // permissive when calling, for now, we'll union the `this` types just like the overlapping-union-signature check does + const thisType = getUnionType([getTypeOfSymbol(left), getTypeOfSymbol(right)], UnionReduction.Subtype); + return createSymbolWithType(left, thisType); + } + + function combineUnionParameters(left: Signature, right: Signature) { + const longest = getParameterCount(left) >= getParameterCount(right) ? left : right; + const shorter = longest === left ? right : left; + const longestCount = getParameterCount(longest); + const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); + const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); + const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); + for (let i = 0; i < longestCount; i++) { + const longestParamType = tryGetTypeAtPosition(longest, i)!; + const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; + const unionParamType = getIntersectionType([longestParamType, shorterParamType]); + const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); + const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); + const leftName = getParameterNameAtPosition(left, i); + const rightName = getParameterNameAtPosition(right, i); + const paramSymbol = createSymbol( + SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), + leftName === rightName ? leftName : `arg${i}` as __String + ); + paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + params[longestCount] = restParamSymbol; + } + return params; + } + + function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { + const declaration = left.declaration; + const params = combineUnionParameters(left, right); + const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter); + const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); + const hasRestParam = left.hasRestParameter || right.hasRestParameter; + const hasLiteralTypes = left.hasLiteralTypes || right.hasLiteralTypes; + const result = createSignature( + declaration, + left.typeParameters || right.typeParameters, + thisParam, + params, + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + minArgCount, + hasRestParam, + hasLiteralTypes + ); + result.unionSignatures = concatenate(left.unionSignatures || [left], [right]); + return result; + } + function getUnionIndexInfo(types: ReadonlyArray, kind: IndexKind): IndexInfo | undefined { const indexTypes: Type[] = []; let isAnyReadonly = false; @@ -17566,6 +17653,26 @@ namespace ts { } function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { + if (sig.unionSignatures) { + // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input + // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, + // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur + // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. + // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. + const results: Type[] = []; + for (const signature of sig.unionSignatures) { + const instance = getReturnTypeOfSignature(signature); + if (isTypeAny(instance)) { + return instance; + } + const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); + if (!propType) { + return; + } + results.push(propType); + } + return getIntersectionType(results); + } const instanceType = getReturnTypeOfSignature(sig); return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); } diff --git a/tests/baselines/reference/callsOnComplexSignatures.errors.txt b/tests/baselines/reference/callsOnComplexSignatures.errors.txt new file mode 100644 index 00000000000..f85a23e4ae7 --- /dev/null +++ b/tests/baselines/reference/callsOnComplexSignatures.errors.txt @@ -0,0 +1,110 @@ +tests/cases/compiler/callsOnComplexSignatures.tsx(38,19): error TS7006: Parameter 'item' implicitly has an 'any' type. + + +==== tests/cases/compiler/callsOnComplexSignatures.tsx (1 errors) ==== + /// + import React from "react"; + + // Simple calls from real usecases + function test1() { + type stringType1 = "foo" | "bar"; + type stringType2 = "baz" | "bar"; + + interface Temp1 { + getValue(name: stringType1): number; + } + + interface Temp2 { + getValue(name: stringType2): string; + } + + function test(t: Temp1 | Temp2) { + const z = t.getValue("bar"); // Should be fine + } + } + + function test2() { + interface Messages { + readonly foo: (options: { [key: string]: any, b: number }) => string; + readonly bar: (options: { [key: string]: any, a: string }) => string; + } + + const messages: Messages = { + foo: (options) => "Foo", + bar: (options) => "Bar", + }; + + const test1 = (type: "foo" | "bar") => + messages[type]({ a: "A", b: 0 }); + } + + function test3(items: string[] | number[]) { + items.forEach(item => console.log(item)); + ~~~~ +!!! error TS7006: Parameter 'item' implicitly has an 'any' type. + } + + function test4( + arg1: ((...objs: {x: number}[]) => number) | ((...objs: {y: number}[]) => number), + arg2: ((a: {x: number}, b: object) => number) | ((a: object, b: {x: number}) => number), + arg3: ((a: {x: number}, ...objs: {y: number}[]) => number) | ((...objs: {x: number}[]) => number), + arg4: ((a?: {x: number}, b?: {x: number}) => number) | ((a?: {y: number}) => number), + arg5: ((a?: {x: number}, ...b: {x: number}[]) => number) | ((a?: {y: number}) => number), + arg6: ((a?: {x: number}, b?: {x: number}) => number) | ((...a: {y: number}[]) => number), + ) { + arg1(); + arg1({x: 0, y: 0}); + arg1({x: 0, y: 0}, {x: 1, y: 1}); + + arg2({x: 0}, {x: 0}); + + arg3({x: 0}); + arg3({x: 0}, {x: 0, y: 0}); + arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}); + + arg4(); + arg4({x: 0, y: 0}); + arg4({x: 0, y: 0}, {x: 0}); + + arg5(); + arg5({x: 0, y: 0}); + arg5({x: 0, y: 0}, {x: 0}); + + arg6(); + arg6({x: 0, y: 0}); + arg6({x: 0, y: 0}, {x: 0, y: 0}); + arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}); + } + + // JSX Tag names + function test5() { + // Pair of non-like intrinsics + function render(url?: string): React.ReactNode { + const Tag = url ? 'a' : 'button'; + return test; + } + + // Union of all intrinsics and components of `any` + function App(props: { component:React.ReactType }) { + const Comp: React.ReactType = props.component; + return (); + } + + // custom components with non-subset props + function render2() { + interface P1 { + p?: boolean; + c?: string; + } + interface P2 { + p?: boolean; + c?: any; + d?: any; + } + + var C: React.ComponentType | React.ComponentType = null as any; + + const a = ; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/callsOnComplexSignatures.js b/tests/baselines/reference/callsOnComplexSignatures.js new file mode 100644 index 00000000000..f631f674269 --- /dev/null +++ b/tests/baselines/reference/callsOnComplexSignatures.js @@ -0,0 +1,169 @@ +//// [callsOnComplexSignatures.tsx] +/// +import React from "react"; + +// Simple calls from real usecases +function test1() { + type stringType1 = "foo" | "bar"; + type stringType2 = "baz" | "bar"; + + interface Temp1 { + getValue(name: stringType1): number; + } + + interface Temp2 { + getValue(name: stringType2): string; + } + + function test(t: Temp1 | Temp2) { + const z = t.getValue("bar"); // Should be fine + } +} + +function test2() { + interface Messages { + readonly foo: (options: { [key: string]: any, b: number }) => string; + readonly bar: (options: { [key: string]: any, a: string }) => string; + } + + const messages: Messages = { + foo: (options) => "Foo", + bar: (options) => "Bar", + }; + + const test1 = (type: "foo" | "bar") => + messages[type]({ a: "A", b: 0 }); +} + +function test3(items: string[] | number[]) { + items.forEach(item => console.log(item)); +} + +function test4( + arg1: ((...objs: {x: number}[]) => number) | ((...objs: {y: number}[]) => number), + arg2: ((a: {x: number}, b: object) => number) | ((a: object, b: {x: number}) => number), + arg3: ((a: {x: number}, ...objs: {y: number}[]) => number) | ((...objs: {x: number}[]) => number), + arg4: ((a?: {x: number}, b?: {x: number}) => number) | ((a?: {y: number}) => number), + arg5: ((a?: {x: number}, ...b: {x: number}[]) => number) | ((a?: {y: number}) => number), + arg6: ((a?: {x: number}, b?: {x: number}) => number) | ((...a: {y: number}[]) => number), +) { + arg1(); + arg1({x: 0, y: 0}); + arg1({x: 0, y: 0}, {x: 1, y: 1}); + + arg2({x: 0}, {x: 0}); + + arg3({x: 0}); + arg3({x: 0}, {x: 0, y: 0}); + arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}); + + arg4(); + arg4({x: 0, y: 0}); + arg4({x: 0, y: 0}, {x: 0}); + + arg5(); + arg5({x: 0, y: 0}); + arg5({x: 0, y: 0}, {x: 0}); + + arg6(); + arg6({x: 0, y: 0}); + arg6({x: 0, y: 0}, {x: 0, y: 0}); + arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}); +} + +// JSX Tag names +function test5() { + // Pair of non-like intrinsics + function render(url?: string): React.ReactNode { + const Tag = url ? 'a' : 'button'; + return test; + } + + // Union of all intrinsics and components of `any` + function App(props: { component:React.ReactType }) { + const Comp: React.ReactType = props.component; + return (); + } + + // custom components with non-subset props + function render2() { + interface P1 { + p?: boolean; + c?: string; + } + interface P2 { + p?: boolean; + c?: any; + d?: any; + } + + var C: React.ComponentType | React.ComponentType = null as any; + + const a = ; + } +} + + +//// [callsOnComplexSignatures.js] +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +exports.__esModule = true; +/// +var react_1 = __importDefault(require("react")); +// Simple calls from real usecases +function test1() { + function test(t) { + var z = t.getValue("bar"); // Should be fine + } +} +function test2() { + var messages = { + foo: function (options) { return "Foo"; }, + bar: function (options) { return "Bar"; } + }; + var test1 = function (type) { + return messages[type]({ a: "A", b: 0 }); + }; +} +function test3(items) { + items.forEach(function (item) { return console.log(item); }); +} +function test4(arg1, arg2, arg3, arg4, arg5, arg6) { + arg1(); + arg1({ x: 0, y: 0 }); + arg1({ x: 0, y: 0 }, { x: 1, y: 1 }); + arg2({ x: 0 }, { x: 0 }); + arg3({ x: 0 }); + arg3({ x: 0 }, { x: 0, y: 0 }); + arg3({ x: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }); + arg4(); + arg4({ x: 0, y: 0 }); + arg4({ x: 0, y: 0 }, { x: 0 }); + arg5(); + arg5({ x: 0, y: 0 }); + arg5({ x: 0, y: 0 }, { x: 0 }); + arg6(); + arg6({ x: 0, y: 0 }); + arg6({ x: 0, y: 0 }, { x: 0, y: 0 }); + arg6({ x: 0, y: 0 }, { x: 0, y: 0 }, { y: 0 }); +} +// JSX Tag names +function test5() { + // Pair of non-like intrinsics + function render(url) { + var Tag = url ? 'a' : 'button'; + return react_1["default"].createElement(Tag, null, "test"); + } + // Union of all intrinsics and components of `any` + function App(props) { + var Comp = props.component; + return (react_1["default"].createElement(Comp, null)); + } + // custom components with non-subset props + function render2() { + var C = null; + var a = react_1["default"].createElement(C, { p: true }); + } +} diff --git a/tests/baselines/reference/callsOnComplexSignatures.symbols b/tests/baselines/reference/callsOnComplexSignatures.symbols new file mode 100644 index 00000000000..dfbddce84ec --- /dev/null +++ b/tests/baselines/reference/callsOnComplexSignatures.symbols @@ -0,0 +1,334 @@ +=== tests/cases/compiler/callsOnComplexSignatures.tsx === +/// +import React from "react"; +>React : Symbol(React, Decl(callsOnComplexSignatures.tsx, 1, 6)) + +// Simple calls from real usecases +function test1() { +>test1 : Symbol(test1, Decl(callsOnComplexSignatures.tsx, 1, 26)) + + type stringType1 = "foo" | "bar"; +>stringType1 : Symbol(stringType1, Decl(callsOnComplexSignatures.tsx, 4, 18)) + + type stringType2 = "baz" | "bar"; +>stringType2 : Symbol(stringType2, Decl(callsOnComplexSignatures.tsx, 5, 37)) + + interface Temp1 { +>Temp1 : Symbol(Temp1, Decl(callsOnComplexSignatures.tsx, 6, 37)) + + getValue(name: stringType1): number; +>getValue : Symbol(Temp1.getValue, Decl(callsOnComplexSignatures.tsx, 8, 21)) +>name : Symbol(name, Decl(callsOnComplexSignatures.tsx, 9, 17)) +>stringType1 : Symbol(stringType1, Decl(callsOnComplexSignatures.tsx, 4, 18)) + } + + interface Temp2 { +>Temp2 : Symbol(Temp2, Decl(callsOnComplexSignatures.tsx, 10, 5)) + + getValue(name: stringType2): string; +>getValue : Symbol(Temp2.getValue, Decl(callsOnComplexSignatures.tsx, 12, 21)) +>name : Symbol(name, Decl(callsOnComplexSignatures.tsx, 13, 17)) +>stringType2 : Symbol(stringType2, Decl(callsOnComplexSignatures.tsx, 5, 37)) + } + + function test(t: Temp1 | Temp2) { +>test : Symbol(test, Decl(callsOnComplexSignatures.tsx, 14, 5)) +>t : Symbol(t, Decl(callsOnComplexSignatures.tsx, 16, 18)) +>Temp1 : Symbol(Temp1, Decl(callsOnComplexSignatures.tsx, 6, 37)) +>Temp2 : Symbol(Temp2, Decl(callsOnComplexSignatures.tsx, 10, 5)) + + const z = t.getValue("bar"); // Should be fine +>z : Symbol(z, Decl(callsOnComplexSignatures.tsx, 17, 13)) +>t.getValue : Symbol(getValue, Decl(callsOnComplexSignatures.tsx, 8, 21), Decl(callsOnComplexSignatures.tsx, 12, 21)) +>t : Symbol(t, Decl(callsOnComplexSignatures.tsx, 16, 18)) +>getValue : Symbol(getValue, Decl(callsOnComplexSignatures.tsx, 8, 21), Decl(callsOnComplexSignatures.tsx, 12, 21)) + } +} + +function test2() { +>test2 : Symbol(test2, Decl(callsOnComplexSignatures.tsx, 19, 1)) + + interface Messages { +>Messages : Symbol(Messages, Decl(callsOnComplexSignatures.tsx, 21, 18)) + + readonly foo: (options: { [key: string]: any, b: number }) => string; +>foo : Symbol(Messages.foo, Decl(callsOnComplexSignatures.tsx, 22, 24)) +>options : Symbol(options, Decl(callsOnComplexSignatures.tsx, 23, 23)) +>key : Symbol(key, Decl(callsOnComplexSignatures.tsx, 23, 35)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 23, 53)) + + readonly bar: (options: { [key: string]: any, a: string }) => string; +>bar : Symbol(Messages.bar, Decl(callsOnComplexSignatures.tsx, 23, 77)) +>options : Symbol(options, Decl(callsOnComplexSignatures.tsx, 24, 23)) +>key : Symbol(key, Decl(callsOnComplexSignatures.tsx, 24, 35)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 24, 53)) + } + + const messages: Messages = { +>messages : Symbol(messages, Decl(callsOnComplexSignatures.tsx, 27, 9)) +>Messages : Symbol(Messages, Decl(callsOnComplexSignatures.tsx, 21, 18)) + + foo: (options) => "Foo", +>foo : Symbol(foo, Decl(callsOnComplexSignatures.tsx, 27, 32)) +>options : Symbol(options, Decl(callsOnComplexSignatures.tsx, 28, 14)) + + bar: (options) => "Bar", +>bar : Symbol(bar, Decl(callsOnComplexSignatures.tsx, 28, 32)) +>options : Symbol(options, Decl(callsOnComplexSignatures.tsx, 29, 14)) + + }; + + const test1 = (type: "foo" | "bar") => +>test1 : Symbol(test1, Decl(callsOnComplexSignatures.tsx, 32, 9)) +>type : Symbol(type, Decl(callsOnComplexSignatures.tsx, 32, 19)) + + messages[type]({ a: "A", b: 0 }); +>messages : Symbol(messages, Decl(callsOnComplexSignatures.tsx, 27, 9)) +>type : Symbol(type, Decl(callsOnComplexSignatures.tsx, 32, 19)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 33, 24)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 33, 32)) +} + +function test3(items: string[] | number[]) { +>test3 : Symbol(test3, Decl(callsOnComplexSignatures.tsx, 34, 1)) +>items : Symbol(items, Decl(callsOnComplexSignatures.tsx, 36, 15)) + + items.forEach(item => console.log(item)); +>items.forEach : Symbol(forEach, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(callsOnComplexSignatures.tsx, 36, 15)) +>forEach : Symbol(forEach, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>item : Symbol(item, Decl(callsOnComplexSignatures.tsx, 37, 18)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>item : Symbol(item, Decl(callsOnComplexSignatures.tsx, 37, 18)) +} + +function test4( +>test4 : Symbol(test4, Decl(callsOnComplexSignatures.tsx, 38, 1)) + + arg1: ((...objs: {x: number}[]) => number) | ((...objs: {y: number}[]) => number), +>arg1 : Symbol(arg1, Decl(callsOnComplexSignatures.tsx, 40, 15)) +>objs : Symbol(objs, Decl(callsOnComplexSignatures.tsx, 41, 12)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 41, 22)) +>objs : Symbol(objs, Decl(callsOnComplexSignatures.tsx, 41, 51)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 41, 61)) + + arg2: ((a: {x: number}, b: object) => number) | ((a: object, b: {x: number}) => number), +>arg2 : Symbol(arg2, Decl(callsOnComplexSignatures.tsx, 41, 86)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 42, 12)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 42, 16)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 42, 27)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 42, 54)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 42, 64)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 42, 69)) + + arg3: ((a: {x: number}, ...objs: {y: number}[]) => number) | ((...objs: {x: number}[]) => number), +>arg3 : Symbol(arg3, Decl(callsOnComplexSignatures.tsx, 42, 92)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 43, 12)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 43, 16)) +>objs : Symbol(objs, Decl(callsOnComplexSignatures.tsx, 43, 27)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 43, 38)) +>objs : Symbol(objs, Decl(callsOnComplexSignatures.tsx, 43, 67)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 43, 77)) + + arg4: ((a?: {x: number}, b?: {x: number}) => number) | ((a?: {y: number}) => number), +>arg4 : Symbol(arg4, Decl(callsOnComplexSignatures.tsx, 43, 102)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 44, 12)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 44, 17)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 44, 28)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 44, 34)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 44, 61)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 44, 66)) + + arg5: ((a?: {x: number}, ...b: {x: number}[]) => number) | ((a?: {y: number}) => number), +>arg5 : Symbol(arg5, Decl(callsOnComplexSignatures.tsx, 44, 89)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 45, 12)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 45, 17)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 45, 28)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 45, 36)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 45, 65)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 45, 70)) + + arg6: ((a?: {x: number}, b?: {x: number}) => number) | ((...a: {y: number}[]) => number), +>arg6 : Symbol(arg6, Decl(callsOnComplexSignatures.tsx, 45, 93)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 46, 12)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 46, 17)) +>b : Symbol(b, Decl(callsOnComplexSignatures.tsx, 46, 28)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 46, 34)) +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 46, 61)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 46, 68)) + +) { + arg1(); +>arg1 : Symbol(arg1, Decl(callsOnComplexSignatures.tsx, 40, 15)) + + arg1({x: 0, y: 0}); +>arg1 : Symbol(arg1, Decl(callsOnComplexSignatures.tsx, 40, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 49, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 49, 15)) + + arg1({x: 0, y: 0}, {x: 1, y: 1}); +>arg1 : Symbol(arg1, Decl(callsOnComplexSignatures.tsx, 40, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 50, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 50, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 50, 24)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 50, 29)) + + arg2({x: 0}, {x: 0}); +>arg2 : Symbol(arg2, Decl(callsOnComplexSignatures.tsx, 41, 86)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 52, 10)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 52, 18)) + + arg3({x: 0}); +>arg3 : Symbol(arg3, Decl(callsOnComplexSignatures.tsx, 42, 92)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 54, 10)) + + arg3({x: 0}, {x: 0, y: 0}); +>arg3 : Symbol(arg3, Decl(callsOnComplexSignatures.tsx, 42, 92)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 55, 10)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 55, 18)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 55, 23)) + + arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}); +>arg3 : Symbol(arg3, Decl(callsOnComplexSignatures.tsx, 42, 92)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 56, 10)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 56, 18)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 56, 23)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 56, 32)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 56, 37)) + + arg4(); +>arg4 : Symbol(arg4, Decl(callsOnComplexSignatures.tsx, 43, 102)) + + arg4({x: 0, y: 0}); +>arg4 : Symbol(arg4, Decl(callsOnComplexSignatures.tsx, 43, 102)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 59, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 59, 15)) + + arg4({x: 0, y: 0}, {x: 0}); +>arg4 : Symbol(arg4, Decl(callsOnComplexSignatures.tsx, 43, 102)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 60, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 60, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 60, 24)) + + arg5(); +>arg5 : Symbol(arg5, Decl(callsOnComplexSignatures.tsx, 44, 89)) + + arg5({x: 0, y: 0}); +>arg5 : Symbol(arg5, Decl(callsOnComplexSignatures.tsx, 44, 89)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 63, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 63, 15)) + + arg5({x: 0, y: 0}, {x: 0}); +>arg5 : Symbol(arg5, Decl(callsOnComplexSignatures.tsx, 44, 89)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 64, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 64, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 64, 24)) + + arg6(); +>arg6 : Symbol(arg6, Decl(callsOnComplexSignatures.tsx, 45, 93)) + + arg6({x: 0, y: 0}); +>arg6 : Symbol(arg6, Decl(callsOnComplexSignatures.tsx, 45, 93)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 67, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 67, 15)) + + arg6({x: 0, y: 0}, {x: 0, y: 0}); +>arg6 : Symbol(arg6, Decl(callsOnComplexSignatures.tsx, 45, 93)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 68, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 68, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 68, 24)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 68, 29)) + + arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}); +>arg6 : Symbol(arg6, Decl(callsOnComplexSignatures.tsx, 45, 93)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 69, 10)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 69, 15)) +>x : Symbol(x, Decl(callsOnComplexSignatures.tsx, 69, 24)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 69, 29)) +>y : Symbol(y, Decl(callsOnComplexSignatures.tsx, 69, 38)) +} + +// JSX Tag names +function test5() { +>test5 : Symbol(test5, Decl(callsOnComplexSignatures.tsx, 70, 1)) + + // Pair of non-like intrinsics + function render(url?: string): React.ReactNode { +>render : Symbol(render, Decl(callsOnComplexSignatures.tsx, 73, 18)) +>url : Symbol(url, Decl(callsOnComplexSignatures.tsx, 75, 20)) +>React : Symbol(React, Decl(callsOnComplexSignatures.tsx, 1, 6)) +>ReactNode : Symbol(React.ReactNode, Decl(react16.d.ts, 216, 49)) + + const Tag = url ? 'a' : 'button'; +>Tag : Symbol(Tag, Decl(callsOnComplexSignatures.tsx, 76, 13)) +>url : Symbol(url, Decl(callsOnComplexSignatures.tsx, 75, 20)) + + return test; +>Tag : Symbol(Tag, Decl(callsOnComplexSignatures.tsx, 76, 13)) +>Tag : Symbol(Tag, Decl(callsOnComplexSignatures.tsx, 76, 13)) + } + + // Union of all intrinsics and components of `any` + function App(props: { component:React.ReactType }) { +>App : Symbol(App, Decl(callsOnComplexSignatures.tsx, 78, 5)) +>props : Symbol(props, Decl(callsOnComplexSignatures.tsx, 81, 17)) +>component : Symbol(component, Decl(callsOnComplexSignatures.tsx, 81, 25)) +>React : Symbol(React, Decl(callsOnComplexSignatures.tsx, 1, 6)) +>ReactType : Symbol(React.ReactType, Decl(react16.d.ts, 112, 21)) + + const Comp: React.ReactType = props.component; +>Comp : Symbol(Comp, Decl(callsOnComplexSignatures.tsx, 82, 13)) +>React : Symbol(React, Decl(callsOnComplexSignatures.tsx, 1, 6)) +>ReactType : Symbol(React.ReactType, Decl(react16.d.ts, 112, 21)) +>props.component : Symbol(component, Decl(callsOnComplexSignatures.tsx, 81, 25)) +>props : Symbol(props, Decl(callsOnComplexSignatures.tsx, 81, 17)) +>component : Symbol(component, Decl(callsOnComplexSignatures.tsx, 81, 25)) + + return (); +>Comp : Symbol(Comp, Decl(callsOnComplexSignatures.tsx, 82, 13)) + } + + // custom components with non-subset props + function render2() { +>render2 : Symbol(render2, Decl(callsOnComplexSignatures.tsx, 84, 5)) + + interface P1 { +>P1 : Symbol(P1, Decl(callsOnComplexSignatures.tsx, 87, 24)) + + p?: boolean; +>p : Symbol(P1.p, Decl(callsOnComplexSignatures.tsx, 88, 22)) + + c?: string; +>c : Symbol(P1.c, Decl(callsOnComplexSignatures.tsx, 89, 24)) + } + interface P2 { +>P2 : Symbol(P2, Decl(callsOnComplexSignatures.tsx, 91, 9)) + + p?: boolean; +>p : Symbol(P2.p, Decl(callsOnComplexSignatures.tsx, 92, 22)) + + c?: any; +>c : Symbol(P2.c, Decl(callsOnComplexSignatures.tsx, 93, 24)) + + d?: any; +>d : Symbol(P2.d, Decl(callsOnComplexSignatures.tsx, 94, 20)) + } + + var C: React.ComponentType | React.ComponentType = null as any; +>C : Symbol(C, Decl(callsOnComplexSignatures.tsx, 98, 11)) +>React : Symbol(React, Decl(callsOnComplexSignatures.tsx, 1, 6)) +>ComponentType : Symbol(React.ComponentType, Decl(react16.d.ts, 117, 60)) +>P1 : Symbol(P1, Decl(callsOnComplexSignatures.tsx, 87, 24)) +>React : Symbol(React, Decl(callsOnComplexSignatures.tsx, 1, 6)) +>ComponentType : Symbol(React.ComponentType, Decl(react16.d.ts, 117, 60)) +>P2 : Symbol(P2, Decl(callsOnComplexSignatures.tsx, 91, 9)) + + const a = ; +>a : Symbol(a, Decl(callsOnComplexSignatures.tsx, 100, 13)) +>C : Symbol(C, Decl(callsOnComplexSignatures.tsx, 98, 11)) +>p : Symbol(p, Decl(callsOnComplexSignatures.tsx, 100, 20)) + } +} + diff --git a/tests/baselines/reference/callsOnComplexSignatures.types b/tests/baselines/reference/callsOnComplexSignatures.types new file mode 100644 index 00000000000..4eb90ac43e3 --- /dev/null +++ b/tests/baselines/reference/callsOnComplexSignatures.types @@ -0,0 +1,416 @@ +=== tests/cases/compiler/callsOnComplexSignatures.tsx === +/// +import React from "react"; +>React : typeof React + +// Simple calls from real usecases +function test1() { +>test1 : () => void + + type stringType1 = "foo" | "bar"; +>stringType1 : "foo" | "bar" + + type stringType2 = "baz" | "bar"; +>stringType2 : "bar" | "baz" + + interface Temp1 { + getValue(name: stringType1): number; +>getValue : (name: "foo" | "bar") => number +>name : "foo" | "bar" + } + + interface Temp2 { + getValue(name: stringType2): string; +>getValue : (name: "bar" | "baz") => string +>name : "bar" | "baz" + } + + function test(t: Temp1 | Temp2) { +>test : (t: Temp1 | Temp2) => void +>t : Temp1 | Temp2 + + const z = t.getValue("bar"); // Should be fine +>z : React.ReactText +>t.getValue("bar") : React.ReactText +>t.getValue : ((name: "foo" | "bar") => number) | ((name: "bar" | "baz") => string) +>t : Temp1 | Temp2 +>getValue : ((name: "foo" | "bar") => number) | ((name: "bar" | "baz") => string) +>"bar" : "bar" + } +} + +function test2() { +>test2 : () => void + + interface Messages { + readonly foo: (options: { [key: string]: any, b: number }) => string; +>foo : (options: { [key: string]: any; b: number; }) => string +>options : { [key: string]: any; b: number; } +>key : string +>b : number + + readonly bar: (options: { [key: string]: any, a: string }) => string; +>bar : (options: { [key: string]: any; a: string; }) => string +>options : { [key: string]: any; a: string; } +>key : string +>a : string + } + + const messages: Messages = { +>messages : Messages +>{ foo: (options) => "Foo", bar: (options) => "Bar", } : { foo: (options: { [key: string]: any; b: number; }) => string; bar: (options: { [key: string]: any; a: string; }) => string; } + + foo: (options) => "Foo", +>foo : (options: { [key: string]: any; b: number; }) => string +>(options) => "Foo" : (options: { [key: string]: any; b: number; }) => string +>options : { [key: string]: any; b: number; } +>"Foo" : "Foo" + + bar: (options) => "Bar", +>bar : (options: { [key: string]: any; a: string; }) => string +>(options) => "Bar" : (options: { [key: string]: any; a: string; }) => string +>options : { [key: string]: any; a: string; } +>"Bar" : "Bar" + + }; + + const test1 = (type: "foo" | "bar") => +>test1 : (type: "foo" | "bar") => string +>(type: "foo" | "bar") => messages[type]({ a: "A", b: 0 }) : (type: "foo" | "bar") => string +>type : "foo" | "bar" + + messages[type]({ a: "A", b: 0 }); +>messages[type]({ a: "A", b: 0 }) : string +>messages[type] : ((options: { [key: string]: any; b: number; }) => string) | ((options: { [key: string]: any; a: string; }) => string) +>messages : Messages +>type : "foo" | "bar" +>{ a: "A", b: 0 } : { a: string; b: number; } +>a : string +>"A" : "A" +>b : number +>0 : 0 +} + +function test3(items: string[] | number[]) { +>test3 : (items: string[] | number[]) => void +>items : string[] | number[] + + items.forEach(item => console.log(item)); +>items.forEach(item => console.log(item)) : void +>items.forEach : ((callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void) | ((callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void) +>items : string[] | number[] +>forEach : ((callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void) | ((callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void) +>item => console.log(item) : (item: any) => void +>item : any +>console.log(item) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>item : any +} + +function test4( +>test4 : (arg1: ((...objs: { x: number; }[]) => number) | ((...objs: { y: number; }[]) => number), arg2: ((a: { x: number; }, b: object) => number) | ((a: object, b: { x: number; }) => number), arg3: ((a: { x: number; }, ...objs: { y: number; }[]) => number) | ((...objs: { x: number; }[]) => number), arg4: ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((a?: { y: number; } | undefined) => number), arg5: ((a?: { x: number; } | undefined, ...b: { x: number; }[]) => number) | ((a?: { y: number; } | undefined) => number), arg6: ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((...a: { y: number; }[]) => number)) => void + + arg1: ((...objs: {x: number}[]) => number) | ((...objs: {y: number}[]) => number), +>arg1 : ((...objs: { x: number; }[]) => number) | ((...objs: { y: number; }[]) => number) +>objs : { x: number; }[] +>x : number +>objs : { y: number; }[] +>y : number + + arg2: ((a: {x: number}, b: object) => number) | ((a: object, b: {x: number}) => number), +>arg2 : ((a: { x: number; }, b: object) => number) | ((a: object, b: { x: number; }) => number) +>a : { x: number; } +>x : number +>b : object +>a : object +>b : { x: number; } +>x : number + + arg3: ((a: {x: number}, ...objs: {y: number}[]) => number) | ((...objs: {x: number}[]) => number), +>arg3 : ((a: { x: number; }, ...objs: { y: number; }[]) => number) | ((...objs: { x: number; }[]) => number) +>a : { x: number; } +>x : number +>objs : { y: number; }[] +>y : number +>objs : { x: number; }[] +>x : number + + arg4: ((a?: {x: number}, b?: {x: number}) => number) | ((a?: {y: number}) => number), +>arg4 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((a?: { y: number; } | undefined) => number) +>a : { x: number; } | undefined +>x : number +>b : { x: number; } | undefined +>x : number +>a : { y: number; } | undefined +>y : number + + arg5: ((a?: {x: number}, ...b: {x: number}[]) => number) | ((a?: {y: number}) => number), +>arg5 : ((a?: { x: number; } | undefined, ...b: { x: number; }[]) => number) | ((a?: { y: number; } | undefined) => number) +>a : { x: number; } | undefined +>x : number +>b : { x: number; }[] +>x : number +>a : { y: number; } | undefined +>y : number + + arg6: ((a?: {x: number}, b?: {x: number}) => number) | ((...a: {y: number}[]) => number), +>arg6 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((...a: { y: number; }[]) => number) +>a : { x: number; } | undefined +>x : number +>b : { x: number; } | undefined +>x : number +>a : { y: number; }[] +>y : number + +) { + arg1(); +>arg1() : number +>arg1 : ((...objs: { x: number; }[]) => number) | ((...objs: { y: number; }[]) => number) + + arg1({x: 0, y: 0}); +>arg1({x: 0, y: 0}) : number +>arg1 : ((...objs: { x: number; }[]) => number) | ((...objs: { y: number; }[]) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg1({x: 0, y: 0}, {x: 1, y: 1}); +>arg1({x: 0, y: 0}, {x: 1, y: 1}) : number +>arg1 : ((...objs: { x: number; }[]) => number) | ((...objs: { y: number; }[]) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{x: 1, y: 1} : { x: number; y: number; } +>x : number +>1 : 1 +>y : number +>1 : 1 + + arg2({x: 0}, {x: 0}); +>arg2({x: 0}, {x: 0}) : number +>arg2 : ((a: { x: number; }, b: object) => number) | ((a: object, b: { x: number; }) => number) +>{x: 0} : { x: number; } +>x : number +>0 : 0 +>{x: 0} : { x: number; } +>x : number +>0 : 0 + + arg3({x: 0}); +>arg3({x: 0}) : number +>arg3 : ((a: { x: number; }, ...objs: { y: number; }[]) => number) | ((...objs: { x: number; }[]) => number) +>{x: 0} : { x: number; } +>x : number +>0 : 0 + + arg3({x: 0}, {x: 0, y: 0}); +>arg3({x: 0}, {x: 0, y: 0}) : number +>arg3 : ((a: { x: number; }, ...objs: { y: number; }[]) => number) | ((...objs: { x: number; }[]) => number) +>{x: 0} : { x: number; } +>x : number +>0 : 0 +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}); +>arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}) : number +>arg3 : ((a: { x: number; }, ...objs: { y: number; }[]) => number) | ((...objs: { x: number; }[]) => number) +>{x: 0} : { x: number; } +>x : number +>0 : 0 +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg4(); +>arg4() : number +>arg4 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((a?: { y: number; } | undefined) => number) + + arg4({x: 0, y: 0}); +>arg4({x: 0, y: 0}) : number +>arg4 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((a?: { y: number; } | undefined) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg4({x: 0, y: 0}, {x: 0}); +>arg4({x: 0, y: 0}, {x: 0}) : number +>arg4 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((a?: { y: number; } | undefined) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{x: 0} : { x: number; } +>x : number +>0 : 0 + + arg5(); +>arg5() : number +>arg5 : ((a?: { x: number; } | undefined, ...b: { x: number; }[]) => number) | ((a?: { y: number; } | undefined) => number) + + arg5({x: 0, y: 0}); +>arg5({x: 0, y: 0}) : number +>arg5 : ((a?: { x: number; } | undefined, ...b: { x: number; }[]) => number) | ((a?: { y: number; } | undefined) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg5({x: 0, y: 0}, {x: 0}); +>arg5({x: 0, y: 0}, {x: 0}) : number +>arg5 : ((a?: { x: number; } | undefined, ...b: { x: number; }[]) => number) | ((a?: { y: number; } | undefined) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{x: 0} : { x: number; } +>x : number +>0 : 0 + + arg6(); +>arg6() : number +>arg6 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((...a: { y: number; }[]) => number) + + arg6({x: 0, y: 0}); +>arg6({x: 0, y: 0}) : number +>arg6 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((...a: { y: number; }[]) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg6({x: 0, y: 0}, {x: 0, y: 0}); +>arg6({x: 0, y: 0}, {x: 0, y: 0}) : number +>arg6 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((...a: { y: number; }[]) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 + + arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}); +>arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}) : number +>arg6 : ((a?: { x: number; } | undefined, b?: { x: number; } | undefined) => number) | ((...a: { y: number; }[]) => number) +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{x: 0, y: 0} : { x: number; y: number; } +>x : number +>0 : 0 +>y : number +>0 : 0 +>{y: 0} : { y: number; } +>y : number +>0 : 0 +} + +// JSX Tag names +function test5() { +>test5 : () => void + + // Pair of non-like intrinsics + function render(url?: string): React.ReactNode { +>render : (url?: string | undefined) => React.ReactNode +>url : string | undefined +>React : any + + const Tag = url ? 'a' : 'button'; +>Tag : "a" | "button" +>url ? 'a' : 'button' : "a" | "button" +>url : string | undefined +>'a' : "a" +>'button' : "button" + + return test; +>test : JSX.Element +>Tag : "a" | "button" +>Tag : "a" | "button" + } + + // Union of all intrinsics and components of `any` + function App(props: { component:React.ReactType }) { +>App : (props: { component: React.ReactType; }) => JSX.Element +>props : { component: React.ReactType; } +>component : React.ReactType +>React : any + + const Comp: React.ReactType = props.component; +>Comp : React.ReactType +>React : any +>props.component : React.ReactType +>props : { component: React.ReactType; } +>component : React.ReactType + + return (); +>() : JSX.Element +> : JSX.Element +>Comp : React.ReactType + } + + // custom components with non-subset props + function render2() { +>render2 : () => void + + interface P1 { + p?: boolean; +>p : boolean | undefined + + c?: string; +>c : string | undefined + } + interface P2 { + p?: boolean; +>p : boolean | undefined + + c?: any; +>c : any + + d?: any; +>d : any + } + + var C: React.ComponentType | React.ComponentType = null as any; +>C : React.ComponentClass | React.StatelessComponent | React.ComponentClass | React.StatelessComponent +>React : any +>React : any +>null as any : any +>null : null + + const a = ; +>a : JSX.Element +> : JSX.Element +>C : React.ComponentClass | React.StatelessComponent | React.ComponentClass | React.StatelessComponent +>p : true +>true : true + } +} + diff --git a/tests/baselines/reference/controlFlowArrayErrors.errors.txt b/tests/baselines/reference/controlFlowArrayErrors.errors.txt index 403cafacf5d..59ec0b44f5c 100644 --- a/tests/baselines/reference/controlFlowArrayErrors.errors.txt +++ b/tests/baselines/reference/controlFlowArrayErrors.errors.txt @@ -6,7 +6,7 @@ tests/cases/compiler/controlFlowArrayErrors.ts(19,9): error TS7034: Variable 'x' tests/cases/compiler/controlFlowArrayErrors.ts(22,9): error TS7005: Variable 'x' implicitly has an 'any[]' type. tests/cases/compiler/controlFlowArrayErrors.ts(29,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. tests/cases/compiler/controlFlowArrayErrors.ts(34,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'. -tests/cases/compiler/controlFlowArrayErrors.ts(48,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures. +tests/cases/compiler/controlFlowArrayErrors.ts(48,12): error TS2345: Argument of type '99' is not assignable to parameter of type 'never'. tests/cases/compiler/controlFlowArrayErrors.ts(56,12): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. tests/cases/compiler/controlFlowArrayErrors.ts(60,11): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined. tests/cases/compiler/controlFlowArrayErrors.ts(63,9): error TS7005: Variable 'x' implicitly has an 'any[]' type. @@ -77,8 +77,8 @@ tests/cases/compiler/controlFlowArrayErrors.ts(63,9): error TS7005: Variable 'x' } x; // boolean[] | (string | number)[] x.push(99); // Error - ~~~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures. + ~~ +!!! error TS2345: Argument of type '99' is not assignable to parameter of type 'never'. } function f7() { diff --git a/tests/baselines/reference/functionCallOnConstrainedTypeVariable.errors.txt b/tests/baselines/reference/functionCallOnConstrainedTypeVariable.errors.txt index 1ff56c7612f..f7ea353e10f 100644 --- a/tests/baselines/reference/functionCallOnConstrainedTypeVariable.errors.txt +++ b/tests/baselines/reference/functionCallOnConstrainedTypeVariable.errors.txt @@ -1,7 +1,7 @@ -tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(11,3): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. -tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(15,3): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. -tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(18,3): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. -tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(19,3): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. +tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(11,7): error TS2345: Argument of type '"s"' is not assignable to parameter of type 'never'. +tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(15,7): error TS2345: Argument of type '"s"' is not assignable to parameter of type 'never'. +tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(18,5): error TS2345: Argument of type '""' is not assignable to parameter of type 'never'. +tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(19,3): error TS2554: Expected 1 arguments, but got 4. ==== tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts (4 errors) ==== @@ -16,20 +16,20 @@ tests/cases/compiler/functionCallOnConstrainedTypeVariable.ts(19,3): error TS234 function call0(p: A | B) { p.a("s"); // Error - ~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. + ~~~ +!!! error TS2345: Argument of type '"s"' is not assignable to parameter of type 'never'. } function callN(p: T) { p.a("s"); // Error - ~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. + ~~~ +!!! error TS2345: Argument of type '"s"' is not assignable to parameter of type 'never'. var a: T["a"] = p.a; a(""); // Error - ~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. + ~~ +!!! error TS2345: Argument of type '""' is not assignable to parameter of type 'never'. a("", "", "", ""); // Error ~~~~~~~~~~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => string) | ((x: boolean) => string)' has no compatible call signatures. +!!! error TS2554: Expected 1 arguments, but got 4. } \ No newline at end of file diff --git a/tests/baselines/reference/tsxUnionElementType1.errors.txt b/tests/baselines/reference/tsxUnionElementType1.errors.txt index 9290e734d83..e2f5bef6333 100644 --- a/tests/baselines/reference/tsxUnionElementType1.errors.txt +++ b/tests/baselines/reference/tsxUnionElementType1.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/jsx/file.tsx(12,2): error TS2604: JSX element type 'SFCComp' does not have any construct or call signatures. +tests/cases/conformance/jsx/file.tsx(12,10): error TS2322: Type 'true' is not assignable to type 'never'. ==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== @@ -14,5 +14,6 @@ tests/cases/conformance/jsx/file.tsx(12,2): error TS2604: JSX element type 'SFCC var SFCComp = SFC1 || SFC2; - ~~~~~~~ -!!! error TS2604: JSX element type 'SFCComp' does not have any construct or call signatures. \ No newline at end of file + ~ +!!! error TS2322: Type 'true' is not assignable to type 'never'. +!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:23: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & { x: number; } & { x: boolean; }' \ No newline at end of file diff --git a/tests/baselines/reference/tsxUnionElementType2.errors.txt b/tests/baselines/reference/tsxUnionElementType2.errors.txt index 091d828dfe3..87f1d7fc2dc 100644 --- a/tests/baselines/reference/tsxUnionElementType2.errors.txt +++ b/tests/baselines/reference/tsxUnionElementType2.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/jsx/file.tsx(12,2): error TS2604: JSX element type 'SFCComp' does not have any construct or call signatures. +tests/cases/conformance/jsx/file.tsx(12,10): error TS2322: Type 'string' is not assignable to type 'never'. ==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== @@ -14,5 +14,6 @@ tests/cases/conformance/jsx/file.tsx(12,2): error TS2604: JSX element type 'SFCC var SFCComp = SFC1 || SFC2; - ~~~~~~~ -!!! error TS2604: JSX element type 'SFCComp' does not have any construct or call signatures. \ No newline at end of file + ~ +!!! error TS2322: Type 'string' is not assignable to type 'never'. +!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:23: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & { x: number; } & { x: boolean; }' \ No newline at end of file diff --git a/tests/baselines/reference/tsxUnionElementType3.errors.txt b/tests/baselines/reference/tsxUnionElementType3.errors.txt index bfdb61f4061..8f8ce3c28bd 100644 --- a/tests/baselines/reference/tsxUnionElementType3.errors.txt +++ b/tests/baselines/reference/tsxUnionElementType3.errors.txt @@ -1,4 +1,5 @@ -tests/cases/conformance/jsx/file.tsx(32,10): error TS2604: JSX element type 'RCComp' does not have any construct or call signatures. +tests/cases/conformance/jsx/file.tsx(32,17): error TS2322: Type 'string' is not assignable to type 'number & string'. + Type 'string' is not assignable to type 'number'. ==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== @@ -34,8 +35,10 @@ tests/cases/conformance/jsx/file.tsx(32,10): error TS2604: JSX element type 'RCC var RCComp = RC1 || RC2; // OK let a = ; - ~~~~~~ -!!! error TS2604: JSX element type 'RCComp' does not have any construct or call signatures. + ~ +!!! error TS2322: Type 'string' is not assignable to type 'number & string'. +!!! error TS2322: Type 'string' is not assignable to type 'number'. +!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:36: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes & { x: number; } & { children?: ReactNode; } & { x: string; } & { children?: ReactNode; }' let a1 = ; let a2 = ; let b = diff --git a/tests/baselines/reference/tsxUnionElementType4.errors.txt b/tests/baselines/reference/tsxUnionElementType4.errors.txt index 4ca07f26541..4b059d093f8 100644 --- a/tests/baselines/reference/tsxUnionElementType4.errors.txt +++ b/tests/baselines/reference/tsxUnionElementType4.errors.txt @@ -1,4 +1,5 @@ -tests/cases/conformance/jsx/file.tsx(32,10): error TS2604: JSX element type 'RCComp' does not have any construct or call signatures. +tests/cases/conformance/jsx/file.tsx(32,17): error TS2322: Type 'true' is not assignable to type 'number & string'. + Type 'true' is not assignable to type 'number'. tests/cases/conformance/jsx/file.tsx(33,10): error TS2322: Type '{ x: number; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & { children?: ReactNode; }'. Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes & { children?: ReactNode; }'. tests/cases/conformance/jsx/file.tsx(34,10): error TS2322: Type '{ prop: true; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & { children?: ReactNode; }'. @@ -38,8 +39,10 @@ tests/cases/conformance/jsx/file.tsx(34,10): error TS2322: Type '{ prop: true; } var PartRCComp = RC1 || RC4; // Error let a = ; - ~~~~~~ -!!! error TS2604: JSX element type 'RCComp' does not have any construct or call signatures. + ~ +!!! error TS2322: Type 'true' is not assignable to type 'number & string'. +!!! error TS2322: Type 'true' is not assignable to type 'number'. +!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:36: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes & { x: number; } & { children?: ReactNode; } & { x: string; } & { children?: ReactNode; }' let b = ~~~~~~~~~~ !!! error TS2322: Type '{ x: number; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & { children?: ReactNode; }'. diff --git a/tests/baselines/reference/unionTypeCallSignatures.errors.txt b/tests/baselines/reference/unionTypeCallSignatures.errors.txt index 5a67bde722c..c260d7ce511 100644 --- a/tests/baselines/reference/unionTypeCallSignatures.errors.txt +++ b/tests/baselines/reference/unionTypeCallSignatures.errors.txt @@ -2,9 +2,11 @@ tests/cases/conformance/types/union/unionTypeCallSignatures.ts(9,43): error TS23 tests/cases/conformance/types/union/unionTypeCallSignatures.ts(10,29): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string'. tests/cases/conformance/types/union/unionTypeCallSignatures.ts(15,29): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string'. tests/cases/conformance/types/union/unionTypeCallSignatures.ts(16,1): error TS2554: Expected 1 arguments, but got 0. -tests/cases/conformance/types/union/unionTypeCallSignatures.ts(19,1): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: number) => number) | ((a: string) => Date)' has no compatible call signatures. -tests/cases/conformance/types/union/unionTypeCallSignatures.ts(20,1): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: number) => number) | ((a: string) => Date)' has no compatible call signatures. -tests/cases/conformance/types/union/unionTypeCallSignatures.ts(21,1): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: number) => number) | ((a: string) => Date)' has no compatible call signatures. +tests/cases/conformance/types/union/unionTypeCallSignatures.ts(19,32): error TS2345: Argument of type '10' is not assignable to parameter of type 'number & string'. + Type '10' is not assignable to type 'string'. +tests/cases/conformance/types/union/unionTypeCallSignatures.ts(20,32): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number & string'. + Type '"hello"' is not assignable to type 'number'. +tests/cases/conformance/types/union/unionTypeCallSignatures.ts(21,1): error TS2554: Expected 1 arguments, but got 0. tests/cases/conformance/types/union/unionTypeCallSignatures.ts(24,1): error TS2554: Expected 1 arguments, but got 0. tests/cases/conformance/types/union/unionTypeCallSignatures.ts(26,36): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. tests/cases/conformance/types/union/unionTypeCallSignatures.ts(29,1): error TS2554: Expected 2 arguments, but got 0. @@ -56,14 +58,17 @@ tests/cases/conformance/types/union/unionTypeCallSignatures.ts(73,12): error TS2 var unionOfDifferentParameterTypes: { (a: number): number; } | { (a: string): Date; }; unionOfDifferentParameterTypes(10);// error - no call signatures - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: number) => number) | ((a: string) => Date)' has no compatible call signatures. + ~~ +!!! error TS2345: Argument of type '10' is not assignable to parameter of type 'number & string'. +!!! error TS2345: Type '10' is not assignable to type 'string'. unionOfDifferentParameterTypes("hello");// error - no call signatures - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: number) => number) | ((a: string) => Date)' has no compatible call signatures. + ~~~~~~~ +!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number & string'. +!!! error TS2345: Type '"hello"' is not assignable to type 'number'. unionOfDifferentParameterTypes();// error - no call signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: number) => number) | ((a: string) => Date)' has no compatible call signatures. +!!! error TS2554: Expected 1 arguments, but got 0. +!!! related TS6210 tests/cases/conformance/types/union/unionTypeCallSignatures.ts:18:40: An argument for 'a' was not provided. var unionOfDifferentNumberOfSignatures: { (a: number): number; } | { (a: number): Date; (a: string): boolean; }; unionOfDifferentNumberOfSignatures(); // error - no call signatures diff --git a/tests/baselines/reference/unionTypeConstructSignatures.errors.txt b/tests/baselines/reference/unionTypeConstructSignatures.errors.txt index a5ad21ba580..92adc83a0d2 100644 --- a/tests/baselines/reference/unionTypeConstructSignatures.errors.txt +++ b/tests/baselines/reference/unionTypeConstructSignatures.errors.txt @@ -2,9 +2,11 @@ tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(9,47): error tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(10,33): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string'. tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(15,33): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string'. tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(16,1): error TS2554: Expected 1 arguments, but got 0. -tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(19,1): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. -tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(20,1): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. -tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(21,1): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. +tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(19,36): error TS2345: Argument of type '10' is not assignable to parameter of type 'number & string'. + Type '10' is not assignable to type 'string'. +tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(20,36): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number & string'. + Type '"hello"' is not assignable to type 'number'. +tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(21,1): error TS2554: Expected 1 arguments, but got 0. tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(24,1): error TS2554: Expected 1 arguments, but got 0. tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(26,40): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'. tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(29,1): error TS2554: Expected 2 arguments, but got 0. @@ -55,14 +57,17 @@ tests/cases/conformance/types/union/unionTypeConstructSignatures.ts(70,12): erro var unionOfDifferentParameterTypes: { new (a: number): number; } | { new (a: string): Date; }; new unionOfDifferentParameterTypes(10);// error - no call signatures - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. + ~~ +!!! error TS2345: Argument of type '10' is not assignable to parameter of type 'number & string'. +!!! error TS2345: Type '10' is not assignable to type 'string'. new unionOfDifferentParameterTypes("hello");// error - no call signatures - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. + ~~~~~~~ +!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number & string'. +!!! error TS2345: Type '"hello"' is not assignable to type 'number'. new unionOfDifferentParameterTypes();// error - no call signatures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. +!!! error TS2554: Expected 1 arguments, but got 0. +!!! related TS6210 tests/cases/conformance/types/union/unionTypeConstructSignatures.ts:18:44: An argument for 'a' was not provided. var unionOfDifferentNumberOfSignatures: { new (a: number): number; } | { new (a: number): Date; new (a: string): boolean; }; new unionOfDifferentNumberOfSignatures(); // error - no call signatures diff --git a/tests/baselines/reference/unionTypeMembers.errors.txt b/tests/baselines/reference/unionTypeMembers.errors.txt index 6d9f1321a74..2a2f93d1a07 100644 --- a/tests/baselines/reference/unionTypeMembers.errors.txt +++ b/tests/baselines/reference/unionTypeMembers.errors.txt @@ -1,4 +1,6 @@ -tests/cases/conformance/types/union/unionTypeMembers.ts(44,1): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: string) => string) | ((a: number) => number)' has no compatible call signatures. +tests/cases/conformance/types/union/unionTypeMembers.ts(44,38): error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'string & number'. + Type 'string' is not assignable to type 'string & number'. + Type 'string' is not assignable to type 'number'. tests/cases/conformance/types/union/unionTypeMembers.ts(51,3): error TS2339: Property 'propertyOnlyInI1' does not exist on type 'I1 | I2'. Property 'propertyOnlyInI1' does not exist on type 'I2'. tests/cases/conformance/types/union/unionTypeMembers.ts(52,3): error TS2339: Property 'propertyOnlyInI2' does not exist on type 'I1 | I2'. @@ -54,8 +56,10 @@ tests/cases/conformance/types/union/unionTypeMembers.ts(54,3): error TS2339: Pro strOrNum = x.commonMethodDifferentReturnType(str); // string | union x.commonMethodDifferentParameterType; // No error - property exists x.commonMethodDifferentParameterType(strOrNum); // error - no call signatures because the type of this property is ((a: string) => string) | (a: number) => number - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((a: string) => string) | ((a: number) => number)' has no compatible call signatures. + ~~~~~~~~ +!!! error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'string & number'. +!!! error TS2345: Type 'string' is not assignable to type 'string & number'. +!!! error TS2345: Type 'string' is not assignable to type 'number'. // and the call signatures arent identical num = x.commonMethodWithTypeParameter(num); num = x.commonMethodWithOwnTypeParameter(num); diff --git a/tests/cases/compiler/callsOnComplexSignatures.tsx b/tests/cases/compiler/callsOnComplexSignatures.tsx new file mode 100644 index 00000000000..eb1ff9e753a --- /dev/null +++ b/tests/cases/compiler/callsOnComplexSignatures.tsx @@ -0,0 +1,106 @@ +// @jsx: react +// @esModuleInterop: true +// @strict: true +/// +import React from "react"; + +// Simple calls from real usecases +function test1() { + type stringType1 = "foo" | "bar"; + type stringType2 = "baz" | "bar"; + + interface Temp1 { + getValue(name: stringType1): number; + } + + interface Temp2 { + getValue(name: stringType2): string; + } + + function test(t: Temp1 | Temp2) { + const z = t.getValue("bar"); // Should be fine + } +} + +function test2() { + interface Messages { + readonly foo: (options: { [key: string]: any, b: number }) => string; + readonly bar: (options: { [key: string]: any, a: string }) => string; + } + + const messages: Messages = { + foo: (options) => "Foo", + bar: (options) => "Bar", + }; + + const test1 = (type: "foo" | "bar") => + messages[type]({ a: "A", b: 0 }); +} + +function test3(items: string[] | number[]) { + items.forEach(item => console.log(item)); +} + +function test4( + arg1: ((...objs: {x: number}[]) => number) | ((...objs: {y: number}[]) => number), + arg2: ((a: {x: number}, b: object) => number) | ((a: object, b: {x: number}) => number), + arg3: ((a: {x: number}, ...objs: {y: number}[]) => number) | ((...objs: {x: number}[]) => number), + arg4: ((a?: {x: number}, b?: {x: number}) => number) | ((a?: {y: number}) => number), + arg5: ((a?: {x: number}, ...b: {x: number}[]) => number) | ((a?: {y: number}) => number), + arg6: ((a?: {x: number}, b?: {x: number}) => number) | ((...a: {y: number}[]) => number), +) { + arg1(); + arg1({x: 0, y: 0}); + arg1({x: 0, y: 0}, {x: 1, y: 1}); + + arg2({x: 0}, {x: 0}); + + arg3({x: 0}); + arg3({x: 0}, {x: 0, y: 0}); + arg3({x: 0}, {x: 0, y: 0}, {x: 0, y: 0}); + + arg4(); + arg4({x: 0, y: 0}); + arg4({x: 0, y: 0}, {x: 0}); + + arg5(); + arg5({x: 0, y: 0}); + arg5({x: 0, y: 0}, {x: 0}); + + arg6(); + arg6({x: 0, y: 0}); + arg6({x: 0, y: 0}, {x: 0, y: 0}); + arg6({x: 0, y: 0}, {x: 0, y: 0}, {y: 0}); +} + +// JSX Tag names +function test5() { + // Pair of non-like intrinsics + function render(url?: string): React.ReactNode { + const Tag = url ? 'a' : 'button'; + return test; + } + + // Union of all intrinsics and components of `any` + function App(props: { component:React.ReactType }) { + const Comp: React.ReactType = props.component; + return (); + } + + // custom components with non-subset props + function render2() { + interface P1 { + p?: boolean; + c?: string; + } + interface P2 { + p?: boolean; + c?: any; + d?: any; + } + + var C: React.ComponentType | React.ComponentType = null as any; + + const a = ; + } +} diff --git a/tests/cases/fourslash/calledUnionsOfDissimilarTyeshaveGoodDisplay.ts b/tests/cases/fourslash/calledUnionsOfDissimilarTyeshaveGoodDisplay.ts new file mode 100644 index 00000000000..e8c42871b67 --- /dev/null +++ b/tests/cases/fourslash/calledUnionsOfDissimilarTyeshaveGoodDisplay.ts @@ -0,0 +1,53 @@ +/// + +////declare const callableThing1: +//// | ((o1: {x: number}) => void) +//// | ((o1: {y: number}) => void) +//// ; +//// +////callableThing1(/*1*/); +//// +////declare const callableThing2: +//// | ((o1: {x: number}) => void) +//// | ((o2: {y: number}) => void) +//// ; +//// +////callableThing2(/*2*/); +//// +////declare const callableThing3: +//// | ((o1: {x: number}) => void) +//// | ((o2: {y: number}) => void) +//// | ((o3: {z: number}) => void) +//// | ((o4: {u: number}) => void) +//// | ((o5: {v: number}) => void) +//// ; +//// +////callableThing3(/*3*/); +//// +////declare const callableThing4: +//// | ((o1: {x: number}) => void) +//// | ((o2: {y: number}) => void) +//// | ((o3: {z: number}) => void) +//// | ((o4: {u: number}) => void) +//// | ((o5: {v: number}) => void) +//// | ((o6: {w: number}) => void) +//// ; +//// +////callableThing4(/*4*/); + +verify.signatureHelp({ + marker: "1", + text: "callableThing1(o1: { x: number; } & { y: number; }): void" +}, +{ + marker: "2", + text: "callableThing2(arg0: { x: number; } & { y: number; }): void" +}, +{ + marker: "3", + text: "callableThing3(arg0: { x: number; } & { y: number; } & { z: number; } & { u: number; } & { v: number; }): void" +}, +{ + marker: "4", + text: "callableThing4(arg0: { x: number; } & { y: number; } & { z: number; } & { u: number; } & { v: number; } & { w: number; }): void" +}); diff --git a/tests/cases/fourslash/tsxCompletionUnionElementType.ts b/tests/cases/fourslash/tsxCompletionUnionElementType.ts index 7d458f83493..b554d385179 100644 --- a/tests/cases/fourslash/tsxCompletionUnionElementType.ts +++ b/tests/cases/fourslash/tsxCompletionUnionElementType.ts @@ -19,4 +19,4 @@ //// var SFCComp = SFC1 || SFC2; //// -verify.completions({ marker: "", exact: undefined }); +verify.completions({ marker: "", exact: ["x"] }); diff --git a/tests/cases/fourslash/tsxGoToDefinitionUnionElementType1.ts b/tests/cases/fourslash/tsxGoToDefinitionUnionElementType1.ts index 9e4cb4e1b1b..957b6b811e0 100644 --- a/tests/cases/fourslash/tsxGoToDefinitionUnionElementType1.ts +++ b/tests/cases/fourslash/tsxGoToDefinitionUnionElementType1.ts @@ -22,5 +22,5 @@ //// <[|SFC/*one*/Comp|] x /> verify.goToDefinition({ - "one": ["def"], + "one": ["def", "pt1"], }); From 93acaac0875ca28bba5b1b3ab626ebf571c3d1e4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 20 Dec 2018 07:17:42 -0800 Subject: [PATCH 071/120] Fix discriminant property check --- src/compiler/checker.ts | 46 +++++++++++++++++------------------------ src/compiler/types.ts | 20 ++++++++++-------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 23c5568f2e5..cf630f5e6fd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7669,38 +7669,37 @@ namespace ts { return props[0]; } let declarations: Declaration[] | undefined; - let commonType: Type | undefined; + let firstType: Type | undefined; let nameType: Type | undefined; const propTypes: Type[] = []; - let first = true; - let commonValueDeclaration: Declaration | undefined; + let firstValueDeclaration: Declaration | undefined; let hasNonUniformValueDeclaration = false; for (const prop of props) { - if (!commonValueDeclaration) { - commonValueDeclaration = prop.valueDeclaration; + if (!firstValueDeclaration) { + firstValueDeclaration = prop.valueDeclaration; } - else if (prop.valueDeclaration !== commonValueDeclaration) { + else if (prop.valueDeclaration !== firstValueDeclaration) { hasNonUniformValueDeclaration = true; } declarations = addRange(declarations, prop.declarations); const type = getTypeOfSymbol(prop); - if (first) { - commonType = type; + if (!firstType) { + firstType = type; nameType = prop.nameType; - first = false; } - else { - if (type !== commonType) { - checkFlags |= CheckFlags.HasNonUniformType; - } + else if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; + } + if (isLiteralType(type)) { + checkFlags |= CheckFlags.HasLiteralType; } propTypes.push(type); } addRange(propTypes, indexTypes); const result = createSymbol(SymbolFlags.Property | commonFlags, name, syntheticFlag | checkFlags); result.containingType = containingType; - if (!hasNonUniformValueDeclaration && commonValueDeclaration) { - result.valueDeclaration = commonValueDeclaration; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; } result.declarations = declarations!; result.nameType = nameType; @@ -14814,17 +14813,8 @@ namespace ts { } function isDiscriminantType(type: Type): boolean { - if (type.flags & TypeFlags.Union) { - if (type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral)) { - return true; - } - let combined = 0; - for (const t of (type).types) combined |= t.flags; - if (combined & TypeFlags.Unit && !(combined & TypeFlags.Instantiable)) { - return true; - } - } - return false; + return !!(type.flags & TypeFlags.Union && + (type.flags & (TypeFlags.Boolean | TypeFlags.EnumLiteral) || !isGenericIndexType(type))); } function isDiscriminantProperty(type: Type | undefined, name: __String) { @@ -14832,7 +14822,9 @@ namespace ts { const prop = getUnionOrIntersectionProperty(type, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = !!((prop).checkFlags & CheckFlags.HasNonUniformType) && isDiscriminantType(getTypeOfSymbol(prop)); + (prop).isDiscriminantProperty = + ((prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + isDiscriminantType(getTypeOfSymbol(prop)); } return !!(prop).isDiscriminantProperty; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b70b4e4d3d0..6e2e480503a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3677,15 +3677,17 @@ namespace ts { Readonly = 1 << 3, // Readonly transient symbol Partial = 1 << 4, // Synthetic property present in some but not all constituents HasNonUniformType = 1 << 5, // Synthetic property with non-uniform type in constituents - ContainsPublic = 1 << 6, // Synthetic property with public constituent(s) - ContainsProtected = 1 << 7, // Synthetic property with protected constituent(s) - ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s) - ContainsStatic = 1 << 9, // Synthetic property with static constituent(s) - Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name - ReverseMapped = 1 << 11, // Property of reverse-inferred homomorphic mapped type - OptionalParameter = 1 << 12, // Optional parameter - RestParameter = 1 << 13, // Rest parameter - Synthetic = SyntheticProperty | SyntheticMethod + HasLiteralType = 1 << 6, // Synthetic property with at least one literal type in constituents + ContainsPublic = 1 << 7, // Synthetic property with public constituent(s) + ContainsProtected = 1 << 8, // Synthetic property with protected constituent(s) + ContainsPrivate = 1 << 9, // Synthetic property with private constituent(s) + ContainsStatic = 1 << 10, // Synthetic property with static constituent(s) + Late = 1 << 11, // Late-bound symbol for a computed property with a dynamic name + ReverseMapped = 1 << 12, // Property of reverse-inferred homomorphic mapped type + OptionalParameter = 1 << 13, // Optional parameter + RestParameter = 1 << 14, // Rest parameter + Synthetic = SyntheticProperty | SyntheticMethod, + Discriminant = HasNonUniformType | HasLiteralType } /* @internal */ From 3e93461fc84108bdcd8abafbcbf96b5c8bbcc284 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 20 Dec 2018 07:21:49 -0800 Subject: [PATCH 072/120] Add regression test --- .../compiler/discriminantPropertyCheck.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/cases/compiler/discriminantPropertyCheck.ts b/tests/cases/compiler/discriminantPropertyCheck.ts index 16fb847bdf0..a24fe07973b 100644 --- a/tests/cases/compiler/discriminantPropertyCheck.ts +++ b/tests/cases/compiler/discriminantPropertyCheck.ts @@ -99,3 +99,25 @@ function func2(inst: Instance) { } } } + +// Repro from #29106 + +const f = (_a: string, _b: string): void => {}; + +interface A { + a?: string; + b?: string; +} + +interface B { + a: string; + b: string; +} + +type U = A | B; + +const u: U = {} as any; + +u.a && u.b && f(u.a, u.b); + +u.b && u.a && f(u.a, u.b); From 017c11acaa0747481f978bb76a25c32315f3f5a7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 20 Dec 2018 07:21:56 -0800 Subject: [PATCH 073/120] Accept new baselines --- .../discriminantPropertyCheck.errors.txt | 22 ++++++ .../reference/discriminantPropertyCheck.js | 27 ++++++++ .../discriminantPropertyCheck.symbols | 66 ++++++++++++++++++ .../reference/discriminantPropertyCheck.types | 68 +++++++++++++++++++ 4 files changed, 183 insertions(+) diff --git a/tests/baselines/reference/discriminantPropertyCheck.errors.txt b/tests/baselines/reference/discriminantPropertyCheck.errors.txt index a57e17ed81e..313116edde5 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.errors.txt +++ b/tests/baselines/reference/discriminantPropertyCheck.errors.txt @@ -106,4 +106,26 @@ tests/cases/compiler/discriminantPropertyCheck.ts(65,9): error TS2532: Object is } } } + + // Repro from #29106 + + const f = (_a: string, _b: string): void => {}; + + interface A { + a?: string; + b?: string; + } + + interface B { + a: string; + b: string; + } + + type U = A | B; + + const u: U = {} as any; + + u.a && u.b && f(u.a, u.b); + + u.b && u.a && f(u.a, u.b); \ No newline at end of file diff --git a/tests/baselines/reference/discriminantPropertyCheck.js b/tests/baselines/reference/discriminantPropertyCheck.js index e58f28dc5a1..8b2c6f122bc 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.js +++ b/tests/baselines/reference/discriminantPropertyCheck.js @@ -98,6 +98,28 @@ function func2(inst: Instance) { } } } + +// Repro from #29106 + +const f = (_a: string, _b: string): void => {}; + +interface A { + a?: string; + b?: string; +} + +interface B { + a: string; + b: string; +} + +type U = A | B; + +const u: U = {} as any; + +u.a && u.b && f(u.a, u.b); + +u.b && u.a && f(u.a, u.b); //// [discriminantPropertyCheck.js] @@ -161,3 +183,8 @@ function func2(inst) { } } } +// Repro from #29106 +var f = function (_a, _b) { }; +var u = {}; +u.a && u.b && f(u.a, u.b); +u.b && u.a && f(u.a, u.b); diff --git a/tests/baselines/reference/discriminantPropertyCheck.symbols b/tests/baselines/reference/discriminantPropertyCheck.symbols index 564cc55e3d3..78884eaf69f 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.symbols +++ b/tests/baselines/reference/discriminantPropertyCheck.symbols @@ -311,3 +311,69 @@ function func2(inst: Instance) { } } +// Repro from #29106 + +const f = (_a: string, _b: string): void => {}; +>f : Symbol(f, Decl(discriminantPropertyCheck.ts, 102, 5)) +>_a : Symbol(_a, Decl(discriminantPropertyCheck.ts, 102, 11)) +>_b : Symbol(_b, Decl(discriminantPropertyCheck.ts, 102, 22)) + +interface A { +>A : Symbol(A, Decl(discriminantPropertyCheck.ts, 102, 47)) + + a?: string; +>a : Symbol(A.a, Decl(discriminantPropertyCheck.ts, 104, 13)) + + b?: string; +>b : Symbol(A.b, Decl(discriminantPropertyCheck.ts, 105, 13)) +} + +interface B { +>B : Symbol(B, Decl(discriminantPropertyCheck.ts, 107, 1)) + + a: string; +>a : Symbol(B.a, Decl(discriminantPropertyCheck.ts, 109, 13)) + + b: string; +>b : Symbol(B.b, Decl(discriminantPropertyCheck.ts, 110, 12)) +} + +type U = A | B; +>U : Symbol(U, Decl(discriminantPropertyCheck.ts, 112, 1)) +>A : Symbol(A, Decl(discriminantPropertyCheck.ts, 102, 47)) +>B : Symbol(B, Decl(discriminantPropertyCheck.ts, 107, 1)) + +const u: U = {} as any; +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>U : Symbol(U, Decl(discriminantPropertyCheck.ts, 112, 1)) + +u.a && u.b && f(u.a, u.b); +>u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>f : Symbol(f, Decl(discriminantPropertyCheck.ts, 102, 5)) +>u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) + +u.b && u.a && f(u.a, u.b); +>u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>f : Symbol(f, Decl(discriminantPropertyCheck.ts, 102, 5)) +>u.a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>a : Symbol(a, Decl(discriminantPropertyCheck.ts, 104, 13), Decl(discriminantPropertyCheck.ts, 109, 13)) +>u.b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) +>u : Symbol(u, Decl(discriminantPropertyCheck.ts, 116, 5)) +>b : Symbol(b, Decl(discriminantPropertyCheck.ts, 105, 13), Decl(discriminantPropertyCheck.ts, 110, 12)) + diff --git a/tests/baselines/reference/discriminantPropertyCheck.types b/tests/baselines/reference/discriminantPropertyCheck.types index 4f34ceafc43..c243ef5e798 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.types +++ b/tests/baselines/reference/discriminantPropertyCheck.types @@ -310,3 +310,71 @@ function func2(inst: Instance) { } } +// Repro from #29106 + +const f = (_a: string, _b: string): void => {}; +>f : (_a: string, _b: string) => void +>(_a: string, _b: string): void => {} : (_a: string, _b: string) => void +>_a : string +>_b : string + +interface A { + a?: string; +>a : string | undefined + + b?: string; +>b : string | undefined +} + +interface B { + a: string; +>a : string + + b: string; +>b : string +} + +type U = A | B; +>U : U + +const u: U = {} as any; +>u : U +>{} as any : any +>{} : {} + +u.a && u.b && f(u.a, u.b); +>u.a && u.b && f(u.a, u.b) : void | "" | undefined +>u.a && u.b : string | undefined +>u.a : string | undefined +>u : U +>a : string | undefined +>u.b : string | undefined +>u : U +>b : string | undefined +>f(u.a, u.b) : void +>f : (_a: string, _b: string) => void +>u.a : string +>u : U +>a : string +>u.b : string +>u : U +>b : string + +u.b && u.a && f(u.a, u.b); +>u.b && u.a && f(u.a, u.b) : void | "" | undefined +>u.b && u.a : string | undefined +>u.b : string | undefined +>u : U +>b : string | undefined +>u.a : string | undefined +>u : U +>a : string | undefined +>f(u.a, u.b) : void +>f : (_a: string, _b: string) => void +>u.a : string +>u : U +>a : string +>u.b : string +>u : U +>b : string + From 2a02077e3797ce12f8edb478d4335ed4b87c9d92 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 09:32:38 -0800 Subject: [PATCH 074/120] Add name of test in all describe blocks in it --- src/testRunner/tsconfig.json | 3 +- .../unittests/commandLineParsing.ts | 4 +-- src/testRunner/unittests/compileOnSave.ts | 4 +-- src/testRunner/unittests/moduleResolution.ts | 14 ++++---- .../{programMissingFiles.ts => programApi.ts} | 28 +++++++++++++++ .../unittests/programNoParseFalsyFileNames.ts | 36 ------------------- src/testRunner/unittests/projectErrors.ts | 2 +- src/testRunner/unittests/projectReferences.ts | 2 +- .../unittests/reuseProgramStructure.ts | 6 ++-- src/testRunner/unittests/session.ts | 10 +++--- src/testRunner/unittests/tsconfigParsing.ts | 2 +- src/testRunner/unittests/typingsInstaller.ts | 18 +++++----- 12 files changed, 60 insertions(+), 69 deletions(-) rename src/testRunner/unittests/{programMissingFiles.ts => programApi.ts} (79%) delete mode 100644 src/testRunner/unittests/programNoParseFalsyFileNames.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 32772ed311b..10b8637aede 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -68,8 +68,7 @@ "unittests/parsePseudoBigInt.ts", "unittests/paths.ts", "unittests/printer.ts", - "unittests/programMissingFiles.ts", - "unittests/programNoParseFalsyFileNames.ts", + "unittests/programApi.ts", "unittests/projectErrors.ts", "unittests/projectReferences.ts", "unittests/publicApi.ts", diff --git a/src/testRunner/unittests/commandLineParsing.ts b/src/testRunner/unittests/commandLineParsing.ts index 7e2090eb3bd..7e8ba8f84bb 100644 --- a/src/testRunner/unittests/commandLineParsing.ts +++ b/src/testRunner/unittests/commandLineParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("parseCommandLine", () => { + describe("commandLineParsing:: parseCommandLine", () => { function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) { const parsed = parseCommandLine(commandLine); @@ -367,7 +367,7 @@ namespace ts { }); }); - describe("parseBuildOptions", () => { + describe("commandLineParsing:: parseBuildOptions", () => { function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) { const parsed = parseBuildCommand(commandLine); const parsedBuildOptions = JSON.stringify(parsed.buildOptions); diff --git a/src/testRunner/unittests/compileOnSave.ts b/src/testRunner/unittests/compileOnSave.ts index 4b98d71ef0a..07a0aa5d3ae 100644 --- a/src/testRunner/unittests/compileOnSave.ts +++ b/src/testRunner/unittests/compileOnSave.ts @@ -6,7 +6,7 @@ namespace ts.projectSystem { return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); } - describe("CompileOnSave affected list", () => { + describe("compileOnSave:: affected list", () => { function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); @@ -504,7 +504,7 @@ namespace ts.projectSystem { }); }); - describe("EmitFile test", () => { + describe("compileOnSave:: EmitFile test", () => { it("should respect line endings", () => { test("\n"); test("\r\n"); diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index 05e17384978..28f25437149 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -80,7 +80,7 @@ namespace ts { } } - describe("Node module resolution - relative paths", () => { + describe("moduleResolution:: Node module resolution - relative paths", () => { function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { for (const ext of supportedTSExtensions) { @@ -200,7 +200,7 @@ namespace ts { }); }); - describe("Node module resolution - non-relative paths", () => { + describe("moduleResolution:: Node module resolution - non-relative paths", () => { it("computes correct commonPrefix for moduleName cache", () => { const resolutionCache = createModuleResolutionCache("/", (f) => f); let cache = resolutionCache.getOrCreateCacheForModuleName("a"); @@ -457,7 +457,7 @@ namespace ts { }); }); - describe("Module resolution - relative imports", () => { + describe("moduleResolution:: Relative imports", () => { function test(files: Map, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { const options: CompilerOptions = { module: ModuleKind.CommonJS }; const host: CompilerHost = { @@ -530,7 +530,7 @@ export = C; }); }); - describe("Files with different casing", () => { + describe("moduleResolution:: Files with different casing", () => { let library: SourceFile; function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -651,7 +651,7 @@ import b = require("./moduleB"); }); }); - describe("baseUrl augmented module resolution", () => { + describe("moduleResolution:: baseUrl augmented module resolution", () => { it("module resolution without path mappings/rootDirs", () => { test(/*hasDirectoryExists*/ false); @@ -1098,7 +1098,7 @@ import b = require("./moduleB"); }); }); - describe("ModuleResolutionHost.directoryExists", () => { + describe("moduleResolution:: ModuleResolutionHost.directoryExists", () => { it("No 'fileExists' calls if containing directory is missing", () => { const host: ModuleResolutionHost = { readFile: notImplemented, @@ -1111,7 +1111,7 @@ import b = require("./moduleB"); }); }); - describe("Type reference directive resolution: ", () => { + describe("moduleResolution:: Type reference directive resolution: ", () => { function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles)); const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host); diff --git a/src/testRunner/unittests/programMissingFiles.ts b/src/testRunner/unittests/programApi.ts similarity index 79% rename from src/testRunner/unittests/programMissingFiles.ts rename to src/testRunner/unittests/programApi.ts index 55dfa7f5cc2..8fa107513fe 100644 --- a/src/testRunner/unittests/programMissingFiles.ts +++ b/src/testRunner/unittests/programApi.ts @@ -97,6 +97,34 @@ namespace ts { "d:/pretend/nonexistent4.tsx" ]); }); + + it("should not have missing file paths", () => { + const testSource = ` + class Foo extends HTMLElement { + bar: string = 'baz'; + }`; + + const host: CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => { + return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "", + writeFile: (_fileName, _content) => { throw new Error("unsupported"); }, + getCurrentDirectory: () => sys.getCurrentDirectory(), + getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), + getNewLine: () => sys.newLine, + useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, + fileExists: fileName => fileName === "test.ts", + readFile: fileName => fileName === "test.ts" ? testSource : undefined, + resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, + getDirectories: _path => { throw new Error("unsupported"); }, + }; + + const program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host); + assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1"); + assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0"); + assert(program.getFileProcessingDiagnostics().getDiagnostics().length === 0, "expected 'getFileProcessingDiagnostics' length to be 0"); + }); }); describe("Program.isSourceFileFromExternalLibrary", () => { diff --git a/src/testRunner/unittests/programNoParseFalsyFileNames.ts b/src/testRunner/unittests/programNoParseFalsyFileNames.ts deleted file mode 100644 index 8040e7c7f43..00000000000 --- a/src/testRunner/unittests/programNoParseFalsyFileNames.ts +++ /dev/null @@ -1,36 +0,0 @@ -namespace ts { - describe("programNoParseFalsyFileNames", () => { - let program: Program; - - beforeEach(() => { - const testSource = ` - class Foo extends HTMLElement { - bar: string = 'baz'; - }`; - - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => { - return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "", - writeFile: (_fileName, _content) => { throw new Error("unsupported"); }, - getCurrentDirectory: () => sys.getCurrentDirectory(), - getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), - getNewLine: () => sys.newLine, - useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, - fileExists: fileName => fileName === "test.ts", - readFile: fileName => fileName === "test.ts" ? testSource : undefined, - resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, - getDirectories: _path => { throw new Error("unsupported"); }, - }; - - program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host); - }); - - it("should not have missing file paths", () => { - assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1"); - assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0"); - assert(program.getFileProcessingDiagnostics().getDiagnostics().length === 0, "expected 'getFileProcessingDiagnostics' length to be 0"); - }); - }); -} \ No newline at end of file diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/projectErrors.ts index b17494b59fb..d243e9d5ea4 100644 --- a/src/testRunner/unittests/projectErrors.ts +++ b/src/testRunner/unittests/projectErrors.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("Project errors", () => { + describe("tsserver:: Project Errors", () => { function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: ReadonlyArray): void { assert.isTrue(projectFiles !== undefined, "missing project files"); checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); diff --git a/src/testRunner/unittests/projectReferences.ts b/src/testRunner/unittests/projectReferences.ts index 5b99e01585d..f67faa4609b 100644 --- a/src/testRunner/unittests/projectReferences.ts +++ b/src/testRunner/unittests/projectReferences.ts @@ -308,7 +308,7 @@ namespace ts { }); }); - describe("errors when a file in a composite project occurs outside the root", () => { + describe("project-references errors when a file in a composite project occurs outside the root", () => { it("Errors when a file is outside the rootdir", () => { const spec: TestSpecification = { "/alpha": { diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 9e56b48a911..63b670912d8 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -210,7 +210,7 @@ namespace ts { checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); } - describe("Reuse program structure", () => { + describe("Reuse program structure:: General", () => { const target = ScriptTarget.Latest; const files: NamedSourceText[] = [ { @@ -895,7 +895,7 @@ namespace ts { }); }); - describe("host is optional", () => { + describe("Reuse program structure:: host is optional", () => { it("should work if host is not provided", () => { createProgram([], {}); }); @@ -905,7 +905,7 @@ namespace ts { import createTestSystem = TestFSWithWatch.createWatchedSystem; import libFile = TestFSWithWatch.libFile; - describe("isProgramUptoDate should return true when there is no change in compiler options and", () => { + describe("Reuse program structure:: isProgramUptoDate should return true when there is no change in compiler options and", () => { function verifyProgramIsUptoDate( program: Program, newRootFileNames: string[], diff --git a/src/testRunner/unittests/session.ts b/src/testRunner/unittests/session.ts index bcbadac1d52..927b22fb475 100644 --- a/src/testRunner/unittests/session.ts +++ b/src/testRunner/unittests/session.ts @@ -35,7 +35,7 @@ namespace ts.server { } } - describe("the Session class", () => { + describe("tsserver:: Session:: General functionality", () => { let session: TestSession; let lastSent: protocol.Message; @@ -418,7 +418,7 @@ namespace ts.server { }); }); - describe("exceptions", () => { + describe("tsserver:: Session:: exceptions", () => { // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test let oldPrepare: AnyFunction; @@ -489,7 +489,7 @@ namespace ts.server { }); }); - describe("how Session is extendable via subclassing", () => { + describe("tsserver:: Session:: how Session is extendable via subclassing", () => { class TestSession extends Session { lastSent: protocol.Message | undefined; customHandler = "testhandler"; @@ -558,7 +558,7 @@ namespace ts.server { }); }); - describe("an example of using the Session API to create an in-process server", () => { + describe("tsserver:: Session:: an example of using the Session API to create an in-process server", () => { class InProcSession extends Session { private queue: protocol.Request[] = []; constructor(private client: InProcClient) { @@ -710,7 +710,7 @@ namespace ts.server { }); }); - describe("helpers", () => { + describe("tsserver:: Session:: helpers", () => { it(getLocationInNewDocument.name, () => { const text = `// blank line\nconst x = 0;`; const renameLocationInOldText = text.indexOf("0"); diff --git a/src/testRunner/unittests/tsconfigParsing.ts b/src/testRunner/unittests/tsconfigParsing.ts index 255129d9fb6..909af429d6e 100644 --- a/src/testRunner/unittests/tsconfigParsing.ts +++ b/src/testRunner/unittests/tsconfigParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("parseConfigFileTextToJson", () => { + describe("tsconfigParsing:: parseConfigFileTextToJson", () => { function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index 7e0790b7dbb..34c5fa2a413 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -48,7 +48,7 @@ namespace ts.projectSystem { import typingsName = TI.typingsName; - describe("local module", () => { + describe("typingsInstaller:: local module", () => { it("should not be picked up", () => { const f1 = { path: "/a/app.js", @@ -87,7 +87,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller", () => { + describe("typingsInstaller:: General functionality", () => { it("configured projects (typings installed) 1", () => { const file1 = { path: "/a/b/app.js", @@ -1250,7 +1250,7 @@ namespace ts.projectSystem { }); }); - describe("Validate package name:", () => { + describe("typingsInstaller:: Validate package name:", () => { it("name cannot be too long", () => { let packageName = "a"; for (let i = 0; i < 8; i++) { @@ -1274,7 +1274,7 @@ namespace ts.projectSystem { }); }); - describe("Invalid package names", () => { + describe("typingsInstaller:: Invalid package names", () => { it("should not be installed", () => { const f1 = { path: "/a/b/app.js", @@ -1306,7 +1306,7 @@ namespace ts.projectSystem { }); }); - describe("discover typings", () => { + describe("typingsInstaller:: discover typings", () => { const emptySafeList = emptyMap; it("should use mappings from safe list", () => { @@ -1518,7 +1518,7 @@ namespace ts.projectSystem { }); }); - describe("telemetry events", () => { + describe("typingsInstaller:: telemetry events", () => { it("should be received", () => { const f1 = { path: "/a/app.js", @@ -1568,7 +1568,7 @@ namespace ts.projectSystem { }); }); - describe("progress notifications", () => { + describe("typingsInstaller:: progress notifications", () => { it("should be sent for success", () => { const f1 = { path: "/a/app.js", @@ -1677,7 +1677,7 @@ namespace ts.projectSystem { }); }); - describe("typing installer's npm installation command", () => { + describe("typingsInstaller:: npm installation command", () => { const npmPath = "npm", tsVersion = "2.9.0-dev.20180410"; const packageNames = ["@types/graphql@ts2.8", "@types/highlight.js@ts2.8", "@types/jest@ts2.8", "@types/mini-css-extract-plugin@ts2.8", "@types/mongoose@ts2.8", "@types/pg@ts2.8", "@types/webpack-bundle-analyzer@ts2.8", "@types/enhanced-resolve@ts2.8", "@types/eslint-plugin-prettier@ts2.8", "@types/friendly-errors-webpack-plugin@ts2.8", "@types/hammerjs@ts2.8", "@types/history@ts2.8", "@types/image-size@ts2.8", "@types/js-cookie@ts2.8", "@types/koa-compress@ts2.8", "@types/less@ts2.8", "@types/material-ui@ts2.8", "@types/mysql@ts2.8", "@types/nodemailer@ts2.8", "@types/prettier@ts2.8", "@types/query-string@ts2.8", "@types/react-places-autocomplete@ts2.8", "@types/react-router@ts2.8", "@types/react-router-config@ts2.8", "@types/react-select@ts2.8", "@types/react-transition-group@ts2.8", "@types/redux-form@ts2.8", "@types/abbrev@ts2.8", "@types/accepts@ts2.8", "@types/acorn@ts2.8", "@types/ansi-regex@ts2.8", "@types/ansi-styles@ts2.8", "@types/anymatch@ts2.8", "@types/apollo-codegen@ts2.8", "@types/are-we-there-yet@ts2.8", "@types/argparse@ts2.8", "@types/arr-union@ts2.8", "@types/array-find-index@ts2.8", "@types/array-uniq@ts2.8", "@types/array-unique@ts2.8", "@types/arrify@ts2.8", "@types/assert-plus@ts2.8", "@types/async@ts2.8", "@types/autoprefixer@ts2.8", "@types/aws4@ts2.8", "@types/babel-code-frame@ts2.8", "@types/babel-generator@ts2.8", "@types/babel-plugin-syntax-jsx@ts2.8", "@types/babel-template@ts2.8", "@types/babel-traverse@ts2.8", "@types/babel-types@ts2.8", "@types/babylon@ts2.8", "@types/base64-js@ts2.8", "@types/basic-auth@ts2.8", "@types/big.js@ts2.8", "@types/bl@ts2.8", "@types/bluebird@ts2.8", "@types/body-parser@ts2.8", "@types/bonjour@ts2.8", "@types/boom@ts2.8", "@types/brace-expansion@ts2.8", "@types/braces@ts2.8", "@types/brorand@ts2.8", "@types/browser-resolve@ts2.8", "@types/bson@ts2.8", "@types/buffer-equal@ts2.8", "@types/builtin-modules@ts2.8", "@types/bytes@ts2.8", "@types/callsites@ts2.8", "@types/camelcase@ts2.8", "@types/camelcase-keys@ts2.8", "@types/caseless@ts2.8", "@types/change-emitter@ts2.8", "@types/check-types@ts2.8", "@types/cheerio@ts2.8", "@types/chokidar@ts2.8", "@types/chownr@ts2.8", "@types/circular-json@ts2.8", "@types/classnames@ts2.8", "@types/clean-css@ts2.8", "@types/clone@ts2.8", "@types/co-body@ts2.8", "@types/color@ts2.8", "@types/color-convert@ts2.8", "@types/color-name@ts2.8", "@types/color-string@ts2.8", "@types/colors@ts2.8", "@types/combined-stream@ts2.8", "@types/common-tags@ts2.8", "@types/component-emitter@ts2.8", "@types/compressible@ts2.8", "@types/compression@ts2.8", "@types/concat-stream@ts2.8", "@types/connect-history-api-fallback@ts2.8", "@types/content-disposition@ts2.8", "@types/content-type@ts2.8", "@types/convert-source-map@ts2.8", "@types/cookie@ts2.8", "@types/cookie-signature@ts2.8", "@types/cookies@ts2.8", "@types/core-js@ts2.8", "@types/cosmiconfig@ts2.8", "@types/create-react-class@ts2.8", "@types/cross-spawn@ts2.8", "@types/cryptiles@ts2.8", "@types/css-modules-require-hook@ts2.8", "@types/dargs@ts2.8", "@types/dateformat@ts2.8", "@types/debug@ts2.8", "@types/decamelize@ts2.8", "@types/decompress@ts2.8", "@types/decompress-response@ts2.8", "@types/deep-equal@ts2.8", "@types/deep-extend@ts2.8", "@types/deepmerge@ts2.8", "@types/defined@ts2.8", "@types/del@ts2.8", "@types/depd@ts2.8", "@types/destroy@ts2.8", "@types/detect-indent@ts2.8", "@types/detect-newline@ts2.8", "@types/diff@ts2.8", "@types/doctrine@ts2.8", "@types/download@ts2.8", "@types/draft-js@ts2.8", "@types/duplexer2@ts2.8", "@types/duplexer3@ts2.8", "@types/duplexify@ts2.8", "@types/ejs@ts2.8", "@types/end-of-stream@ts2.8", "@types/entities@ts2.8", "@types/escape-html@ts2.8", "@types/escape-string-regexp@ts2.8", "@types/escodegen@ts2.8", "@types/eslint-scope@ts2.8", "@types/eslint-visitor-keys@ts2.8", "@types/esprima@ts2.8", "@types/estraverse@ts2.8", "@types/etag@ts2.8", "@types/events@ts2.8", "@types/execa@ts2.8", "@types/exenv@ts2.8", "@types/exit@ts2.8", "@types/exit-hook@ts2.8", "@types/expect@ts2.8", "@types/express@ts2.8", "@types/express-graphql@ts2.8", "@types/extend@ts2.8", "@types/extract-zip@ts2.8", "@types/fancy-log@ts2.8", "@types/fast-diff@ts2.8", "@types/fast-levenshtein@ts2.8", "@types/figures@ts2.8", "@types/file-type@ts2.8", "@types/filenamify@ts2.8", "@types/filesize@ts2.8", "@types/finalhandler@ts2.8", "@types/find-root@ts2.8", "@types/find-up@ts2.8", "@types/findup-sync@ts2.8", "@types/forever-agent@ts2.8", "@types/form-data@ts2.8", "@types/forwarded@ts2.8", "@types/fresh@ts2.8", "@types/from2@ts2.8", "@types/fs-extra@ts2.8", "@types/get-caller-file@ts2.8", "@types/get-stdin@ts2.8", "@types/get-stream@ts2.8", "@types/get-value@ts2.8", "@types/glob-base@ts2.8", "@types/glob-parent@ts2.8", "@types/glob-stream@ts2.8", "@types/globby@ts2.8", "@types/globule@ts2.8", "@types/got@ts2.8", "@types/graceful-fs@ts2.8", "@types/gulp-rename@ts2.8", "@types/gulp-sourcemaps@ts2.8", "@types/gulp-util@ts2.8", "@types/gzip-size@ts2.8", "@types/handlebars@ts2.8", "@types/has-ansi@ts2.8", "@types/hasha@ts2.8", "@types/he@ts2.8", "@types/hoek@ts2.8", "@types/html-entities@ts2.8", "@types/html-minifier@ts2.8", "@types/htmlparser2@ts2.8", "@types/http-assert@ts2.8", "@types/http-errors@ts2.8", "@types/http-proxy@ts2.8", "@types/http-proxy-middleware@ts2.8", "@types/indent-string@ts2.8", "@types/inflected@ts2.8", "@types/inherits@ts2.8", "@types/ini@ts2.8", "@types/inline-style-prefixer@ts2.8", "@types/inquirer@ts2.8", "@types/internal-ip@ts2.8", "@types/into-stream@ts2.8", "@types/invariant@ts2.8", "@types/ip@ts2.8", "@types/ip-regex@ts2.8", "@types/is-absolute-url@ts2.8", "@types/is-binary-path@ts2.8", "@types/is-finite@ts2.8", "@types/is-glob@ts2.8", "@types/is-my-json-valid@ts2.8", "@types/is-number@ts2.8", "@types/is-object@ts2.8", "@types/is-path-cwd@ts2.8", "@types/is-path-in-cwd@ts2.8", "@types/is-promise@ts2.8", "@types/is-scoped@ts2.8", "@types/is-stream@ts2.8", "@types/is-svg@ts2.8", "@types/is-url@ts2.8", "@types/is-windows@ts2.8", "@types/istanbul-lib-coverage@ts2.8", "@types/istanbul-lib-hook@ts2.8", "@types/istanbul-lib-instrument@ts2.8", "@types/istanbul-lib-report@ts2.8", "@types/istanbul-lib-source-maps@ts2.8", "@types/istanbul-reports@ts2.8", "@types/jest-diff@ts2.8", "@types/jest-docblock@ts2.8", "@types/jest-get-type@ts2.8", "@types/jest-matcher-utils@ts2.8", "@types/jest-validate@ts2.8", "@types/jpeg-js@ts2.8", "@types/js-base64@ts2.8", "@types/js-string-escape@ts2.8", "@types/js-yaml@ts2.8", "@types/jsbn@ts2.8", "@types/jsdom@ts2.8", "@types/jsesc@ts2.8", "@types/json-parse-better-errors@ts2.8", "@types/json-schema@ts2.8", "@types/json-stable-stringify@ts2.8", "@types/json-stringify-safe@ts2.8", "@types/json5@ts2.8", "@types/jsonfile@ts2.8", "@types/jsontoxml@ts2.8", "@types/jss@ts2.8", "@types/keygrip@ts2.8", "@types/keymirror@ts2.8", "@types/keyv@ts2.8", "@types/klaw@ts2.8", "@types/koa-send@ts2.8", "@types/leven@ts2.8", "@types/listr@ts2.8", "@types/load-json-file@ts2.8", "@types/loader-runner@ts2.8", "@types/loader-utils@ts2.8", "@types/locate-path@ts2.8", "@types/lodash-es@ts2.8", "@types/lodash.assign@ts2.8", "@types/lodash.camelcase@ts2.8", "@types/lodash.clonedeep@ts2.8", "@types/lodash.debounce@ts2.8", "@types/lodash.escape@ts2.8", "@types/lodash.flowright@ts2.8", "@types/lodash.get@ts2.8", "@types/lodash.isarguments@ts2.8", "@types/lodash.isarray@ts2.8", "@types/lodash.isequal@ts2.8", "@types/lodash.isobject@ts2.8", "@types/lodash.isstring@ts2.8", "@types/lodash.keys@ts2.8", "@types/lodash.memoize@ts2.8", "@types/lodash.merge@ts2.8", "@types/lodash.mergewith@ts2.8", "@types/lodash.pick@ts2.8", "@types/lodash.sortby@ts2.8", "@types/lodash.tail@ts2.8", "@types/lodash.template@ts2.8", "@types/lodash.throttle@ts2.8", "@types/lodash.unescape@ts2.8", "@types/lodash.uniq@ts2.8", "@types/log-symbols@ts2.8", "@types/log-update@ts2.8", "@types/loglevel@ts2.8", "@types/loud-rejection@ts2.8", "@types/lru-cache@ts2.8", "@types/make-dir@ts2.8", "@types/map-obj@ts2.8", "@types/media-typer@ts2.8", "@types/mem@ts2.8", "@types/mem-fs@ts2.8", "@types/memory-fs@ts2.8", "@types/meow@ts2.8", "@types/merge-descriptors@ts2.8", "@types/merge-stream@ts2.8", "@types/methods@ts2.8", "@types/micromatch@ts2.8", "@types/mime@ts2.8", "@types/mime-db@ts2.8", "@types/mime-types@ts2.8", "@types/minimatch@ts2.8", "@types/minimist@ts2.8", "@types/minipass@ts2.8", "@types/mkdirp@ts2.8", "@types/mongodb@ts2.8", "@types/morgan@ts2.8", "@types/move-concurrently@ts2.8", "@types/ms@ts2.8", "@types/msgpack-lite@ts2.8", "@types/multimatch@ts2.8", "@types/mz@ts2.8", "@types/negotiator@ts2.8", "@types/node-dir@ts2.8", "@types/node-fetch@ts2.8", "@types/node-forge@ts2.8", "@types/node-int64@ts2.8", "@types/node-ipc@ts2.8", "@types/node-notifier@ts2.8", "@types/nomnom@ts2.8", "@types/nopt@ts2.8", "@types/normalize-package-data@ts2.8", "@types/normalize-url@ts2.8", "@types/number-is-nan@ts2.8", "@types/object-assign@ts2.8", "@types/on-finished@ts2.8", "@types/on-headers@ts2.8", "@types/once@ts2.8", "@types/onetime@ts2.8", "@types/opener@ts2.8", "@types/opn@ts2.8", "@types/optimist@ts2.8", "@types/ora@ts2.8", "@types/os-homedir@ts2.8", "@types/os-locale@ts2.8", "@types/os-tmpdir@ts2.8", "@types/p-cancelable@ts2.8", "@types/p-each-series@ts2.8", "@types/p-event@ts2.8", "@types/p-lazy@ts2.8", "@types/p-limit@ts2.8", "@types/p-locate@ts2.8", "@types/p-map@ts2.8", "@types/p-map-series@ts2.8", "@types/p-reduce@ts2.8", "@types/p-timeout@ts2.8", "@types/p-try@ts2.8", "@types/pako@ts2.8", "@types/parse-glob@ts2.8", "@types/parse-json@ts2.8", "@types/parseurl@ts2.8", "@types/path-exists@ts2.8", "@types/path-is-absolute@ts2.8", "@types/path-parse@ts2.8", "@types/pg-pool@ts2.8", "@types/pg-types@ts2.8", "@types/pify@ts2.8", "@types/pixelmatch@ts2.8", "@types/pkg-dir@ts2.8", "@types/pluralize@ts2.8", "@types/pngjs@ts2.8", "@types/prelude-ls@ts2.8", "@types/pretty-bytes@ts2.8", "@types/pretty-format@ts2.8", "@types/progress@ts2.8", "@types/promise-retry@ts2.8", "@types/proxy-addr@ts2.8", "@types/pump@ts2.8", "@types/q@ts2.8", "@types/qs@ts2.8", "@types/range-parser@ts2.8", "@types/rc@ts2.8", "@types/rc-select@ts2.8", "@types/rc-slider@ts2.8", "@types/rc-tooltip@ts2.8", "@types/rc-tree@ts2.8", "@types/react-event-listener@ts2.8", "@types/react-side-effect@ts2.8", "@types/react-slick@ts2.8", "@types/read-chunk@ts2.8", "@types/read-pkg@ts2.8", "@types/read-pkg-up@ts2.8", "@types/recompose@ts2.8", "@types/recursive-readdir@ts2.8", "@types/relateurl@ts2.8", "@types/replace-ext@ts2.8", "@types/request@ts2.8", "@types/request-promise-native@ts2.8", "@types/require-directory@ts2.8", "@types/require-from-string@ts2.8", "@types/require-relative@ts2.8", "@types/resolve@ts2.8", "@types/resolve-from@ts2.8", "@types/retry@ts2.8", "@types/rx@ts2.8", "@types/rx-lite@ts2.8", "@types/rx-lite-aggregates@ts2.8", "@types/safe-regex@ts2.8", "@types/sane@ts2.8", "@types/sass-graph@ts2.8", "@types/sax@ts2.8", "@types/scriptjs@ts2.8", "@types/semver@ts2.8", "@types/send@ts2.8", "@types/serialize-javascript@ts2.8", "@types/serve-index@ts2.8", "@types/serve-static@ts2.8", "@types/set-value@ts2.8", "@types/shallowequal@ts2.8", "@types/shelljs@ts2.8", "@types/sockjs@ts2.8", "@types/sockjs-client@ts2.8", "@types/source-list-map@ts2.8", "@types/source-map-support@ts2.8", "@types/spdx-correct@ts2.8", "@types/spdy@ts2.8", "@types/split@ts2.8", "@types/sprintf@ts2.8", "@types/sprintf-js@ts2.8", "@types/sqlstring@ts2.8", "@types/sshpk@ts2.8", "@types/stack-utils@ts2.8", "@types/stat-mode@ts2.8", "@types/statuses@ts2.8", "@types/strict-uri-encode@ts2.8", "@types/string-template@ts2.8", "@types/strip-ansi@ts2.8", "@types/strip-bom@ts2.8", "@types/strip-json-comments@ts2.8", "@types/supports-color@ts2.8", "@types/svg2png@ts2.8", "@types/svgo@ts2.8", "@types/table@ts2.8", "@types/tapable@ts2.8", "@types/tar@ts2.8", "@types/temp@ts2.8", "@types/tempfile@ts2.8", "@types/through@ts2.8", "@types/through2@ts2.8", "@types/tinycolor2@ts2.8", "@types/tmp@ts2.8", "@types/to-absolute-glob@ts2.8", "@types/tough-cookie@ts2.8", "@types/trim@ts2.8", "@types/tryer@ts2.8", "@types/type-check@ts2.8", "@types/type-is@ts2.8", "@types/ua-parser-js@ts2.8", "@types/uglify-js@ts2.8", "@types/uglifyjs-webpack-plugin@ts2.8", "@types/underscore@ts2.8", "@types/uniq@ts2.8", "@types/uniqid@ts2.8", "@types/untildify@ts2.8", "@types/urijs@ts2.8", "@types/url-join@ts2.8", "@types/url-parse@ts2.8", "@types/url-regex@ts2.8", "@types/user-home@ts2.8", "@types/util-deprecate@ts2.8", "@types/util.promisify@ts2.8", "@types/utils-merge@ts2.8", "@types/uuid@ts2.8", "@types/vali-date@ts2.8", "@types/vary@ts2.8", "@types/verror@ts2.8", "@types/vinyl@ts2.8", "@types/vinyl-fs@ts2.8", "@types/warning@ts2.8", "@types/watch@ts2.8", "@types/watchpack@ts2.8", "@types/webpack-dev-middleware@ts2.8", "@types/webpack-sources@ts2.8", "@types/which@ts2.8", "@types/window-size@ts2.8", "@types/wrap-ansi@ts2.8", "@types/write-file-atomic@ts2.8", "@types/ws@ts2.8", "@types/xml2js@ts2.8", "@types/xmlbuilder@ts2.8", "@types/xtend@ts2.8", "@types/yallist@ts2.8", "@types/yargs@ts2.8", "@types/yauzl@ts2.8", "@types/yeoman-generator@ts2.8", "@types/zen-observable@ts2.8", "@types/react-content-loader@ts2.8"]; const expectedCommands = [ @@ -1705,7 +1705,7 @@ namespace ts.projectSystem { }); }); - describe("recomputing resolutions of unresolved imports", () => { + describe("typingsInstaller:: recomputing resolutions of unresolved imports", () => { const globalTypingsCacheLocation = "/tmp"; const appPath = "/a/b/app.js" as Path; const foooPath = "/a/b/node_modules/fooo/index.d.ts"; From 47200acfcd38d4febefb16b3e982dee77f6a1b2d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 09:49:57 -0800 Subject: [PATCH 075/120] Split watch environment into its own unittest --- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/tscWatchMode.ts | 179 +----------- .../unittests/tsserverProjectSystem.ts | 84 ------ src/testRunner/unittests/watchEnvironment.ts | 266 ++++++++++++++++++ 4 files changed, 268 insertions(+), 262 deletions(-) create mode 100644 src/testRunner/unittests/watchEnvironment.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 10b8637aede..bd3eded500d 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -87,6 +87,7 @@ "unittests/tsconfigParsing.ts", "unittests/tscWatchMode.ts", "unittests/versionCache.ts", + "unittests/watchEnvironment.ts", "unittests/evaluation/asyncArrow.ts", "unittests/evaluation/asyncGenerator.ts", "unittests/evaluation/forAwaitOf.ts", diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index 65f64b9da78..03bead8178b 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -10,7 +10,6 @@ namespace ts.tscWatch { export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; export import checkOutputContains = TestFSWithWatch.checkOutputContains; export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; - export import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray) { checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); @@ -34,7 +33,7 @@ namespace ts.tscWatch { return () => watch.getCurrentProgram().getProgram(); } - function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) { + export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) { const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host); compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; const watch = createWatchProgram(compilerHost); @@ -2885,182 +2884,6 @@ declare module "fs" { }); }); - describe("tsc-watch with different polling/non polling options", () => { - it("watchFile using dynamic priority polling", () => { - const projectFolder = "/a/username/project"; - const file1: File = { - path: `${projectFolder}/typescript.ts`, - content: "var z = 10;" - }; - const files = [file1, libFile]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling"); - const host = createWatchedSystem(files, { environmentVariables }); - const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); - - const initialProgram = watch(); - verifyProgram(); - - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - for (let index = 0; index < mediumPollingIntervalThreshold; index++) { - // Transition libFile and file1 to low priority queue - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), initialProgram); - } - - // Make a change to file - file1.content = "var zz30 = 100;"; - host.reloadFS(files); - - // This should detect change in the file - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), initialProgram); - - // Callbacks: medium priority + high priority queue and scheduled program update - host.checkTimeoutQueueLengthAndRun(3); - // During this timeout the file would be detected as unchanged - let fileUnchangeDetected = 1; - const newProgram = watch(); - assert.notStrictEqual(newProgram, initialProgram); - - verifyProgram(); - const outputFile1 = changeExtension(file1.path, ".js"); - assert.isTrue(host.fileExists(outputFile1)); - assert.equal(host.readFile(outputFile1), file1.content + host.newLine); - - const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; - for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { - // For high + Medium/low polling interval - host.checkTimeoutQueueLengthAndRun(2); - assert.deepEqual(watch(), newProgram); - } - - // Everything goes in high polling interval queue - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), newProgram); - - function verifyProgram() { - checkProgramActualFiles(watch(), files.map(f => f.path)); - checkWatchedFiles(host, []); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, [], /*recursive*/ true); - } - }); - - describe("tsc-watch when watchDirectories implementation", () => { - function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const file: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" - }; - const programFiles = [file, libFile]; - const files = [file, configFile, libFile]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - const host = createWatchedSystem(files, { environmentVariables }); - const watch = createWatchOfConfigFile(configFile.path, host); - const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; - // Watching files config file, file, lib file - const expectedWatchedFiles = files.map(f => f.path); - const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; - if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { - expectedWatchedFiles.push(...projectFolders); - } - - verifyProgram(checkOutputErrorsInitial); - - // Rename the file: - file.path = file.path.replace("file1.ts", "file2.ts"); - expectedWatchedFiles[0] = file.path; - host.reloadFS(files); - if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { - // With dynamic polling the fs change would be detected only by running timeouts - host.runQueuedTimeoutCallbacks(); - } - // Delayed update program - host.runQueuedTimeoutCallbacks(); - verifyProgram(checkOutputErrorsIncremental); - - function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrors(host, emptyArray); - - const outputFile = changeExtension(file.path, ".js"); - assert(host.fileExists(outputFile)); - assert.equal(host.readFile(outputFile), file.content); - - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - // Watching config file, file, lib file and directories - checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false); - } - } - - it("uses watchFile when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile); - }); - - it("uses non recursive watchDirectory when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); - }); - - it("uses non recursive dynamic polling when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); - }); - - it("when there are symlinks to folders in recursive folders", () => { - const cwd = "/home/user/projects/myproject"; - const file1: File = { - path: `${cwd}/src/file.ts`, - content: `import * as a from "a"` - }; - const tsconfig: File = { - path: `${cwd}/tsconfig.json`, - content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` - }; - const realA: File = { - path: `${cwd}/node_modules/reala/index.d.ts`, - content: `export {}` - }; - const realB: File = { - path: `${cwd}/node_modules/realb/index.d.ts`, - content: `export {}` - }; - const symLinkA: SymLink = { - path: `${cwd}/node_modules/a`, - symLink: `${cwd}/node_modules/reala` - }; - const symLinkB: SymLink = { - path: `${cwd}/node_modules/b`, - symLink: `${cwd}/node_modules/realb` - }; - const symLinkBInA: SymLink = { - path: `${cwd}/node_modules/reala/node_modules/b`, - symLink: `${cwd}/node_modules/b` - }; - const symLinkAInB: SymLink = { - path: `${cwd}/node_modules/realb/node_modules/a`, - symLink: `${cwd}/node_modules/a` - }; - const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); - createWatchOfConfigFile("tsconfig.json", host); - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, - `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); - }); - }); - }); - describe("tsc-watch with modules linked to sibling folder", () => { const projectRoot = "/user/username/projects/project"; const mainPackageRoot = `${projectRoot}/main`; diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index e6fc1fa3726..cefa8a5e34c 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -15,7 +15,6 @@ namespace ts.projectSystem { export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; import safeList = TestFSWithWatch.safeList; - import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; function mapOutputToJson(s: string) { @@ -9156,89 +9155,6 @@ export const x = 10;` }); }); - describe("tsserverProjectSystem watchDirectories implementation", () => { - function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const index: File = { - path: `${projectSrcFolder}/index.ts`, - content: `import {} from "./"` - }; - const file1: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" - }; - - const files = [index, file1, configFile, libFile]; - const fileNames = files.map(file => file.path); - // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder - const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); - const expectedWatchedDirectories = createMap(); - const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? - expectedWatchedDirectories : - tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? - expectedWatchedFiles : - createMap(); - // For failed resolution lookup and tsconfig files => cached so only watched only once - mapOfDirectories.set(projectFolder, 1); - // Through above recursive watches - mapOfDirectories.set(projectSrcFolder, 1); - // node_modules/@types folder - mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); - const expectedCompletions = ["file1"]; - const completionPosition = index.content.lastIndexOf('"'); - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - const host = createServerHost(files, { environmentVariables }); - const projectService = createProjectService(host); - projectService.openClientFile(index.path); - - const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); - verifyProjectAndCompletions(); - - // Add file2 - const file2: File = { - path: `${projectSrcFolder}/file2.ts`, - content: "" - }; - files.push(file2); - fileNames.push(file2.path); - expectedWatchedFiles.set(file2.path, 1); - expectedCompletions.push("file2"); - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - assert.equal(projectService.configuredProjects.get(configFile.path), project); - verifyProjectAndCompletions(); - - function verifyProjectAndCompletions() { - const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; - checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); - - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - checkWatchedFilesDetailed(host, expectedWatchedFiles); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); - checkProjectActualFiles(project, fileNames); - } - } - - it("uses watchFile when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); - }); - - it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); - }); - - it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); - }); - }); - describe("tsserverProjectSystem getEditsForFileRename", () => { it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { const userTs: File = { diff --git a/src/testRunner/unittests/watchEnvironment.ts b/src/testRunner/unittests/watchEnvironment.ts new file mode 100644 index 00000000000..5b620b54e85 --- /dev/null +++ b/src/testRunner/unittests/watchEnvironment.ts @@ -0,0 +1,266 @@ +namespace ts { + import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; + + export namespace tscWatch { + describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => { + it("watchFile using dynamic priority polling", () => { + const projectFolder = "/a/username/project"; + const file1: File = { + path: `${projectFolder}/typescript.ts`, + content: "var z = 10;" + }; + const files = [file1, libFile]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling"); + const host = createWatchedSystem(files, { environmentVariables }); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); + + const initialProgram = watch(); + verifyProgram(); + + const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; + for (let index = 0; index < mediumPollingIntervalThreshold; index++) { + // Transition libFile and file1 to low priority queue + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), initialProgram); + } + + // Make a change to file + file1.content = "var zz30 = 100;"; + host.reloadFS(files); + + // This should detect change in the file + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), initialProgram); + + // Callbacks: medium priority + high priority queue and scheduled program update + host.checkTimeoutQueueLengthAndRun(3); + // During this timeout the file would be detected as unchanged + let fileUnchangeDetected = 1; + const newProgram = watch(); + assert.notStrictEqual(newProgram, initialProgram); + + verifyProgram(); + const outputFile1 = changeExtension(file1.path, ".js"); + assert.isTrue(host.fileExists(outputFile1)); + assert.equal(host.readFile(outputFile1), file1.content + host.newLine); + + const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; + for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { + // For high + Medium/low polling interval + host.checkTimeoutQueueLengthAndRun(2); + assert.deepEqual(watch(), newProgram); + } + + // Everything goes in high polling interval queue + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), newProgram); + + function verifyProgram() { + checkProgramActualFiles(watch(), files.map(f => f.path)); + checkWatchedFiles(host, []); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, [], /*recursive*/ true); + } + }); + + describe("tsc-watch when watchDirectories implementation", () => { + function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const file: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + const programFiles = [file, libFile]; + const files = [file, configFile, libFile]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables }); + const watch = createWatchOfConfigFile(configFile.path, host); + const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; + // Watching files config file, file, lib file + const expectedWatchedFiles = files.map(f => f.path); + const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; + if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { + expectedWatchedFiles.push(...projectFolders); + } + + verifyProgram(checkOutputErrorsInitial); + + // Rename the file: + file.path = file.path.replace("file1.ts", "file2.ts"); + expectedWatchedFiles[0] = file.path; + host.reloadFS(files); + if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { + // With dynamic polling the fs change would be detected only by running timeouts + host.runQueuedTimeoutCallbacks(); + } + // Delayed update program + host.runQueuedTimeoutCallbacks(); + verifyProgram(checkOutputErrorsIncremental); + + function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrors(host, emptyArray); + + const outputFile = changeExtension(file.path, ".js"); + assert(host.fileExists(outputFile)); + assert.equal(host.readFile(outputFile), file.content); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + // Watching config file, file, lib file and directories + checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false); + } + } + + it("uses watchFile when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile); + }); + + it("uses non recursive watchDirectory when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses non recursive dynamic polling when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); + }); + + it("when there are symlinks to folders in recursive folders", () => { + const cwd = "/home/user/projects/myproject"; + const file1: File = { + path: `${cwd}/src/file.ts`, + content: `import * as a from "a"` + }; + const tsconfig: File = { + path: `${cwd}/tsconfig.json`, + content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` + }; + const realA: File = { + path: `${cwd}/node_modules/reala/index.d.ts`, + content: `export {}` + }; + const realB: File = { + path: `${cwd}/node_modules/realb/index.d.ts`, + content: `export {}` + }; + const symLinkA: SymLink = { + path: `${cwd}/node_modules/a`, + symLink: `${cwd}/node_modules/reala` + }; + const symLinkB: SymLink = { + path: `${cwd}/node_modules/b`, + symLink: `${cwd}/node_modules/realb` + }; + const symLinkBInA: SymLink = { + path: `${cwd}/node_modules/reala/node_modules/b`, + symLink: `${cwd}/node_modules/b` + }; + const symLinkAInB: SymLink = { + path: `${cwd}/node_modules/realb/node_modules/a`, + symLink: `${cwd}/node_modules/a` + }; + const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, + `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); + }); + }); + }); + } + + export namespace projectSystem { + describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { + function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const index: File = { + path: `${projectSrcFolder}/index.ts`, + content: `import {} from "./"` + }; + const file1: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + + const files = [index, file1, configFile, libFile]; + const fileNames = files.map(file => file.path); + // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder + const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); + const expectedWatchedDirectories = createMap(); + const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? + expectedWatchedDirectories : + tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? + expectedWatchedFiles : + createMap(); + // For failed resolution lookup and tsconfig files => cached so only watched only once + mapOfDirectories.set(projectFolder, 1); + // Through above recursive watches + mapOfDirectories.set(projectSrcFolder, 1); + // node_modules/@types folder + mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); + const expectedCompletions = ["file1"]; + const completionPosition = index.content.lastIndexOf('"'); + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + const host = createServerHost(files, { environmentVariables }); + const projectService = createProjectService(host); + projectService.openClientFile(index.path); + + const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); + verifyProjectAndCompletions(); + + // Add file2 + const file2: File = { + path: `${projectSrcFolder}/file2.ts`, + content: "" + }; + files.push(file2); + fileNames.push(file2.path); + expectedWatchedFiles.set(file2.path, 1); + expectedCompletions.push("file2"); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.equal(projectService.configuredProjects.get(configFile.path), project); + verifyProjectAndCompletions(); + + function verifyProjectAndCompletions() { + const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; + checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + checkWatchedFilesDetailed(host, expectedWatchedFiles); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); + checkProjectActualFiles(project, fileNames); + } + } + + it("uses watchFile when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); + }); + + it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); + }); + }); + } +} From 7b9b0f8da71ded5a005f581f16b2d768c49ddd4b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 10:30:10 -0800 Subject: [PATCH 076/120] Split resolutionCache and watchApi tests into its own unittest --- src/testRunner/tsconfig.json | 8 +- src/testRunner/unittests/resolutionCache.ts | 981 +++++++++++++++ src/testRunner/unittests/tsbuildWatchMode.ts | 1 - src/testRunner/unittests/tscWatchHelpers.ts | 237 ++++ src/testRunner/unittests/tscWatchMode.ts | 645 ---------- src/testRunner/unittests/tsserverHelpers.ts | 679 +++++++++++ .../unittests/tsserverProjectSystem.ts | 1065 ----------------- src/testRunner/unittests/typingsInstaller.ts | 1 - src/testRunner/unittests/watchApi.ts | 40 + src/testRunner/unittests/watchEnvironment.ts | 50 + 10 files changed, 1993 insertions(+), 1714 deletions(-) create mode 100644 src/testRunner/unittests/resolutionCache.ts create mode 100644 src/testRunner/unittests/tscWatchHelpers.ts create mode 100644 src/testRunner/unittests/tsserverHelpers.ts create mode 100644 src/testRunner/unittests/watchApi.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index bd3eded500d..674188eee63 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -37,8 +37,8 @@ "runner.ts", "unittests/extractTestHelpers.ts", - "unittests/tsserverProjectSystem.ts", - "unittests/typingsInstaller.ts", + "unittests/tscWatchHelpers.ts", + "unittests/tsserverHelpers.ts", "unittests/asserts.ts", "unittests/base64.ts", @@ -72,6 +72,7 @@ "unittests/projectErrors.ts", "unittests/projectReferences.ts", "unittests/publicApi.ts", + "unittests/resolutionCache.ts", "unittests/reuseProgramStructure.ts", "unittests/session.ts", "unittests/semver.ts", @@ -86,8 +87,11 @@ "unittests/tsbuildWatchMode.ts", "unittests/tsconfigParsing.ts", "unittests/tscWatchMode.ts", + "unittests/tsserverProjectSystem.ts", + "unittests/typingsInstaller.ts", "unittests/versionCache.ts", "unittests/watchEnvironment.ts", + "unittests/watchApi.ts", "unittests/evaluation/asyncArrow.ts", "unittests/evaluation/asyncGenerator.ts", "unittests/evaluation/forAwaitOf.ts", diff --git a/src/testRunner/unittests/resolutionCache.ts b/src/testRunner/unittests/resolutionCache.ts new file mode 100644 index 00000000000..fbc792481fc --- /dev/null +++ b/src/testRunner/unittests/resolutionCache.ts @@ -0,0 +1,981 @@ +namespace ts.tscWatch { + describe("resolutionCache:: tsc-watch module resolution caching", () => { + it("works", () => { + const root = { + path: "/a/d/f0.ts", + content: `import {x} from "f1"` + }; + const imported = { + path: "/a/f1.ts", + content: `foo()` + }; + + const files = [root, imported, libFile]; + const host = createWatchedSystem(files); + 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"); + + // ensure that imported file was found + checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); + + const originalFileExists = host.fileExists; + { + const newContent = `import {x} from "f1" + var x: string = 1;`; + root.content = newContent; + host.reloadFS(files); + + // patch fileExists to make sure that disk is not touched + host.fileExists = notImplemented; + + // trigger synchronization to make sure that import will be fetched from the cache + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + f1IsNotModule, + getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"), + cannotFindFoo + ]); + } + { + let fileExistsIsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f2.") !== -1); + return originalFileExists.call(host, fileName); + }; + + root.content = `import {x} from "f2"`; + host.reloadFS(files); + + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") + ]); + + assert.isTrue(fileExistsIsCalled); + } + { + let fileExistsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsCalled = true; + assert.isTrue(fileName.indexOf("/f1.") !== -1); + return originalFileExists.call(host, fileName); + }; + + const newContent = `import {x} from "f1"`; + root.content = newContent; + + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); + assert.isTrue(fileExistsCalled); + } + }); + + it("loads missing files from disk", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; + + const files = [root, libFile]; + const host = createWatchedSystem(files); + const originalFileExists = host.fileExists; + + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") + ]); + + fileExistsCalledForBar = false; + root.content = `import {y} from "bar"`; + host.reloadFS(files.concat(imported)); + + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;export const x = 10;` + }; + + const files = [root, libFile]; + const filesWithImported = files.concat(imported); + const host = createWatchedSystem(filesWithImported); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, emptyArray); + + fileExistsCalledForBar = false; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") + ]); + + fileExistsCalledForBar = false; + host.reloadFS(filesWithImported); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("works when module resolution changes to ambient module", () => { + const root = { + path: "/a/b/foo.ts", + content: `import * as fs from "fs";` + }; + + const packageJson = { + path: "/a/b/node_modules/@types/node/package.json", + content: ` +{ + "main": "" +} +` + }; + + const nodeType = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: ` +declare module "fs" { + export interface Stats { + isFile(): boolean; + } +}` + }; + + const files = [root, libFile]; + const filesWithNodeType = files.concat(packageJson, nodeType); + const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); + + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") + ]); + + host.reloadFS(filesWithNodeType); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + }); + + it("works when included file with ambient module changes", () => { + const root = { + path: "/a/b/foo.ts", + content: ` +import * as fs from "fs"; +import * as u from "url"; +` + }; + + const file = { + path: "/a/b/bar.d.ts", + content: ` +declare module "url" { + export interface Url { + href?: string; + } +} +` + }; + + const fileContentWithFS = ` +declare module "fs" { + export interface Stats { + isFile(): boolean; + } +} +`; + + const files = [root, file, libFile]; + const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); + + const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); + + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") + ]); + + file.content += fileContentWithFS; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + }); + + it("works when reusing program with files from external library", () => { + interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; } + const configDir = "/a/b/projects/myProject/src/"; + const file1: File = { + path: configDir + "file1.ts", + content: 'import module1 = require("module1");\nmodule1("hello");' + }; + const file2: File = { + path: configDir + "file2.ts", + content: 'import module11 = require("module1");\nmodule11("hello");' + }; + const module1: File = { + path: "/a/b/projects/myProject/node_modules/module1/index.js", + content: "module.exports = options => { return options.toString(); }" + }; + const configFile: File = { + path: configDir + "tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + allowJs: true, + rootDir: ".", + outDir: "../dist", + moduleResolution: "node", + maxNodeModuleJsDepth: 1 + } + }) + }; + 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 = createWatchOfConfigFile(configFile.path, host); + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrorsInitial(host, emptyArray); + const expectedFiles: ExpectedFile[] = [ + createExpectedEmittedFile(file1), + createExpectedEmittedFile(file2), + createExpectedToNotEmitFile("index.js"), + createExpectedToNotEmitFile("src/index.js"), + createExpectedToNotEmitFile("src/file1.js"), + createExpectedToNotEmitFile("src/file2.js"), + createExpectedToNotEmitFile("lib.js"), + createExpectedToNotEmitFile("lib.d.ts") + ]; + verifyExpectedFiles(expectedFiles); + + file1.content += "\n;"; + expectedFiles[0].content += ";\n"; // Only emit file1 with this change + expectedFiles[1].isExpectedToEmit = false; + host.reloadFS(programFiles.concat(configFile)); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrorsIncremental(host, emptyArray); + verifyExpectedFiles(expectedFiles); + + + function verifyExpectedFiles(expectedFiles: ExpectedFile[]) { + forEach(expectedFiles, f => { + assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit")); + if (f.isExpectedToEmit) { + assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path); + } + }); + } + + function createExpectedToNotEmitFile(fileName: string): ExpectedFile { + return { + path: outDirFolder + fileName, + isExpectedToEmit: false + }; + } + + function createExpectedEmittedFile(file: File): ExpectedFile { + return { + path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js, + isExpectedToEmit: true, + content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n" + }; + } + }); + + it("works when renaming node_modules folder that already contains @types folder", () => { + const currentDirectory = "/user/username/projects/myproject"; + const file: File = { + path: `${currentDirectory}/a.ts`, + content: `import * as q from "qqq";` + }; + const module: File = { + path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, + content: "export {}" + }; + const files = [file, module, libFile]; + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfFilesAndCompilerOptions([file.path], host); + + checkProgramActualFiles(watch(), [file.path, libFile.path]); + checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true); + + host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); + checkOutputErrorsIncremental(host, emptyArray); + }); + + describe("ignores files/folder changes in node_modules that start with '.'", () => { + const projectPath = "/user/username/projects/project"; + const npmCacheFile: File = { + path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + content: JSON.stringify({ something: 10 }) + }; + const file1: File = { + path: `${projectPath}/test.ts`, + content: `import { x } from "somemodule";` + }; + const file2: File = { + path: `${projectPath}/node_modules/somemodule/index.d.ts`, + content: `export const x = 10;` + }; + const files = [libFile, file1, file2]; + const expectedFiles = files.map(f => f.path); + it("when watching node_modules in inferred project for failed lookup", () => { + const host = createWatchedSystem(files); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1); + checkProgramActualFiles(watch(), expectedFiles); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + it("when watching node_modules as part of wild card directories in config project", () => { + const config: File = { + path: `${projectPath}/tsconfig.json`, + content: "{}" + }; + const host = createWatchedSystem(files.concat(config)); + const watch = createWatchOfConfigFile(config.path, host); + checkProgramActualFiles(watch(), expectedFiles); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + }); + }); + + describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => { + const projectRoot = "/user/username/projects/project"; + const mainPackageRoot = `${projectRoot}/main`; + const linkedPackageRoot = `${projectRoot}/linked-package`; + const mainFile: File = { + path: `${mainPackageRoot}/index.ts`, + content: "import { Foo } from '@scoped/linked-package'" + }; + const config: File = { + path: `${mainPackageRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, + files: ["index.ts"] + }) + }; + const linkedPackageInMain: SymLink = { + path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, + symLink: `${linkedPackageRoot}` + }; + const linkedPackageJson: File = { + path: `${linkedPackageRoot}/package.json`, + content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) + }; + const linkedPackageIndex: File = { + path: `${linkedPackageRoot}/dist/index.d.ts`, + content: "export * from './other';" + }; + const linkedPackageOther: File = { + path: `${linkedPackageRoot}/dist/other.d.ts`, + content: 'export declare const Foo = "BAR";' + }; + + it("verify watched directories", () => { + const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; + const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true); + }); + }); +} + +namespace ts.projectSystem { + function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) { + const resolutionTrace: string[] = []; + host.trace = resolutionTrace.push.bind(resolutionTrace); + return resolutionTrace; + } + + describe("resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => { + it("can load typings that are proper modules", () => { + const file1 = { + path: "/a/b/app.js", + content: `var x = require("lib")` + }; + const lib = { + path: "/a/cache/node_modules/@types/lib/index.d.ts", + content: "export let x = 1" + }; + const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]); + const resolutionTrace = createHostModuleResolutionTrace(host); + const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", /*throttleLimit*/5, host) }); + + projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true }); + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + + assert.deepEqual(resolutionTrace, [ + "======== Resolving module 'lib' from '/a/b/app.js'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module 'lib' from 'node_modules' folder, target file type 'TypeScript'.", + "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.", + "Directory '/a/node_modules' does not exist, skipping all lookups in it.", + "Directory '/node_modules' does not exist, skipping all lookups in it.", + "Loading module 'lib' from 'node_modules' folder, target file type 'JavaScript'.", + "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.", + "Directory '/a/node_modules' does not exist, skipping all lookups in it.", + "Directory '/node_modules' does not exist, skipping all lookups in it.", + "======== Module name 'lib' was not resolved. ========", + `Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`, + "File '/a/cache/node_modules/lib.d.ts' does not exist.", + "File '/a/cache/node_modules/@types/lib/package.json' does not exist.", + "File '/a/cache/node_modules/@types/lib.d.ts' does not exist.", + "File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.", + ]); + checkProjectActualFiles(proj, [file1.path, lib.path]); + }); + }); + + describe("resolutionCache:: tsserverProjectSystem module resolution caching", () => { + const projectLocation = "/user/username/projects/myproject"; + const configFile: File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { traceResolution: true } }) + }; + + function getModules(module1Path: string, module2Path: string) { + const module1: File = { + path: module1Path, + content: `export function module1() {}` + }; + const module2: File = { + path: module2Path, + content: `export function module2() {}` + }; + return { module1, module2 }; + } + + function verifyTrace(resolutionTrace: string[], expected: string[]) { + assert.deepEqual(resolutionTrace, expected); + resolutionTrace.length = 0; + } + + function getExpectedFileDoesNotExistResolutionTrace(host: TestServerHost, expectedTrace: string[], foundModule: boolean, module: File, directory: string, file: string, ignoreIfParentMissing?: boolean) { + if (!foundModule) { + const path = combinePaths(directory, file); + if (!ignoreIfParentMissing || host.directoryExists(getDirectoryPath(path))) { + if (module.path === path) { + foundModule = true; + } + else { + expectedTrace.push(`File '${path}' does not exist.`); + } + } + } + return foundModule; + } + + function getExpectedMissedLocationResolutionTrace(host: TestServerHost, expectedTrace: string[], dirPath: string, module: File, moduleName: string, useNodeModules: boolean, cacheLocation?: string) { + let foundModule = false; + forEachAncestorDirectory(dirPath, dirPath => { + if (dirPath === cacheLocation) { + return foundModule; + } + + const directory = useNodeModules ? combinePaths(dirPath, nodeModules) : dirPath; + if (useNodeModules && !foundModule && !host.directoryExists(directory)) { + expectedTrace.push(`Directory '${directory}' does not exist, skipping all lookups in it.`); + return undefined; + } + foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/package.json`, /*ignoreIfParentMissing*/ true); + foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ts`); + foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.tsx`); + foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.d.ts`); + foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/index.ts`, /*ignoreIfParentMissing*/ true); + if (useNodeModules && !foundModule) { + expectedTrace.push(`Directory '${directory}/@types' does not exist, skipping all lookups in it.`); + } + return foundModule ? true : undefined; + }); + } + + function getExpectedResolutionTraceHeader(expectedTrace: string[], file: File, moduleName: string) { + expectedTrace.push( + `======== Resolving module '${moduleName}' from '${file.path}'. ========`, + `Module resolution kind is not specified, using 'NodeJs'.` + ); + } + + function getExpectedResolutionTraceFooter(expectedTrace: string[], module: File, moduleName: string, addRealPathTrace: boolean, ignoreModuleFileFound?: boolean) { + if (!ignoreModuleFileFound) { + expectedTrace.push(`File '${module.path}' exist - use it as a name resolution result.`); + } + if (addRealPathTrace) { + expectedTrace.push(`Resolving real path for '${module.path}', result '${module.path}'.`); + } + expectedTrace.push(`======== Module name '${moduleName}' was successfully resolved to '${module.path}'. ========`); + } + + function getExpectedRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) { + getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); + expectedTrace.push(`Loading module as file / folder, candidate module location '${removeFileExtension(module.path)}', target file type 'TypeScript'.`); + getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(normalizePath(combinePaths(getDirectoryPath(file.path), moduleName))), module, moduleName.substring(moduleName.lastIndexOf("/") + 1), /*useNodeModules*/ false); + getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false); + return expectedTrace; + } + + function getExpectedNonRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) { + getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); + expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`); + getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true); + getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ true); + return expectedTrace; + } + + function getExpectedNonRelativeModuleResolutionFromCacheTrace(host: TestServerHost, file: File, module: File, moduleName: string, cacheLocation: string, expectedTrace: string[] = []) { + getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); + expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`); + getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true, cacheLocation); + expectedTrace.push(`Resolution for module '${moduleName}' was found in cache from location '${cacheLocation}'.`); + getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false, /*ignoreModuleFileFound*/ true); + return expectedTrace; + } + + function getExpectedReusingResolutionFromOldProgram(file: File, moduleName: string) { + return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`; + } + + function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: ReadonlyArray) { + const expectedRecursiveDirectories = arrayToSet([projectLocation, `${projectLocation}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); + checkWatchedFiles(host, mapDefined(files, f => { + if (f === openFile) { + return undefined; + } + const indexOfNodeModules = f.path.indexOf("/node_modules/"); + if (indexOfNodeModules === -1) { + return f.path; + } + expectedRecursiveDirectories.set(f.path.substr(0, indexOfNodeModules + "/node_modules".length), true); + return undefined; + })); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.keys()), /*recursive*/ true); + } + + describe("from files in same folder", () => { + function getFiles(fileContent: string) { + const file1: File = { + path: `${projectLocation}/src/file1.ts`, + content: fileContent + }; + const file2: File = { + path: `${projectLocation}/src/file2.ts`, + content: fileContent + }; + return { file1, file2 }; + } + + it("relative module name", () => { + const module1Name = "./module1"; + const module2Name = "../module2"; + const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; + const { file1, file2 } = getFiles(fileContent); + const { module1, module2 } = getModules(`${projectLocation}/src/module1.ts`, `${projectLocation}/module2.ts`); + const files = [module1, module2, file1, file2, configFile, libFile]; + const host = createServerHost(files); + const resolutionTrace = createHostModuleResolutionTrace(host); + const service = createProjectService(host); + service.openClientFile(file1.path); + const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name); + getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); + verifyTrace(resolutionTrace, expectedTrace); + verifyWatchesWithConfigFile(host, files, file1); + + file1.content += fileContent; + file2.content += fileContent; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + verifyTrace(resolutionTrace, [ + getExpectedReusingResolutionFromOldProgram(file1, module1Name), + getExpectedReusingResolutionFromOldProgram(file1, module2Name) + ]); + verifyWatchesWithConfigFile(host, files, file1); + }); + + it("non relative module name", () => { + const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`]; + const module1Name = "module1"; + const module2Name = "module2"; + const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; + const { file1, file2 } = getFiles(fileContent); + const { module1, module2 } = getModules(`${projectLocation}/src/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); + const files = [module1, module2, file1, file2, configFile, libFile]; + const host = createServerHost(files); + const resolutionTrace = createHostModuleResolutionTrace(host); + const service = createProjectService(host); + service.openClientFile(file1.path); + const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); + getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); + verifyTrace(resolutionTrace, expectedTrace); + verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + + file1.content += fileContent; + file2.content += fileContent; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + verifyTrace(resolutionTrace, [ + getExpectedReusingResolutionFromOldProgram(file1, module1Name), + getExpectedReusingResolutionFromOldProgram(file1, module2Name) + ]); + verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + }); + }); + + describe("from files in different folders", () => { + function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) { + const file1: File = { + path: `${projectLocation}/product/src/file1.ts`, + content: fileContent1 + }; + const file2: File = { + path: `${projectLocation}/product/src/feature/file2.ts`, + content: fileContent2 + }; + const file3: File = { + path: `${projectLocation}/product/test/src/file3.ts`, + content: fileContent3 + }; + const file4: File = { + path: `${projectLocation}/product/test/file4.ts`, + content: fileContent4 + }; + return { file1, file2, file3, file4 }; + } + + it("relative module name", () => { + const module1Name = "./module1"; + const module2Name = "../module2"; + const module3Name = "../module1"; + const module4Name = "../../module2"; + const module5Name = "../../src/module1"; + const module6Name = "../src/module1"; + const fileContent1 = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; + const fileContent2 = `import { module1 } from "${module3Name}";import { module2 } from "${module4Name}";`; + const fileContent3 = `import { module1 } from "${module5Name}";import { module2 } from "${module4Name}";`; + const fileContent4 = `import { module1 } from "${module6Name}";import { module2 } from "${module2Name}";`; + const { file1, file2, file3, file4 } = getFiles(fileContent1, fileContent2, fileContent3, fileContent4); + const { module1, module2 } = getModules(`${projectLocation}/product/src/module1.ts`, `${projectLocation}/product/module2.ts`); + const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; + const host = createServerHost(files); + const resolutionTrace = createHostModuleResolutionTrace(host); + const service = createProjectService(host); + service.openClientFile(file1.path); + const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name); + getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file2, module1, module3Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file2, module2, module4Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file4, module1, module6Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file3, module1, module5Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file3, module2, module4Name, expectedTrace); + verifyTrace(resolutionTrace, expectedTrace); + verifyWatchesWithConfigFile(host, files, file1); + + file1.content += fileContent1; + file2.content += fileContent2; + file3.content += fileContent3; + file4.content += fileContent4; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + verifyTrace(resolutionTrace, [ + getExpectedReusingResolutionFromOldProgram(file1, module1Name), + getExpectedReusingResolutionFromOldProgram(file1, module2Name) + ]); + verifyWatchesWithConfigFile(host, files, file1); + }); + + it("non relative module name", () => { + const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/product`]; + const module1Name = "module1"; + const module2Name = "module2"; + const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; + const { file1, file2, file3, file4 } = getFiles(fileContent); + const { module1, module2 } = getModules(`${projectLocation}/product/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); + const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; + const host = createServerHost(files); + const resolutionTrace = createHostModuleResolutionTrace(host); + const service = createProjectService(host); + service.openClientFile(file1.path); + const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); + getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectLocation}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectLocation}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); + verifyTrace(resolutionTrace, expectedTrace); + verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + + file1.content += fileContent; + file2.content += fileContent; + file3.content += fileContent; + file4.content += fileContent; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + verifyTrace(resolutionTrace, [ + getExpectedReusingResolutionFromOldProgram(file1, module1Name), + getExpectedReusingResolutionFromOldProgram(file1, module2Name) + ]); + verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + }); + + it("non relative module name from inferred project", () => { + const module1Name = "module1"; + const module2Name = "module2"; + const file2Name = "./feature/file2"; + const file3Name = "../test/src/file3"; + const file4Name = "../test/file4"; + const importModuleContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; + const { file1, file2, file3, file4 } = getFiles(`import "${file2Name}"; import "${file4Name}"; import "${file3Name}"; ${importModuleContent}`, importModuleContent, importModuleContent, importModuleContent); + const { module1, module2 } = getModules(`${projectLocation}/product/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); + const files = [module1, module2, file1, file2, file3, file4, libFile]; + const host = createServerHost(files); + const resolutionTrace = createHostModuleResolutionTrace(host); + const service = createProjectService(host); + service.setCompilerOptionsForInferredProjects({ traceResolution: true }); + service.openClientFile(file1.path); + const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, file2, file2Name); + getExpectedRelativeModuleResolutionTrace(host, file1, file4, file4Name, expectedTrace); + getExpectedRelativeModuleResolutionTrace(host, file1, file3, file3Name, expectedTrace); + getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name, expectedTrace); + getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectLocation}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectLocation}/product`, expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); + getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); + verifyTrace(resolutionTrace, expectedTrace); + + const currentDirectory = getDirectoryPath(file1.path); + const watchedFiles = mapDefined(files, f => f === file1 || f.path.indexOf("/node_modules/") !== -1 ? undefined : f.path); + forEachAncestorDirectory(currentDirectory, d => { + watchedFiles.push(combinePaths(d, "tsconfig.json"), combinePaths(d, "jsconfig.json")); + }); + const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([ + `${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${projectLocation}/product/${nodeModules}`, + `${projectLocation}/${nodeModules}`, `${projectLocation}/product/test/${nodeModules}`, + `${projectLocation}/product/test/src/${nodeModules}` + ]); + checkWatches(); + + file1.content += importModuleContent; + file2.content += importModuleContent; + file3.content += importModuleContent; + file4.content += importModuleContent; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + verifyTrace(resolutionTrace, [ + getExpectedReusingResolutionFromOldProgram(file1, file2Name), + getExpectedReusingResolutionFromOldProgram(file1, file4Name), + getExpectedReusingResolutionFromOldProgram(file1, file3Name), + getExpectedReusingResolutionFromOldProgram(file1, module1Name), + getExpectedReusingResolutionFromOldProgram(file1, module2Name) + ]); + checkWatches(); + + function checkWatches() { + checkWatchedFiles(host, watchedFiles); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + } + }); + }); + + describe("when watching directories for failed lookup locations in amd resolution", () => { + const projectRoot = "/user/username/projects/project"; + const nodeFile: File = { + path: `${projectRoot}/src/typings/node.d.ts`, + content: ` +declare module "fs" { + export interface something { + } +}` + }; + const electronFile: File = { + path: `${projectRoot}/src/typings/electron.d.ts`, + content: ` +declare module 'original-fs' { + import * as fs from 'fs'; + export = fs; +}` + }; + const srcFile: File = { + path: `${projectRoot}/src/somefolder/srcfile.ts`, + content: ` +import { x } from "somefolder/module1"; +import { x } from "somefolder/module2"; +const y = x;` + }; + const moduleFile: File = { + path: `${projectRoot}/src/somefolder/module1.ts`, + content: ` +export const x = 10;` + }; + const configFile: File = { + path: `${projectRoot}/src/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "amd", + moduleResolution: "classic", + target: "es5", + outDir: "../out", + baseUrl: "./", + typeRoots: ["typings"] + } + }) + }; + + function verifyModuleResolution(useNodeFile: boolean) { + const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); + checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); + checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); + if (useNodeFile) { + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup + } + else { + checkWatchedDirectoriesDetailed(host, [`${projectRoot}`, `${projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs + } + const expectedWatchedDirectories = createMap(); + expectedWatchedDirectories.set(`${projectRoot}/src`, 1); // Wild card + expectedWatchedDirectories.set(`${projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2 + expectedWatchedDirectories.set(`${projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2 + expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 + expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs + expectedWatchedDirectories.set(`${projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); + } + + it("when resolves to ambient module", () => { + verifyModuleResolution(/*useNodeFile*/ true); + }); + + it("when resolution fails", () => { + verifyModuleResolution(/*useNodeFile*/ false); + }); + }); + + describe("ignores files/folder changes in node_modules that start with '.'", () => { + const projectPath = "/user/username/projects/project"; + const npmCacheFile: File = { + path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + content: JSON.stringify({ something: 10 }) + }; + const file1: File = { + path: `${projectPath}/test.ts`, + content: `import { x } from "somemodule";` + }; + const file2: File = { + path: `${projectPath}/node_modules/somemodule/index.d.ts`, + content: `export const x = 10;` + }; + it("when watching node_modules in inferred project for failed lookup/closed script infos", () => { + const files = [libFile, file1, file2]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(file1.path); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, files.map(f => f.path)); + (project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1; + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + it("when watching node_modules as part of wild card directories in config project", () => { + const config: File = { + path: `${projectPath}/tsconfig.json`, + content: "{}" + }; + const files = [libFile, file1, file2, config]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(file1.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = Debug.assertDefined(service.configuredProjects.get(config.path)); + checkProjectActualFiles(project, files.map(f => f.path)); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 9178416373c..9f45da51656 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -1,5 +1,4 @@ namespace ts.tscWatch { - export import libFile = TestFSWithWatch.libFile; import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath; import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; diff --git a/src/testRunner/unittests/tscWatchHelpers.ts b/src/testRunner/unittests/tscWatchHelpers.ts new file mode 100644 index 00000000000..6e3fd40c495 --- /dev/null +++ b/src/testRunner/unittests/tscWatchHelpers.ts @@ -0,0 +1,237 @@ +namespace ts.tscWatch { + export import WatchedSystem = TestFSWithWatch.TestServerHost; + export type File = TestFSWithWatch.File; + export type SymLink = TestFSWithWatch.SymLink; + export import libFile = TestFSWithWatch.libFile; + export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; + export import checkArray = TestFSWithWatch.checkArray; + export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; + export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; + export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; + export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; + export import checkOutputContains = TestFSWithWatch.checkOutputContains; + export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; + + export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray) { + checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); + } + + export function checkProgramRootFiles(program: Program, expectedFiles: ReadonlyArray) { + checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); + } + + export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { + const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host); + compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; + const watch = createWatchProgram(compilerHost); + return () => watch.getCurrentProgram(); + } + + export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { + const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host); + compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; + const watch = createWatchProgram(compilerHost); + return () => watch.getCurrentProgram().getProgram(); + } + + export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) { + const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host); + compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; + const watch = createWatchProgram(compilerHost); + return () => watch.getCurrentProgram().getProgram(); + } + + //function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) { + // return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`; + //} + + //function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) { + // return `TSFILE: ${filename}${host.newLine}`; + //} + + //interface FileOrFolderEmit extends File { + // output?: string; + //} + + //function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit { + // const result = file as FileOrFolderEmit; + // if (getOutput) { + // result.output = getOutput(file); + // } + // return result; + //} + + //function getEmittedLines(files: FileOrFolderEmit[]) { + // const seen = createMap(); + // const result: string[] = []; + // for (const { output } of files) { + // if (output && !seen.has(output)) { + // seen.set(output, true); + // result.push(output); + // } + // } + // return result; + //} + + //function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) { + // const expectedAffectedFiles = getEmittedLines(affectedFiles); + // const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line); + // checkOutputContains(host, expectedAffectedFiles); + // checkOutputDoesNotContain(host, expectedNonAffectedFiles); + //} + + const elapsedRegex = /^Elapsed:: [0-9]+ms/; + function checkOutputErrors( + host: WatchedSystem, + logsBeforeWatchDiagnostic: string[] | undefined, + preErrorsWatchDiagnostic: Diagnostic, + logsBeforeErrors: string[] | undefined, + errors: ReadonlyArray | ReadonlyArray, + disableConsoleClears?: boolean | undefined, + ...postErrorsWatchDiagnostics: Diagnostic[] + ) { + let screenClears = 0; + const outputs = host.getOutput(); + const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length + + (logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0); + assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs)); + let index = 0; + forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log)); + assertWatchDiagnostic(preErrorsWatchDiagnostic); + forEach(logsBeforeErrors, log => assertLog("logBeforeError", log)); + // Verify errors + forEach(errors, assertDiagnostic); + forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic); + assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears"); + host.clearOutput(); + + function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic { + return !!(diagnostic as Diagnostic).messageText; + } + + function assertDiagnostic(diagnostic: Diagnostic | string) { + const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic; + assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected)); + index++; + } + + function assertLog(caption: string, expected: string) { + const actual = outputs[index]; + assert.equal(actual.replace(elapsedRegex, ""), expected.replace(elapsedRegex, ""), getOutputAtFailedMessage(caption, expected)); + index++; + } + + function assertWatchDiagnostic(diagnostic: Diagnostic) { + const expected = getWatchDiagnosticWithoutDate(diagnostic); + if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) { + assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`); + screenClears++; + } + assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected)); + index++; + } + + function getOutputAtFailedMessage(caption: string, expectedOutput: string) { + return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`; + } + + function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) { + const newLines = contains(screenStartingMessageCodes, diagnostic.code) + ? `${host.newLine}${host.newLine}` + : host.newLine; + return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`; + } + } + + function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray | ReadonlyArray) { + return errors.length === 1 + ? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) + : createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length); + } + + export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) { + checkOutputErrors( + host, + /*logsBeforeWatchDiagnostic*/ undefined, + createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode), + logsBeforeErrors, + errors, + disableConsoleClears, + createErrorsFoundCompilerDiagnostic(errors)); + } + + export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { + checkOutputErrors( + host, + logsBeforeWatchDiagnostic, + createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), + logsBeforeErrors, + errors, + disableConsoleClears, + createErrorsFoundCompilerDiagnostic(errors)); + } + + export function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { + checkOutputErrors( + host, + logsBeforeWatchDiagnostic, + createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), + logsBeforeErrors, + errors, + disableConsoleClears); + assert.equal(host.exitCode, expectedExitCode); + } + + function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic { + return { + file, + start, + length, + + messageText: text, + category: message.category, + code: message.code, + }; + } + + //function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { + // let text = getLocaleSpecificMessage(message); + + // if (arguments.length > 1) { + // text = formatStringFromArgs(text, arguments, 1); + // } + + // return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message); + //} + + //function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { + // let text = getLocaleSpecificMessage(message); + + // if (arguments.length > 4) { + // text = formatStringFromArgs(text, arguments, 4); + // } + + // return getDiagnosticOfFileFrom(file, text, start, length, message); + //} + + //function getUnknownCompilerOption(program: Program, configFile: File, option: string) { + // const quotedOption = `"${option}"`; + // return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option); + //} + + export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { + let text = getLocaleSpecificMessage(message); + + if (arguments.length > 5) { + text = formatStringFromArgs(text, arguments, 5); + } + + return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!, + text, start, length, message); + } + + export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) { + const quotedModuleName = `"${moduleName}"`; + return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName); + } +} diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index 03bead8178b..662f26e8b13 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -1,45 +1,4 @@ namespace ts.tscWatch { - export import WatchedSystem = TestFSWithWatch.TestServerHost; - export type File = TestFSWithWatch.File; - export type SymLink = TestFSWithWatch.SymLink; - export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; - export import checkArray = TestFSWithWatch.checkArray; - export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; - export import checkOutputContains = TestFSWithWatch.checkOutputContains; - export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; - - export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray) { - checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); - } - - export function checkProgramRootFiles(program: Program, expectedFiles: ReadonlyArray) { - checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); - } - - export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { - const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host); - compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; - const watch = createWatchProgram(compilerHost); - return () => watch.getCurrentProgram(); - } - - export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { - const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host); - compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; - const watch = createWatchProgram(compilerHost); - return () => watch.getCurrentProgram().getProgram(); - } - - export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) { - const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host); - compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; - const watch = createWatchProgram(compilerHost); - return () => watch.getCurrentProgram().getProgram(); - } - function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) { return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`; } @@ -79,108 +38,6 @@ namespace ts.tscWatch { checkOutputDoesNotContain(host, expectedNonAffectedFiles); } - const elapsedRegex = /^Elapsed:: [0-9]+ms/; - function checkOutputErrors( - host: WatchedSystem, - logsBeforeWatchDiagnostic: string[] | undefined, - preErrorsWatchDiagnostic: Diagnostic, - logsBeforeErrors: string[] | undefined, - errors: ReadonlyArray | ReadonlyArray, - disableConsoleClears?: boolean | undefined, - ...postErrorsWatchDiagnostics: Diagnostic[] - ) { - let screenClears = 0; - const outputs = host.getOutput(); - const expectedOutputCount = 1 + errors.length + postErrorsWatchDiagnostics.length + - (logsBeforeWatchDiagnostic ? logsBeforeWatchDiagnostic.length : 0) + (logsBeforeErrors ? logsBeforeErrors.length : 0); - assert.equal(outputs.length, expectedOutputCount, JSON.stringify(outputs)); - let index = 0; - forEach(logsBeforeWatchDiagnostic, log => assertLog("logsBeforeWatchDiagnostic", log)); - assertWatchDiagnostic(preErrorsWatchDiagnostic); - forEach(logsBeforeErrors, log => assertLog("logBeforeError", log)); - // Verify errors - forEach(errors, assertDiagnostic); - forEach(postErrorsWatchDiagnostics, assertWatchDiagnostic); - assert.equal(host.screenClears.length, screenClears, "Expected number of screen clears"); - host.clearOutput(); - - function isDiagnostic(diagnostic: Diagnostic | string): diagnostic is Diagnostic { - return !!(diagnostic as Diagnostic).messageText; - } - - function assertDiagnostic(diagnostic: Diagnostic | string) { - const expected = isDiagnostic(diagnostic) ? formatDiagnostic(diagnostic, host) : diagnostic; - assert.equal(outputs[index], expected, getOutputAtFailedMessage("Diagnostic", expected)); - index++; - } - - function assertLog(caption: string, expected: string) { - const actual = outputs[index]; - assert.equal(actual.replace(elapsedRegex, ""), expected.replace(elapsedRegex, ""), getOutputAtFailedMessage(caption, expected)); - index++; - } - - function assertWatchDiagnostic(diagnostic: Diagnostic) { - const expected = getWatchDiagnosticWithoutDate(diagnostic); - if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) { - assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`); - screenClears++; - } - assert.isTrue(endsWith(outputs[index], expected), getOutputAtFailedMessage("Watch diagnostic", expected)); - index++; - } - - function getOutputAtFailedMessage(caption: string, expectedOutput: string) { - return `Expected ${caption}: ${JSON.stringify(expectedOutput)} at ${index} in ${JSON.stringify(outputs)}`; - } - - function getWatchDiagnosticWithoutDate(diagnostic: Diagnostic) { - const newLines = contains(screenStartingMessageCodes, diagnostic.code) - ? `${host.newLine}${host.newLine}` - : host.newLine; - return ` - ${flattenDiagnosticMessageText(diagnostic.messageText, host.newLine)}${newLines}`; - } - } - - function createErrorsFoundCompilerDiagnostic(errors: ReadonlyArray | ReadonlyArray) { - return errors.length === 1 - ? createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes) - : createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length); - } - - export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) { - checkOutputErrors( - host, - /*logsBeforeWatchDiagnostic*/ undefined, - createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode), - logsBeforeErrors, - errors, - disableConsoleClears, - createErrorsFoundCompilerDiagnostic(errors)); - } - - export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { - checkOutputErrors( - host, - logsBeforeWatchDiagnostic, - createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), - logsBeforeErrors, - errors, - disableConsoleClears, - createErrorsFoundCompilerDiagnostic(errors)); - } - - function checkOutputErrorsIncrementalWithExit(host: WatchedSystem, errors: ReadonlyArray | ReadonlyArray, expectedExitCode: ExitStatus, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) { - checkOutputErrors( - host, - logsBeforeWatchDiagnostic, - createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), - logsBeforeErrors, - errors, - disableConsoleClears); - assert.equal(host.exitCode, expectedExitCode); - } - function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic { return { file, @@ -218,22 +75,6 @@ namespace ts.tscWatch { return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option); } - function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { - let text = getLocaleSpecificMessage(message); - - if (arguments.length > 5) { - text = formatStringFromArgs(text, arguments, 5); - } - - return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase()))!, - text, start, length, message); - } - - function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) { - const quotedModuleName = `"${moduleName}"`; - return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName); - } - describe("tsc-watch program updates", () => { const commonFile1: File = { path: "/a/b/commonFile1.ts", @@ -2343,411 +2184,6 @@ interface Document { }); }); - describe("tsc-watch module resolution caching", () => { - it("works", () => { - const root = { - path: "/a/d/f0.ts", - content: `import {x} from "f1"` - }; - const imported = { - path: "/a/f1.ts", - content: `foo()` - }; - - const files = [root, imported, libFile]; - const host = createWatchedSystem(files); - 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"); - - // ensure that imported file was found - checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); - - const originalFileExists = host.fileExists; - { - const newContent = `import {x} from "f1" - var x: string = 1;`; - root.content = newContent; - host.reloadFS(files); - - // patch fileExists to make sure that disk is not touched - host.fileExists = notImplemented; - - // trigger synchronization to make sure that import will be fetched from the cache - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - f1IsNotModule, - getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"), - cannotFindFoo - ]); - } - { - let fileExistsIsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f2.") !== -1); - return originalFileExists.call(host, fileName); - }; - - root.content = `import {x} from "f2"`; - host.reloadFS(files); - - // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") - ]); - - assert.isTrue(fileExistsIsCalled); - } - { - let fileExistsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsCalled = true; - assert.isTrue(fileName.indexOf("/f1.") !== -1); - return originalFileExists.call(host, fileName); - }; - - const newContent = `import {x} from "f1"`; - root.content = newContent; - - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); - assert.isTrue(fileExistsCalled); - } - }); - - it("loads missing files from disk", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;` - }; - - const files = [root, libFile]; - const host = createWatchedSystem(files); - const originalFileExists = host.fileExists; - - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - - return originalFileExists.call(host, fileName); - }; - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); - - fileExistsCalledForBar = false; - root.content = `import {y} from "bar"`; - host.reloadFS(files.concat(imported)); - - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;export const x = 10;` - }; - - const files = [root, libFile]; - const filesWithImported = files.concat(imported); - const host = createWatchedSystem(filesWithImported); - const originalFileExists = host.fileExists; - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - return originalFileExists.call(host, fileName); - }; - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, emptyArray); - - fileExistsCalledForBar = false; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); - - fileExistsCalledForBar = false; - host.reloadFS(filesWithImported); - host.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("works when module resolution changes to ambient module", () => { - const root = { - path: "/a/b/foo.ts", - content: `import * as fs from "fs";` - }; - - const packageJson = { - path: "/a/b/node_modules/@types/node/package.json", - content: ` -{ - "main": "" -} -` - }; - - const nodeType = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: ` -declare module "fs" { - export interface Stats { - isFile(): boolean; - } -}` - }; - - const files = [root, libFile]; - const filesWithNodeType = files.concat(packageJson, nodeType); - const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); - - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ]); - - host.reloadFS(filesWithNodeType); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - }); - - it("works when included file with ambient module changes", () => { - const root = { - path: "/a/b/foo.ts", - content: ` -import * as fs from "fs"; -import * as u from "url"; -` - }; - - const file = { - path: "/a/b/bar.d.ts", - content: ` -declare module "url" { - export interface Url { - href?: string; - } -} -` - }; - - const fileContentWithFS = ` -declare module "fs" { - export interface Stats { - isFile(): boolean; - } -} -`; - - const files = [root, file, libFile]; - const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - - const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); - - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ]); - - file.content += fileContentWithFS; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - }); - - it("works when reusing program with files from external library", () => { - interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; } - const configDir = "/a/b/projects/myProject/src/"; - const file1: File = { - path: configDir + "file1.ts", - content: 'import module1 = require("module1");\nmodule1("hello");' - }; - const file2: File = { - path: configDir + "file2.ts", - content: 'import module11 = require("module1");\nmodule11("hello");' - }; - const module1: File = { - path: "/a/b/projects/myProject/node_modules/module1/index.js", - content: "module.exports = options => { return options.toString(); }" - }; - const configFile: File = { - path: configDir + "tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - allowJs: true, - rootDir: ".", - outDir: "../dist", - moduleResolution: "node", - maxNodeModuleJsDepth: 1 - } - }) - }; - 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 = createWatchOfConfigFile(configFile.path, host); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrorsInitial(host, emptyArray); - const expectedFiles: ExpectedFile[] = [ - createExpectedEmittedFile(file1), - createExpectedEmittedFile(file2), - createExpectedToNotEmitFile("index.js"), - createExpectedToNotEmitFile("src/index.js"), - createExpectedToNotEmitFile("src/file1.js"), - createExpectedToNotEmitFile("src/file2.js"), - createExpectedToNotEmitFile("lib.js"), - createExpectedToNotEmitFile("lib.d.ts") - ]; - verifyExpectedFiles(expectedFiles); - - file1.content += "\n;"; - expectedFiles[0].content += ";\n"; // Only emit file1 with this change - expectedFiles[1].isExpectedToEmit = false; - host.reloadFS(programFiles.concat(configFile)); - host.runQueuedTimeoutCallbacks(); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrorsIncremental(host, emptyArray); - verifyExpectedFiles(expectedFiles); - - - function verifyExpectedFiles(expectedFiles: ExpectedFile[]) { - forEach(expectedFiles, f => { - assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit")); - if (f.isExpectedToEmit) { - assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path); - } - }); - } - - function createExpectedToNotEmitFile(fileName: string): ExpectedFile { - return { - path: outDirFolder + fileName, - isExpectedToEmit: false - }; - } - - function createExpectedEmittedFile(file: File): ExpectedFile { - return { - path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js, - isExpectedToEmit: true, - content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n" - }; - } - }); - - it("works when renaming node_modules folder that already contains @types folder", () => { - const currentDirectory = "/user/username/projects/myproject"; - const file: File = { - path: `${currentDirectory}/a.ts`, - content: `import * as q from "qqq";` - }; - const module: File = { - path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, - content: "export {}" - }; - const files = [file, module, libFile]; - const host = createWatchedSystem(files, { currentDirectory }); - const watch = createWatchOfFilesAndCompilerOptions([file.path], host); - - checkProgramActualFiles(watch(), [file.path, libFile.path]); - checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true); - - host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); - host.runQueuedTimeoutCallbacks(); - checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); - checkOutputErrorsIncremental(host, emptyArray); - }); - - describe("ignores files/folder changes in node_modules that start with '.'", () => { - const projectPath = "/user/username/projects/project"; - const npmCacheFile: File = { - path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, - content: JSON.stringify({ something: 10 }) - }; - const file1: File = { - path: `${projectPath}/test.ts`, - content: `import { x } from "somemodule";` - }; - const file2: File = { - path: `${projectPath}/node_modules/somemodule/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2]; - const expectedFiles = files.map(f => f.path); - it("when watching node_modules in inferred project for failed lookup", () => { - const host = createWatchedSystem(files); - const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1); - checkProgramActualFiles(watch(), expectedFiles); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - it("when watching node_modules as part of wild card directories in config project", () => { - const config: File = { - path: `${projectPath}/tsconfig.json`, - content: "{}" - }; - const host = createWatchedSystem(files.concat(config)); - const watch = createWatchOfConfigFile(config.path, host); - checkProgramActualFiles(watch(), expectedFiles); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - }); - }); - describe("tsc-watch with when module emit is specified as node", () => { it("when instead of filechanged recursive directory watcher is invoked", () => { const configFile: File = { @@ -2883,85 +2319,4 @@ declare module "fs" { }); }); }); - - describe("tsc-watch with modules linked to sibling folder", () => { - const projectRoot = "/user/username/projects/project"; - const mainPackageRoot = `${projectRoot}/main`; - const linkedPackageRoot = `${projectRoot}/linked-package`; - const mainFile: File = { - path: `${mainPackageRoot}/index.ts`, - content: "import { Foo } from '@scoped/linked-package'" - }; - const config: File = { - path: `${mainPackageRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, - files: ["index.ts"] - }) - }; - const linkedPackageInMain: SymLink = { - path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, - symLink: `${linkedPackageRoot}` - }; - const linkedPackageJson: File = { - path: `${linkedPackageRoot}/package.json`, - content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) - }; - const linkedPackageIndex: File = { - path: `${linkedPackageRoot}/dist/index.d.ts`, - content: "export * from './other';" - }; - const linkedPackageOther: File = { - path: `${linkedPackageRoot}/dist/other.d.ts`, - content: 'export declare const Foo = "BAR";' - }; - - it("verify watched directories", () => { - const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; - const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot }); - createWatchOfConfigFile("tsconfig.json", host); - checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true); - }); - }); - - describe("tsc-watch with custom module resolution", () => { - const projectRoot = "/user/username/projects/project"; - const configFileJson: any = { - compilerOptions: { module: "commonjs", resolveJsonModule: true }, - files: ["index.ts"] - }; - const mainFile: File = { - path: `${projectRoot}/index.ts`, - content: "import settings from './settings.json';" - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify(configFileJson) - }; - const settingsJson: File = { - path: `${projectRoot}/settings.json`, - content: JSON.stringify({ content: "Print this" }) - }; - - it("verify that module resolution with json extension works when returned without extension", () => { - const files = [libFile, mainFile, config, settingsJson]; - const host = createWatchedSystem(files, { currentDirectory: projectRoot }); - const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host); - const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path); - compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { - const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost); - const resolvedModule = result.resolvedModule!; - return { - resolvedFileName: resolvedModule.resolvedFileName, - isExternalLibraryImport: resolvedModule.isExternalLibraryImport, - originalFileName: resolvedModule.originalPath, - }; - }); - const watch = createWatchProgram(compilerHost); - const program = watch.getCurrentProgram().getProgram(); - checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]); - }); - }); } diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserverHelpers.ts new file mode 100644 index 00000000000..5162f17d6ae --- /dev/null +++ b/src/testRunner/unittests/tsserverHelpers.ts @@ -0,0 +1,679 @@ +namespace ts.projectSystem { + export import TI = server.typingsInstaller; + export import protocol = server.protocol; + export import CommandNames = server.CommandNames; + + export import TestServerHost = TestFSWithWatch.TestServerHost; + export type File = TestFSWithWatch.File; + export type SymLink = TestFSWithWatch.SymLink; + export type Folder = TestFSWithWatch.Folder; + export import createServerHost = TestFSWithWatch.createServerHost; + export import checkArray = TestFSWithWatch.checkArray; + export import libFile = TestFSWithWatch.libFile; + export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; + export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; + export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; + export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; + + //const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; + //function mapOutputToJson(s: string) { + // return convertToObject( + // parseJsonText("json.json", s.replace(outputEventRegex, "")), + // [] + // ); + //} + + export const customTypesMap = { + path: "/typesMap.json", + content: `{ + "typesMap": { + "jquery": { + "match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$", + "types": ["jquery"] + }, + "quack": { + "match": "/duckquack-(\\\\d+)\\\\.min\\\\.js", + "types": ["duck-types"] + } + }, + "simpleMap": { + "Bacon": "baconjs", + "bliss": "blissfuljs", + "commander": "commander", + "cordova": "cordova", + "react": "react", + "lodash": "lodash" + } + }` + }; + + export interface PostExecAction { + readonly success: boolean; + readonly callback: TI.RequestCompletedAction; + } + + export const nullLogger: server.Logger = { + close: noop, + hasLevel: () => false, + loggingEnabled: () => false, + perftrc: noop, + info: noop, + msg: noop, + startGroup: noop, + endGroup: noop, + getLogFileName: () => undefined, + }; + + export function createHasErrorMessageLogger() { + let hasErrorMsg = false; + const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger; + const logger: server.Logger = { + close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc, + msg: (s, type) => { + Debug.fail(`Error: ${s}, type: ${type}`); + hasErrorMsg = true; + } + }; + return { logger, hasErrorMsg: () => hasErrorMsg }; + } + + export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { + protected projectService!: server.ProjectService; + constructor( + readonly globalTypingsCacheLocation: string, + throttleLimit: number, + installTypingHost: server.ServerHost, + readonly typesRegistry = createMap>(), + log?: TI.Log) { + super(installTypingHost, globalTypingsCacheLocation, TestFSWithWatch.safeList.path, customTypesMap.path, throttleLimit, log); + } + + protected postExecActions: PostExecAction[] = []; + + isKnownTypesPackageName = notImplemented; + installPackage = notImplemented; + inspectValue = notImplemented; + + executePendingCommands() { + const actionsToRun = this.postExecActions; + this.postExecActions = []; + for (const action of actionsToRun) { + action.callback(action.success); + } + } + + checkPendingCommands(expectedCount: number) { + assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); + } + + onProjectClosed = noop; + + attach(projectService: server.ProjectService) { + this.projectService = projectService; + } + + getInstallTypingHost() { + return this.installTypingHost; + } + + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + this.addPostExecAction("success", cb); + } + + sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { + this.projectService.updateTypingsForProject(response); + } + + enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { + const request = server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); + this.install(request); + } + + addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { + const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout); + const action: PostExecAction = { + success: !!out, + callback: cb + }; + this.postExecActions.push(action); + } + } + + function createNpmPackageJsonString(installedTypings: string[]): string { + const dependencies: MapLike = {}; + for (const typing of installedTypings) { + dependencies[typing] = "1.0.0"; + } + return JSON.stringify({ dependencies }); + } + + export function createTypesRegistry(...list: string[]): Map> { + const versionMap = { + "latest": "1.3.0", + "ts2.0": "1.0.0", + "ts2.1": "1.0.0", + "ts2.2": "1.2.0", + "ts2.3": "1.3.0", + "ts2.4": "1.3.0", + "ts2.5": "1.3.0", + "ts2.6": "1.3.0", + "ts2.7": "1.3.0" + }; + const map = createMap>(); + for (const l of list) { + map.set(l, versionMap); + } + return map; + } + + export function toExternalFile(fileName: string): protocol.ExternalFile { + return { fileName }; + } + + export function toExternalFiles(fileNames: string[]) { + return map(fileNames, toExternalFile); + } + + export function fileStats(nonZeroStats: Partial): server.FileStats { + return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats }; + } + + export interface ConfigFileDiagnostic { + fileName: string | undefined; + start: number | undefined; + length: number | undefined; + messageText: string; + category: DiagnosticCategory; + code: number; + reportsUnnecessary?: {}; + source?: string; + relatedInformation?: DiagnosticRelatedInformation[]; + } + + export class TestServerEventManager { + private events: server.ProjectServiceEvent[] = []; + readonly session: TestSession; + readonly service: server.ProjectService; + readonly host: TestServerHost; + constructor(files: File[], suppressDiagnosticEvents?: boolean) { + this.host = createServerHost(files); + this.session = createSession(this.host, { + canUseEvents: true, + eventHandler: event => this.events.push(event), + suppressDiagnosticEvents, + }); + this.service = this.session.getProjectService(); + } + + getEvents(): ReadonlyArray { + const events = this.events; + this.events = []; + return events; + } + + getEvent(eventName: T["eventName"]): T["data"] { + let eventData: T["data"] | undefined; + filterMutate(this.events, e => { + if (e.eventName === eventName) { + if (eventData !== undefined) { + assert(false, "more than one event found"); + } + eventData = e.data; + return false; + } + return true; + }); + return Debug.assertDefined(eventData); + } + + hasZeroEvent(eventName: T["eventName"]) { + this.events.forEach(event => assert.notEqual(event.eventName, eventName)); + } + + checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string, errors: ReadonlyArray) { + const eventData = this.getEvent(server.ConfigFileDiagEvent); + assert.equal(eventData.configFileName, configFileName); + assert.equal(eventData.triggerFile, triggerFile); + const actual = eventData.diagnostics.map(({ file, messageText, ...rest }) => ({ fileName: file && file.fileName, messageText: isString(messageText) ? messageText : "", ...rest })); + if (errors) { + assert.deepEqual(actual, errors); + } + } + + assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { + assert.deepEqual(this.getEvent(server.ProjectInfoTelemetryEvent), { + projectId: sys.createSHA256Hash!(configFile), + fileStats: fileStats({ ts: 1 }), + compilerOptions: {}, + extends: false, + files: false, + include: false, + exclude: false, + compileOnSave: false, + typeAcquisition: { + enable: false, + exclude: false, + include: false, + }, + configFileName: "tsconfig.json", + projectType: "configured", + languageServiceEnabled: true, + version, + ...partial, + }); + } + + assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void { + assert.deepEqual(this.getEvent(server.OpenFileInfoTelemetryEvent), { info }); + } + assertNoOpenFilesTelemetryEvent(): void { + this.hasZeroEvent(server.OpenFileInfoTelemetryEvent); + } + } + + export class TestSession extends server.Session { + private seq = 0; + public events: protocol.Event[] = []; + public host!: TestServerHost; + + getProjectService() { + return this.projectService; + } + + public getSeq() { + return this.seq; + } + + public getNextSeq() { + return this.seq + 1; + } + + public executeCommandSeq(request: Partial) { + this.seq++; + request.seq = this.seq; + request.type = "request"; + return this.executeCommand(request); + } + + public event(body: T, eventName: string) { + this.events.push(server.toEvent(eventName, body)); + super.event(body, eventName); + } + + public clearMessages() { + clear(this.events); + this.host.clearOutput(); + } + } + + export function createSession(host: server.ServerHost, opts: Partial = {}) { + if (opts.typingsInstaller === undefined) { + opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host); + } + + if (opts.eventHandler !== undefined) { + opts.canUseEvents = true; + } + + const sessionOptions: server.SessionOptions = { + host, + cancellationToken: server.nullCancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + typingsInstaller: undefined!, // TODO: GH#18217 + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: opts.logger || createHasErrorMessageLogger().logger, + canUseEvents: false + }; + + return new TestSession({ ...sessionOptions, ...opts }); + } + + //function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { + // const events: T[] = []; + // const session = createSession(host, { + // eventHandler: e => { + // if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { + // events.push(e as T); + // } + // } + // }); + + // return { session, events }; + //} + + //function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { + // const session = createSession(host, { canUseEvents: true, ...opts }); + + // return { + // session, + // getEvents, + // clearEvents + // }; + + // function getEvents() { + // return mapDefined(host.getOutput(), s => { + // const e = mapOutputToJson(s); + // return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; + // }); + // } + + // function clearEvents() { + // session.clearMessages(); + // } + //} + + export interface CreateProjectServiceParameters { + cancellationToken?: HostCancellationToken; + logger?: server.Logger; + useSingleInferredProject?: boolean; + typingsInstaller?: server.ITypingsInstaller; + eventHandler?: server.ProjectServiceEventHandler; + } + + export class TestProjectService extends server.ProjectService { + constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, + typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler, opts: Partial = {}) { + super({ + host, + logger, + cancellationToken, + useSingleInferredProject, + useInferredProjectPerProjectRoot: false, + typingsInstaller, + typesMapLocation: customTypesMap.path, + eventHandler, + ...opts + }); + } + + checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { + checkNumberOfProjects(this, count); + } + } + export function createProjectService(host: server.ServerHost, parameters: CreateProjectServiceParameters = {}, options?: Partial) { + const cancellationToken = parameters.cancellationToken || server.nullCancellationToken; + const logger = parameters.logger || createHasErrorMessageLogger().logger; + const useSingleInferredProject = parameters.useSingleInferredProject !== undefined ? parameters.useSingleInferredProject : false; + return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, parameters.typingsInstaller!, parameters.eventHandler!, options); // TODO: GH#18217 + } + + export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { + assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); + } + + export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { + assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); + } + + export function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { + assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); + } + + export function checkNumberOfProjects(projectService: server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { + checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0); + checkNumberOfExternalProjects(projectService, count.externalProjects || 0); + checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); + } + + export function configuredProjectAt(projectService: server.ProjectService, index: number) { + const values = projectService.configuredProjects.values(); + while (index > 0) { + values.next(); + index--; + } + return values.next().value; + } + + export function checkProjectActualFiles(project: server.Project, expectedFiles: ReadonlyArray) { + checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles); + } + + //function checkProjectRootFiles(project: server.Project, expectedFiles: ReadonlyArray) { + // checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); + //} + + export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { + dir = normalizePath(dir); + const result: string[] = []; + forEachAncestorDirectory(dir, ancestor => { + if (mapAncestor(ancestor)) { + result.push(combinePaths(ancestor, path2)); + } + }); + return result; + } + + export function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) { + return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(directorySeparator).length > 4); + } + + export const nodeModules = "node_modules"; + //function getNodeModuleDirectories(dir: string) { + // return getRootsToWatchWithAncestorDirectory(dir, nodeModules); + //} + + export const nodeModulesAtTypes = "node_modules/@types"; + export function getTypeRootsFromLocation(currentDirectory: string) { + return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); + } + + //function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { + // return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); + //} + + //function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { + // checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); + //} + + //function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { + // const start = str.indexOf(substring); + // Debug.assert(start !== -1); + // return protocolToLocation(str)(start); + //} + //function protocolToLocation(text: string): (pos: number) => protocol.Location { + // const lineStarts = computeLineStarts(text); + // return pos => { + // const x = computeLineAndCharacterOfPosition(lineStarts, pos); + // return { line: x.line + 1, offset: x.character + 1 }; + // }; + //} + //function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { + // const span = textSpanFromSubstring(str, substring, options); + // const toLocation = protocolToLocation(str); + // return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; + //} + //function protocolRenameSpanFromSubstring( + // str: string, + // substring: string, + // options?: SpanFromSubstringOptions, + // prefixSuffixText?: { readonly prefixText?: string, readonly suffixText?: string }, + //): protocol.RenameTextSpan { + // return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; + //} + //function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { + // const start = nthIndexOf(str, substring, options ? options.index : 0); + // Debug.assert(start !== -1); + // return createTextSpan(start, substring.length); + //} + //function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { + // return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; + //} + //function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan { + // return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) }; + //} + //function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan { + // return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) }; + //} + //function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { + // return documentSpanFromSubstring(file, substring, options); + //} + //interface SpanFromSubstringOptions { + // readonly index: number; + //} + + //function nthIndexOf(str: string, substr: string, n: number): number { + // let index = -1; + // for (; n >= 0; n--) { + // index = str.indexOf(substr, index + 1); + // if (index === -1) return -1; + // } + // return index; + //} + + /** + * Test server cancellation token used to mock host token cancellation requests. + * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls + * should be made before canceling the token. The id of the request to cancel should be set with + * setRequestToCancel(); + */ + export class TestServerCancellationToken implements server.ServerCancellationToken { + private currentId: number | undefined = -1; + private requestToCancel = -1; + private isCancellationRequestedCount = 0; + + constructor(private cancelAfterRequest = 0) { + } + + setRequest(requestId: number) { + this.currentId = requestId; + } + + setRequestToCancel(requestId: number) { + this.resetToken(); + this.requestToCancel = requestId; + } + + resetRequest(requestId: number) { + assert.equal(requestId, this.currentId, "unexpected request id in cancellation"); + this.currentId = undefined; + } + + isCancellationRequested() { + this.isCancellationRequestedCount++; + // If the request id is the request to cancel and isCancellationRequestedCount + // has been met then cancel the request. Ex: cancel the request if it is a + // nav bar request & isCancellationRequested() has already been called three times. + return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest; + } + + resetToken() { + this.currentId = -1; + this.isCancellationRequestedCount = 0; + this.requestToCancel = -1; + } + } + + export function makeSessionRequest(command: string, args: T): protocol.Request { + return { + seq: 0, + type: "request", + command, + arguments: args + }; + } + + export function executeSessionRequest(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] { + return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; + } + + export function executeSessionRequestNoResponse(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { + session.executeCommand(makeSessionRequest(command, args)); + } + + export function openFilesForSession(files: ReadonlyArray, session: server.Session): void { + for (const file of files) { + session.executeCommand(makeSessionRequest(CommandNames.Open, + "projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); + } + } + + export function closeFilesForSession(files: ReadonlyArray, session: server.Session): void { + for (const file of files) { + session.executeCommand(makeSessionRequest(CommandNames.Close, { file: file.path })); + } + } + + //interface ErrorInformation { + // diagnosticMessage: DiagnosticMessage; + // errorTextArguments?: string[]; + //} + + //function getProtocolDiagnosticMessage({ diagnosticMessage, errorTextArguments = [] }: ErrorInformation) { + // return formatStringFromArgs(diagnosticMessage.message, errorTextArguments); + //} + + //function verifyDiagnostics(actual: server.protocol.Diagnostic[], expected: ErrorInformation[]) { + // const expectedErrors = expected.map(getProtocolDiagnosticMessage); + // assert.deepEqual(actual.map(diag => flattenDiagnosticMessageText(diag.text, "\n")), expectedErrors); + //} + + //function verifyNoDiagnostics(actual: server.protocol.Diagnostic[]) { + // verifyDiagnostics(actual, []); + //} + + //function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void { + // checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent); + //} + + //function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic { + // return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined }; + //} + + //function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void { + // checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent); + //} + + //function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { + // checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); + //} + + //function checkNoDiagnosticEvents(session: TestSession) { + // for (const event of session.events) { + // assert.isFalse(event.event.endsWith("Diag"), JSON.stringify(event)); + // } + //} + + //function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { + // const events = session.events; + // assert.deepEqual(events[index], expectedEvent, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`); + + // const outputs = session.host.getOutput(); + // assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine)); + + // if (isMostRecent) { + // assert.strictEqual(events.length, index + 1, JSON.stringify(events)); + // assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); + // } + //} + + //function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { + // return { + // ...protocolFileSpanFromSubstring(file, text, options), + // isDefinition, + // isWriteAccess: isDefinition, + // lineText, + // }; + //} + + //function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry { + // return { + // ...documentSpanFromSubstring(file, text, options), + // isDefinition, + // isWriteAccess: isDefinition, + // isInString: undefined, + // }; + //} + + //function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray): void { + // openFilesForSession([file], session); + // const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); + // const program = project.getCurrentProgram()!; + // const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); + // closeFilesForSession([file], session); + + // Debug.assert(!output.emitSkipped); + // assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); + //} +} diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index cefa8a5e34c..3b7c276cfc4 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -1,21 +1,4 @@ namespace ts.projectSystem { - import TI = server.typingsInstaller; - import protocol = server.protocol; - import CommandNames = server.CommandNames; - - export import TestServerHost = TestFSWithWatch.TestServerHost; - export type File = TestFSWithWatch.File; - export type SymLink = TestFSWithWatch.SymLink; - export type Folder = TestFSWithWatch.Folder; - export import createServerHost = TestFSWithWatch.createServerHost; - export import checkArray = TestFSWithWatch.checkArray; - export import libFile = TestFSWithWatch.libFile; - export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles; - export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed; - export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; - export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; - import safeList = TestFSWithWatch.safeList; - const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; function mapOutputToJson(s: string) { return convertToObject( @@ -24,319 +7,6 @@ namespace ts.projectSystem { ); } - export const customTypesMap = { - path: "/typesMap.json", - content: `{ - "typesMap": { - "jquery": { - "match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$", - "types": ["jquery"] - }, - "quack": { - "match": "/duckquack-(\\\\d+)\\\\.min\\\\.js", - "types": ["duck-types"] - } - }, - "simpleMap": { - "Bacon": "baconjs", - "bliss": "blissfuljs", - "commander": "commander", - "cordova": "cordova", - "react": "react", - "lodash": "lodash" - } - }` - }; - - export interface PostExecAction { - readonly success: boolean; - readonly callback: TI.RequestCompletedAction; - } - - export const nullLogger: server.Logger = { - close: noop, - hasLevel: () => false, - loggingEnabled: () => false, - perftrc: noop, - info: noop, - msg: noop, - startGroup: noop, - endGroup: noop, - getLogFileName: () => undefined, - }; - - export function createHasErrorMessageLogger() { - let hasErrorMsg = false; - const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger; - const logger: server.Logger = { - close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc, - msg: (s, type) => { - Debug.fail(`Error: ${s}, type: ${type}`); - hasErrorMsg = true; - } - }; - return { logger, hasErrorMsg: () => hasErrorMsg }; - } - - export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { - protected projectService!: server.ProjectService; - constructor( - readonly globalTypingsCacheLocation: string, - throttleLimit: number, - installTypingHost: server.ServerHost, - readonly typesRegistry = createMap>(), - log?: TI.Log) { - super(installTypingHost, globalTypingsCacheLocation, safeList.path, customTypesMap.path, throttleLimit, log); - } - - protected postExecActions: PostExecAction[] = []; - - isKnownTypesPackageName = notImplemented; - installPackage = notImplemented; - inspectValue = notImplemented; - - executePendingCommands() { - const actionsToRun = this.postExecActions; - this.postExecActions = []; - for (const action of actionsToRun) { - action.callback(action.success); - } - } - - checkPendingCommands(expectedCount: number) { - assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); - } - - onProjectClosed = noop; - - attach(projectService: server.ProjectService) { - this.projectService = projectService; - } - - getInstallTypingHost() { - return this.installTypingHost; - } - - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - this.addPostExecAction("success", cb); - } - - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { - this.projectService.updateTypingsForProject(response); - } - - enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { - const request = server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); - this.install(request); - } - - addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { - const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout); - const action: PostExecAction = { - success: !!out, - callback: cb - }; - this.postExecActions.push(action); - } - } - - function createNpmPackageJsonString(installedTypings: string[]): string { - const dependencies: MapLike = {}; - for (const typing of installedTypings) { - dependencies[typing] = "1.0.0"; - } - return JSON.stringify({ dependencies }); - } - - export function createTypesRegistry(...list: string[]): Map> { - const versionMap = { - "latest": "1.3.0", - "ts2.0": "1.0.0", - "ts2.1": "1.0.0", - "ts2.2": "1.2.0", - "ts2.3": "1.3.0", - "ts2.4": "1.3.0", - "ts2.5": "1.3.0", - "ts2.6": "1.3.0", - "ts2.7": "1.3.0" - }; - const map = createMap>(); - for (const l of list) { - map.set(l, versionMap); - } - return map; - } - - function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) { - const resolutionTrace: string[] = []; - host.trace = resolutionTrace.push.bind(resolutionTrace); - return resolutionTrace; - } - - export function toExternalFile(fileName: string): protocol.ExternalFile { - return { fileName }; - } - - export function toExternalFiles(fileNames: string[]) { - return map(fileNames, toExternalFile); - } - - export function fileStats(nonZeroStats: Partial): server.FileStats { - return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats }; - } - - export interface ConfigFileDiagnostic { - fileName: string | undefined; - start: number | undefined; - length: number | undefined; - messageText: string; - category: DiagnosticCategory; - code: number; - reportsUnnecessary?: {}; - source?: string; - relatedInformation?: DiagnosticRelatedInformation[]; - } - - export class TestServerEventManager { - private events: server.ProjectServiceEvent[] = []; - readonly session: TestSession; - readonly service: server.ProjectService; - readonly host: TestServerHost; - constructor(files: File[], suppressDiagnosticEvents?: boolean) { - this.host = createServerHost(files); - this.session = createSession(this.host, { - canUseEvents: true, - eventHandler: event => this.events.push(event), - suppressDiagnosticEvents, - }); - this.service = this.session.getProjectService(); - } - - getEvents(): ReadonlyArray { - const events = this.events; - this.events = []; - return events; - } - - getEvent(eventName: T["eventName"]): T["data"] { - let eventData: T["data"] | undefined; - filterMutate(this.events, e => { - if (e.eventName === eventName) { - if (eventData !== undefined) { - assert(false, "more than one event found"); - } - eventData = e.data; - return false; - } - return true; - }); - return Debug.assertDefined(eventData); - } - - hasZeroEvent(eventName: T["eventName"]) { - this.events.forEach(event => assert.notEqual(event.eventName, eventName)); - } - - checkSingleConfigFileDiagEvent(configFileName: string, triggerFile: string, errors: ReadonlyArray) { - const eventData = this.getEvent(server.ConfigFileDiagEvent); - assert.equal(eventData.configFileName, configFileName); - assert.equal(eventData.triggerFile, triggerFile); - const actual = eventData.diagnostics.map(({ file, messageText, ...rest }) => ({ fileName: file && file.fileName, messageText: isString(messageText) ? messageText : "", ...rest })); - if (errors) { - assert.deepEqual(actual, errors); - } - } - - assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { - assert.deepEqual(this.getEvent(server.ProjectInfoTelemetryEvent), { - projectId: sys.createSHA256Hash!(configFile), - fileStats: fileStats({ ts: 1 }), - compilerOptions: {}, - extends: false, - files: false, - include: false, - exclude: false, - compileOnSave: false, - typeAcquisition: { - enable: false, - exclude: false, - include: false, - }, - configFileName: "tsconfig.json", - projectType: "configured", - languageServiceEnabled: true, - version, - ...partial, - }); - } - - assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void { - assert.deepEqual(this.getEvent(server.OpenFileInfoTelemetryEvent), { info }); - } - assertNoOpenFilesTelemetryEvent(): void { - this.hasZeroEvent(server.OpenFileInfoTelemetryEvent); - } - } - - class TestSession extends server.Session { - private seq = 0; - public events: protocol.Event[] = []; - public host!: TestServerHost; - - getProjectService() { - return this.projectService; - } - - public getSeq() { - return this.seq; - } - - public getNextSeq() { - return this.seq + 1; - } - - public executeCommandSeq(request: Partial) { - this.seq++; - request.seq = this.seq; - request.type = "request"; - return this.executeCommand(request); - } - - public event(body: T, eventName: string) { - this.events.push(server.toEvent(eventName, body)); - super.event(body, eventName); - } - - public clearMessages() { - clear(this.events); - this.host.clearOutput(); - } - } - - export function createSession(host: server.ServerHost, opts: Partial = {}) { - if (opts.typingsInstaller === undefined) { - opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host); - } - - if (opts.eventHandler !== undefined) { - opts.canUseEvents = true; - } - - const sessionOptions: server.SessionOptions = { - host, - cancellationToken: server.nullCancellationToken, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - typingsInstaller: undefined!, // TODO: GH#18217 - byteLength: Utils.byteLength, - hrtime: process.hrtime, - logger: opts.logger || createHasErrorMessageLogger().logger, - canUseEvents: false - }; - - return new TestSession({ ...sessionOptions, ...opts }); - } - function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { const events: T[] = []; const session = createSession(host, { @@ -371,101 +41,14 @@ namespace ts.projectSystem { } } - interface CreateProjectServiceParameters { - cancellationToken?: HostCancellationToken; - logger?: server.Logger; - useSingleInferredProject?: boolean; - typingsInstaller?: server.ITypingsInstaller; - eventHandler?: server.ProjectServiceEventHandler; - } - - export class TestProjectService extends server.ProjectService { - constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, - typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler, opts: Partial = {}) { - super({ - host, - logger, - cancellationToken, - useSingleInferredProject, - useInferredProjectPerProjectRoot: false, - typingsInstaller, - typesMapLocation: customTypesMap.path, - eventHandler, - ...opts - }); - } - - checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { - checkNumberOfProjects(this, count); - } - } - export function createProjectService(host: server.ServerHost, parameters: CreateProjectServiceParameters = {}, options?: Partial) { - const cancellationToken = parameters.cancellationToken || server.nullCancellationToken; - const logger = parameters.logger || createHasErrorMessageLogger().logger; - const useSingleInferredProject = parameters.useSingleInferredProject !== undefined ? parameters.useSingleInferredProject : false; - return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, parameters.typingsInstaller!, parameters.eventHandler!, options); // TODO: GH#18217 - } - - export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); - } - - function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); - } - - function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); - } - - export function checkNumberOfProjects(projectService: server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { - checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0); - checkNumberOfExternalProjects(projectService, count.externalProjects || 0); - checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); - } - - export function configuredProjectAt(projectService: server.ProjectService, index: number) { - const values = projectService.configuredProjects.values(); - while (index > 0) { - values.next(); - index--; - } - return values.next().value; - } - - export function checkProjectActualFiles(project: server.Project, expectedFiles: ReadonlyArray) { - checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles); - } - function checkProjectRootFiles(project: server.Project, expectedFiles: ReadonlyArray) { checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); } - function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { - dir = normalizePath(dir); - const result: string[] = []; - forEachAncestorDirectory(dir, ancestor => { - if (mapAncestor(ancestor)) { - result.push(combinePaths(ancestor, path2)); - } - }); - return result; - } - - function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) { - return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(directorySeparator).length > 4); - } - - const nodeModules = "node_modules"; function getNodeModuleDirectories(dir: string) { return getRootsToWatchWithAncestorDirectory(dir, nodeModules); } - export const nodeModulesAtTypes = "node_modules/@types"; - export function getTypeRootsFromLocation(currentDirectory: string) { - return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); - } - function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); } @@ -533,79 +116,6 @@ namespace ts.projectSystem { return index; } - /** - * Test server cancellation token used to mock host token cancellation requests. - * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls - * should be made before canceling the token. The id of the request to cancel should be set with - * setRequestToCancel(); - */ - export class TestServerCancellationToken implements server.ServerCancellationToken { - private currentId: number | undefined = -1; - private requestToCancel = -1; - private isCancellationRequestedCount = 0; - - constructor(private cancelAfterRequest = 0) { - } - - setRequest(requestId: number) { - this.currentId = requestId; - } - - setRequestToCancel(requestId: number) { - this.resetToken(); - this.requestToCancel = requestId; - } - - resetRequest(requestId: number) { - assert.equal(requestId, this.currentId, "unexpected request id in cancellation"); - this.currentId = undefined; - } - - isCancellationRequested() { - this.isCancellationRequestedCount++; - // If the request id is the request to cancel and isCancellationRequestedCount - // has been met then cancel the request. Ex: cancel the request if it is a - // nav bar request & isCancellationRequested() has already been called three times. - return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest; - } - - resetToken() { - this.currentId = -1; - this.isCancellationRequestedCount = 0; - this.requestToCancel = -1; - } - } - - export function makeSessionRequest(command: string, args: T): protocol.Request { - return { - seq: 0, - type: "request", - command, - arguments: args - }; - } - - export function executeSessionRequest(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] { - return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; - } - - export function executeSessionRequestNoResponse(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { - session.executeCommand(makeSessionRequest(command, args)); - } - - export function openFilesForSession(files: ReadonlyArray, session: server.Session): void { - for (const file of files) { - session.executeCommand(makeSessionRequest(CommandNames.Open, - "projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); - } - } - - export function closeFilesForSession(files: ReadonlyArray, session: server.Session): void { - for (const file of files) { - session.executeCommand(makeSessionRequest(CommandNames.Close, { file: file.path })); - } - } - interface ErrorInformation { diagnosticMessage: DiagnosticMessage; errorTextArguments?: string[]; @@ -4171,47 +3681,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem extra resolution pass in lshost", () => { - it("can load typings that are proper modules", () => { - const file1 = { - path: "/a/b/app.js", - content: `var x = require("lib")` - }; - const lib = { - path: "/a/cache/node_modules/@types/lib/index.d.ts", - content: "export let x = 1" - }; - const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]); - const resolutionTrace = createHostModuleResolutionTrace(host); - const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", /*throttleLimit*/5, host) }); - - projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true }); - projectService.openClientFile(file1.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - - assert.deepEqual(resolutionTrace, [ - "======== Resolving module 'lib' from '/a/b/app.js'. ========", - "Module resolution kind is not specified, using 'NodeJs'.", - "Loading module 'lib' from 'node_modules' folder, target file type 'TypeScript'.", - "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.", - "Directory '/a/node_modules' does not exist, skipping all lookups in it.", - "Directory '/node_modules' does not exist, skipping all lookups in it.", - "Loading module 'lib' from 'node_modules' folder, target file type 'JavaScript'.", - "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.", - "Directory '/a/node_modules' does not exist, skipping all lookups in it.", - "Directory '/node_modules' does not exist, skipping all lookups in it.", - "======== Module name 'lib' was not resolved. ========", - `Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`, - "File '/a/cache/node_modules/lib.d.ts' does not exist.", - "File '/a/cache/node_modules/@types/lib/package.json' does not exist.", - "File '/a/cache/node_modules/@types/lib.d.ts' does not exist.", - "File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.", - ]); - checkProjectActualFiles(proj, [file1.path, lib.path]); - }); - }); - describe("tsserverProjectSystem navigate-to for javascript project", () => { function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined; @@ -8218,56 +7687,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem Watched recursive directories with windows style file system", () => { - function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { - const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; - const configFile: File = { - path: root + "project/tsconfig.json", - content: "{}" - }; - const file1: File = { - path: root + "project/file1.ts", - content: "let x = 10;" - }; - const file2: File = { - path: root + "project/file2.ts", - content: "let y = 10;" - }; - const files = [configFile, file1, file2, libFile]; - const host = createServerHost(files, { useWindowsStylePaths: true }); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); - checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); - checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, [ - root + "project", - root + "project/node_modules/@types" - ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); - } - - function verifyRootedDirectoryWatch(rootedPath: string) { - it("When project is in rootFolder of style c:/", () => { - verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); - }); - - it("When files at some folder other than root", () => { - verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); - }); - } - - describe("for rootFolder of style c:/", () => { - verifyRootedDirectoryWatch("c:/"); - }); - - describe("for rootFolder of style c:/users/username", () => { - verifyRootedDirectoryWatch("c:/users/username/"); - }); - }); - describe("tsserverProjectSystem typingsInstaller on inferred Project", () => { it("when projectRootPath is provided", () => { const projects = "/users/username/projects"; @@ -8671,490 +8090,6 @@ new C();` }); }); - describe("tsserverProjectSystem module resolution caching", () => { - const projectLocation = "/user/username/projects/myproject"; - const configFile: File = { - path: `${projectLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { traceResolution: true } }) - }; - - function getModules(module1Path: string, module2Path: string) { - const module1: File = { - path: module1Path, - content: `export function module1() {}` - }; - const module2: File = { - path: module2Path, - content: `export function module2() {}` - }; - return { module1, module2 }; - } - - function verifyTrace(resolutionTrace: string[], expected: string[]) { - assert.deepEqual(resolutionTrace, expected); - resolutionTrace.length = 0; - } - - function getExpectedFileDoesNotExistResolutionTrace(host: TestServerHost, expectedTrace: string[], foundModule: boolean, module: File, directory: string, file: string, ignoreIfParentMissing?: boolean) { - if (!foundModule) { - const path = combinePaths(directory, file); - if (!ignoreIfParentMissing || host.directoryExists(getDirectoryPath(path))) { - if (module.path === path) { - foundModule = true; - } - else { - expectedTrace.push(`File '${path}' does not exist.`); - } - } - } - return foundModule; - } - - function getExpectedMissedLocationResolutionTrace(host: TestServerHost, expectedTrace: string[], dirPath: string, module: File, moduleName: string, useNodeModules: boolean, cacheLocation?: string) { - let foundModule = false; - forEachAncestorDirectory(dirPath, dirPath => { - if (dirPath === cacheLocation) { - return foundModule; - } - - const directory = useNodeModules ? combinePaths(dirPath, nodeModules) : dirPath; - if (useNodeModules && !foundModule && !host.directoryExists(directory)) { - expectedTrace.push(`Directory '${directory}' does not exist, skipping all lookups in it.`); - return undefined; - } - foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/package.json`, /*ignoreIfParentMissing*/ true); - foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ts`); - foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.tsx`); - foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.d.ts`); - foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/index.ts`, /*ignoreIfParentMissing*/ true); - if (useNodeModules && !foundModule) { - expectedTrace.push(`Directory '${directory}/@types' does not exist, skipping all lookups in it.`); - } - return foundModule ? true : undefined; - }); - } - - function getExpectedResolutionTraceHeader(expectedTrace: string[], file: File, moduleName: string) { - expectedTrace.push( - `======== Resolving module '${moduleName}' from '${file.path}'. ========`, - `Module resolution kind is not specified, using 'NodeJs'.` - ); - } - - function getExpectedResolutionTraceFooter(expectedTrace: string[], module: File, moduleName: string, addRealPathTrace: boolean, ignoreModuleFileFound?: boolean) { - if (!ignoreModuleFileFound) { - expectedTrace.push(`File '${module.path}' exist - use it as a name resolution result.`); - } - if (addRealPathTrace) { - expectedTrace.push(`Resolving real path for '${module.path}', result '${module.path}'.`); - } - expectedTrace.push(`======== Module name '${moduleName}' was successfully resolved to '${module.path}'. ========`); - } - - function getExpectedRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) { - getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); - expectedTrace.push(`Loading module as file / folder, candidate module location '${removeFileExtension(module.path)}', target file type 'TypeScript'.`); - getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(normalizePath(combinePaths(getDirectoryPath(file.path), moduleName))), module, moduleName.substring(moduleName.lastIndexOf("/") + 1), /*useNodeModules*/ false); - getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false); - return expectedTrace; - } - - function getExpectedNonRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) { - getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); - expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`); - getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true); - getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ true); - return expectedTrace; - } - - function getExpectedNonRelativeModuleResolutionFromCacheTrace(host: TestServerHost, file: File, module: File, moduleName: string, cacheLocation: string, expectedTrace: string[] = []) { - getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); - expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`); - getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true, cacheLocation); - expectedTrace.push(`Resolution for module '${moduleName}' was found in cache from location '${cacheLocation}'.`); - getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false, /*ignoreModuleFileFound*/ true); - return expectedTrace; - } - - function getExpectedReusingResolutionFromOldProgram(file: File, moduleName: string) { - return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`; - } - - function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: ReadonlyArray) { - const expectedRecursiveDirectories = arrayToSet([projectLocation, `${projectLocation}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); - checkWatchedFiles(host, mapDefined(files, f => { - if (f === openFile) { - return undefined; - } - const indexOfNodeModules = f.path.indexOf("/node_modules/"); - if (indexOfNodeModules === -1) { - return f.path; - } - expectedRecursiveDirectories.set(f.path.substr(0, indexOfNodeModules + "/node_modules".length), true); - return undefined; - })); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.keys()), /*recursive*/ true); - } - - describe("from files in same folder", () => { - function getFiles(fileContent: string) { - const file1: File = { - path: `${projectLocation}/src/file1.ts`, - content: fileContent - }; - const file2: File = { - path: `${projectLocation}/src/file2.ts`, - content: fileContent - }; - return { file1, file2 }; - } - - it("relative module name", () => { - const module1Name = "./module1"; - const module2Name = "../module2"; - const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; - const { file1, file2 } = getFiles(fileContent); - const { module1, module2 } = getModules(`${projectLocation}/src/module1.ts`, `${projectLocation}/module2.ts`); - const files = [module1, module2, file1, file2, configFile, libFile]; - const host = createServerHost(files); - const resolutionTrace = createHostModuleResolutionTrace(host); - const service = createProjectService(host); - service.openClientFile(file1.path); - const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name); - getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); - verifyTrace(resolutionTrace, expectedTrace); - verifyWatchesWithConfigFile(host, files, file1); - - file1.content += fileContent; - file2.content += fileContent; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - verifyTrace(resolutionTrace, [ - getExpectedReusingResolutionFromOldProgram(file1, module1Name), - getExpectedReusingResolutionFromOldProgram(file1, module2Name) - ]); - verifyWatchesWithConfigFile(host, files, file1); - }); - - it("non relative module name", () => { - const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`]; - const module1Name = "module1"; - const module2Name = "module2"; - const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; - const { file1, file2 } = getFiles(fileContent); - const { module1, module2 } = getModules(`${projectLocation}/src/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); - const files = [module1, module2, file1, file2, configFile, libFile]; - const host = createServerHost(files); - const resolutionTrace = createHostModuleResolutionTrace(host); - const service = createProjectService(host); - service.openClientFile(file1.path); - const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); - getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); - verifyTrace(resolutionTrace, expectedTrace); - verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); - - file1.content += fileContent; - file2.content += fileContent; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - verifyTrace(resolutionTrace, [ - getExpectedReusingResolutionFromOldProgram(file1, module1Name), - getExpectedReusingResolutionFromOldProgram(file1, module2Name) - ]); - verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); - }); - }); - - describe("from files in different folders", () => { - function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) { - const file1: File = { - path: `${projectLocation}/product/src/file1.ts`, - content: fileContent1 - }; - const file2: File = { - path: `${projectLocation}/product/src/feature/file2.ts`, - content: fileContent2 - }; - const file3: File = { - path: `${projectLocation}/product/test/src/file3.ts`, - content: fileContent3 - }; - const file4: File = { - path: `${projectLocation}/product/test/file4.ts`, - content: fileContent4 - }; - return { file1, file2, file3, file4 }; - } - - it("relative module name", () => { - const module1Name = "./module1"; - const module2Name = "../module2"; - const module3Name = "../module1"; - const module4Name = "../../module2"; - const module5Name = "../../src/module1"; - const module6Name = "../src/module1"; - const fileContent1 = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; - const fileContent2 = `import { module1 } from "${module3Name}";import { module2 } from "${module4Name}";`; - const fileContent3 = `import { module1 } from "${module5Name}";import { module2 } from "${module4Name}";`; - const fileContent4 = `import { module1 } from "${module6Name}";import { module2 } from "${module2Name}";`; - const { file1, file2, file3, file4 } = getFiles(fileContent1, fileContent2, fileContent3, fileContent4); - const { module1, module2 } = getModules(`${projectLocation}/product/src/module1.ts`, `${projectLocation}/product/module2.ts`); - const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; - const host = createServerHost(files); - const resolutionTrace = createHostModuleResolutionTrace(host); - const service = createProjectService(host); - service.openClientFile(file1.path); - const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name); - getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file2, module1, module3Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file2, module2, module4Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file4, module1, module6Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file3, module1, module5Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file3, module2, module4Name, expectedTrace); - verifyTrace(resolutionTrace, expectedTrace); - verifyWatchesWithConfigFile(host, files, file1); - - file1.content += fileContent1; - file2.content += fileContent2; - file3.content += fileContent3; - file4.content += fileContent4; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - verifyTrace(resolutionTrace, [ - getExpectedReusingResolutionFromOldProgram(file1, module1Name), - getExpectedReusingResolutionFromOldProgram(file1, module2Name) - ]); - verifyWatchesWithConfigFile(host, files, file1); - }); - - it("non relative module name", () => { - const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/product`]; - const module1Name = "module1"; - const module2Name = "module2"; - const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; - const { file1, file2, file3, file4 } = getFiles(fileContent); - const { module1, module2 } = getModules(`${projectLocation}/product/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); - const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; - const host = createServerHost(files); - const resolutionTrace = createHostModuleResolutionTrace(host); - const service = createProjectService(host); - service.openClientFile(file1.path); - const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); - getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectLocation}/product`, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectLocation}/product`, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); - verifyTrace(resolutionTrace, expectedTrace); - verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); - - file1.content += fileContent; - file2.content += fileContent; - file3.content += fileContent; - file4.content += fileContent; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - verifyTrace(resolutionTrace, [ - getExpectedReusingResolutionFromOldProgram(file1, module1Name), - getExpectedReusingResolutionFromOldProgram(file1, module2Name) - ]); - verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); - }); - - it("non relative module name from inferred project", () => { - const module1Name = "module1"; - const module2Name = "module2"; - const file2Name = "./feature/file2"; - const file3Name = "../test/src/file3"; - const file4Name = "../test/file4"; - const importModuleContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; - const { file1, file2, file3, file4 } = getFiles(`import "${file2Name}"; import "${file4Name}"; import "${file3Name}"; ${importModuleContent}`, importModuleContent, importModuleContent, importModuleContent); - const { module1, module2 } = getModules(`${projectLocation}/product/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); - const files = [module1, module2, file1, file2, file3, file4, libFile]; - const host = createServerHost(files); - const resolutionTrace = createHostModuleResolutionTrace(host); - const service = createProjectService(host); - service.setCompilerOptionsForInferredProjects({ traceResolution: true }); - service.openClientFile(file1.path); - const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, file2, file2Name); - getExpectedRelativeModuleResolutionTrace(host, file1, file4, file4Name, expectedTrace); - getExpectedRelativeModuleResolutionTrace(host, file1, file3, file3Name, expectedTrace); - getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name, expectedTrace); - getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${projectLocation}/product`, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${projectLocation}/product`, expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); - getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); - verifyTrace(resolutionTrace, expectedTrace); - - const currentDirectory = getDirectoryPath(file1.path); - const watchedFiles = mapDefined(files, f => f === file1 || f.path.indexOf("/node_modules/") !== -1 ? undefined : f.path); - forEachAncestorDirectory(currentDirectory, d => { - watchedFiles.push(combinePaths(d, "tsconfig.json"), combinePaths(d, "jsconfig.json")); - }); - const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([ - `${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${projectLocation}/product/${nodeModules}`, - `${projectLocation}/${nodeModules}`, `${projectLocation}/product/test/${nodeModules}`, - `${projectLocation}/product/test/src/${nodeModules}` - ]); - checkWatches(); - - file1.content += importModuleContent; - file2.content += importModuleContent; - file3.content += importModuleContent; - file4.content += importModuleContent; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - verifyTrace(resolutionTrace, [ - getExpectedReusingResolutionFromOldProgram(file1, file2Name), - getExpectedReusingResolutionFromOldProgram(file1, file4Name), - getExpectedReusingResolutionFromOldProgram(file1, file3Name), - getExpectedReusingResolutionFromOldProgram(file1, module1Name), - getExpectedReusingResolutionFromOldProgram(file1, module2Name) - ]); - checkWatches(); - - function checkWatches() { - checkWatchedFiles(host, watchedFiles); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); - } - }); - }); - - describe("when watching directories for failed lookup locations in amd resolution", () => { - const projectRoot = "/user/username/projects/project"; - const nodeFile: File = { - path: `${projectRoot}/src/typings/node.d.ts`, - content: ` -declare module "fs" { - export interface something { - } -}` - }; - const electronFile: File = { - path: `${projectRoot}/src/typings/electron.d.ts`, - content: ` -declare module 'original-fs' { - import * as fs from 'fs'; - export = fs; -}` - }; - const srcFile: File = { - path: `${projectRoot}/src/somefolder/srcfile.ts`, - content: ` -import { x } from "somefolder/module1"; -import { x } from "somefolder/module2"; -const y = x;` - }; - const moduleFile: File = { - path: `${projectRoot}/src/somefolder/module1.ts`, - content: ` -export const x = 10;` - }; - const configFile: File = { - path: `${projectRoot}/src/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "amd", - moduleResolution: "classic", - target: "es5", - outDir: "../out", - baseUrl: "./", - typeRoots: ["typings"] - } - }) - }; - - function verifyModuleResolution(useNodeFile: boolean) { - const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, projectRoot); - checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); - checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); - if (useNodeFile) { - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup - } - else { - checkWatchedDirectoriesDetailed(host, [`${projectRoot}`, `${projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs - } - const expectedWatchedDirectories = createMap(); - expectedWatchedDirectories.set(`${projectRoot}/src`, 1); // Wild card - expectedWatchedDirectories.set(`${projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 - expectedWatchedDirectories.set(`${projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs - expectedWatchedDirectories.set(`${projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); - } - - it("when resolves to ambient module", () => { - verifyModuleResolution(/*useNodeFile*/ true); - }); - - it("when resolution fails", () => { - verifyModuleResolution(/*useNodeFile*/ false); - }); - }); - - describe("ignores files/folder changes in node_modules that start with '.'", () => { - const projectPath = "/user/username/projects/project"; - const npmCacheFile: File = { - path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, - content: JSON.stringify({ something: 10 }) - }; - const file1: File = { - path: `${projectPath}/test.ts`, - content: `import { x } from "somemodule";` - }; - const file2: File = { - path: `${projectPath}/node_modules/somemodule/index.d.ts`, - content: `export const x = 10;` - }; - it("when watching node_modules in inferred project for failed lookup/closed script infos", () => { - const files = [libFile, file1, file2]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(file1.path); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, files.map(f => f.path)); - (project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1; - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - it("when watching node_modules as part of wild card directories in config project", () => { - const config: File = { - path: `${projectPath}/tsconfig.json`, - content: "{}" - }; - const files = [libFile, file1, file2, config]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(file1.path); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = Debug.assertDefined(service.configuredProjects.get(config.path)); - checkProjectActualFiles(project, files.map(f => f.path)); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - }); - }); - describe("tsserverProjectSystem getEditsForFileRename", () => { it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { const userTs: File = { diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index 34c5fa2a413..2b18af6a71f 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -1,5 +1,4 @@ namespace ts.projectSystem { - import TI = server.typingsInstaller; import validatePackageName = JsTyping.validatePackageName; import PackageNameValidationResult = JsTyping.PackageNameValidationResult; diff --git a/src/testRunner/unittests/watchApi.ts b/src/testRunner/unittests/watchApi.ts new file mode 100644 index 00000000000..60072e24730 --- /dev/null +++ b/src/testRunner/unittests/watchApi.ts @@ -0,0 +1,40 @@ +namespace ts.tscWatch { + describe("watchAPI:: tsc-watch with custom module resolution", () => { + const projectRoot = "/user/username/projects/project"; + const configFileJson: any = { + compilerOptions: { module: "commonjs", resolveJsonModule: true }, + files: ["index.ts"] + }; + const mainFile: File = { + path: `${projectRoot}/index.ts`, + content: "import settings from './settings.json';" + }; + const config: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify(configFileJson) + }; + const settingsJson: File = { + path: `${projectRoot}/settings.json`, + content: JSON.stringify({ content: "Print this" }) + }; + + it("verify that module resolution with json extension works when returned without extension", () => { + const files = [libFile, mainFile, config, settingsJson]; + const host = createWatchedSystem(files, { currentDirectory: projectRoot }); + const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host); + const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path); + compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { + const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost); + const resolvedModule = result.resolvedModule!; + return { + resolvedFileName: resolvedModule.resolvedFileName, + isExternalLibraryImport: resolvedModule.isExternalLibraryImport, + originalFileName: resolvedModule.originalPath, + }; + }); + const watch = createWatchProgram(compilerHost); + const program = watch.getCurrentProgram().getProgram(); + checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]); + }); + }); +} diff --git a/src/testRunner/unittests/watchEnvironment.ts b/src/testRunner/unittests/watchEnvironment.ts index 5b620b54e85..74a6637d6fc 100644 --- a/src/testRunner/unittests/watchEnvironment.ts +++ b/src/testRunner/unittests/watchEnvironment.ts @@ -262,5 +262,55 @@ namespace ts { verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); }); }); + + describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { + function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { + const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; + const configFile: File = { + path: root + "project/tsconfig.json", + content: "{}" + }; + const file1: File = { + path: root + "project/file1.ts", + content: "let x = 10;" + }; + const file2: File = { + path: root + "project/file2.ts", + content: "let y = 10;" + }; + const files = [configFile, file1, file2, libFile]; + const host = createServerHost(files, { useWindowsStylePaths: true }); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); + checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); + checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, [ + root + "project", + root + "project/node_modules/@types" + ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); + } + + function verifyRootedDirectoryWatch(rootedPath: string) { + it("When project is in rootFolder of style c:/", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); + }); + + it("When files at some folder other than root", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); + }); + } + + describe("for rootFolder of style c:/", () => { + verifyRootedDirectoryWatch("c:/"); + }); + + describe("for rootFolder of style c:/users/username", () => { + verifyRootedDirectoryWatch("c:/users/username/"); + }); + }); } } From 347f89c8516f755cc47849f5fa90517d2893707a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 10:52:00 -0800 Subject: [PATCH 077/120] tsc-watch emit tests in to its own tests --- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/tscWatchEmit.ts | 718 +++++++++++++++++++ src/testRunner/unittests/tscWatchHelpers.ts | 66 +- src/testRunner/unittests/tscWatchMode.ts | 729 -------------------- 4 files changed, 720 insertions(+), 794 deletions(-) create mode 100644 src/testRunner/unittests/tscWatchEmit.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 674188eee63..a95bc106fb1 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -86,6 +86,7 @@ "unittests/tsbuild.ts", "unittests/tsbuildWatchMode.ts", "unittests/tsconfigParsing.ts", + "unittests/tscWatchEmit.ts", "unittests/tscWatchMode.ts", "unittests/tsserverProjectSystem.ts", "unittests/typingsInstaller.ts", diff --git a/src/testRunner/unittests/tscWatchEmit.ts b/src/testRunner/unittests/tscWatchEmit.ts new file mode 100644 index 00000000000..e0e9abe516c --- /dev/null +++ b/src/testRunner/unittests/tscWatchEmit.ts @@ -0,0 +1,718 @@ +namespace ts.tscWatch { + function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) { + return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`; + } + + function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) { + return `TSFILE: ${filename}${host.newLine}`; + } + + interface FileOrFolderEmit extends File { + output?: string; + } + + function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit { + const result = file as FileOrFolderEmit; + if (getOutput) { + result.output = getOutput(file); + } + return result; + } + + function getEmittedLines(files: FileOrFolderEmit[]) { + const seen = createMap(); + const result: string[] = []; + for (const { output } of files) { + if (output && !seen.has(output)) { + seen.set(output, true); + result.push(output); + } + } + return result; + } + + function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) { + const expectedAffectedFiles = getEmittedLines(affectedFiles); + const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line); + checkOutputContains(host, expectedAffectedFiles); + checkOutputDoesNotContain(host, expectedNonAffectedFiles); + } + + describe("tsc-watch emit with outFile or out setting", () => { + function createWatchForOut(out?: string, outFile?: string) { + const host = createWatchedSystem([]); + const config: FileOrFolderEmit = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { listEmittedFiles: true } + }) + }; + + let getOutput: (file: File) => string; + if (out) { + config.content = JSON.stringify({ + compilerOptions: { listEmittedFiles: true, out } + }); + getOutput = __ => getEmittedLineForSingleFileOutput(out, host); + } + else if (outFile) { + config.content = JSON.stringify({ + compilerOptions: { listEmittedFiles: true, outFile } + }); + getOutput = __ => getEmittedLineForSingleFileOutput(outFile, host); + } + else { + getOutput = file => getEmittedLineForMultiFileOutput(file, host); + } + + const f1 = getFileOrFolderEmit({ + path: "/a/a.ts", + content: "let x = 1" + }, getOutput); + const f2 = getFileOrFolderEmit({ + path: "/a/b.ts", + content: "let y = 1" + }, getOutput); + + const files = [f1, f2, config, libFile]; + host.reloadFS(files); + createWatchOfConfigFile(config.path, host); + + const allEmittedLines = getEmittedLines(files); + checkOutputContains(host, allEmittedLines); + host.clearOutput(); + + f1.content = "let x = 11"; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkAffectedLines(host, [f1], allEmittedLines); + } + + it("projectUsesOutFile should not be returned if not set", () => { + createWatchForOut(); + }); + + it("projectUsesOutFile should be true if out is set", () => { + const outJs = "/a/out.js"; + createWatchForOut(outJs); + }); + + it("projectUsesOutFile should be true if outFile is set", () => { + const outJs = "/a/out.js"; + createWatchForOut(/*out*/ undefined, outJs); + }); + + function verifyFilesEmittedOnce(useOutFile: boolean) { + const file1: File = { + path: "/a/b/output/AnotherDependency/file1.d.ts", + content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" + }; + const file2: File = { + path: "/a/b/dependencies/file2.d.ts", + content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" + }; + const file3: File = { + path: "/a/b/project/src/main.ts", + content: "namespace Main { export function fooBar() {} }" + }; + const file4: File = { + path: "/a/b/project/src/main2.ts", + content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" + }; + const configFile: File = { + path: "/a/b/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: useOutFile ? + { outFile: "../output/common.js", target: "es5" } : + { outDir: "../output", target: "es5" }, + files: [file1.path, file2.path, file3.path, file4.path] + }) + }; + const files = [file1, file2, file3, file4]; + const allfiles = files.concat(configFile); + const host = createWatchedSystem(allfiles); + const originalWriteFile = host.writeFile.bind(host); + const mapOfFilesWritten = createMap(); + host.writeFile = (p: string, content: string) => { + const count = mapOfFilesWritten.get(p); + mapOfFilesWritten.set(p, count ? count + 1 : 1); + return originalWriteFile(p, content); + }; + createWatchOfConfigFile(configFile.path, host); + if (useOutFile) { + // Only out file + assert.equal(mapOfFilesWritten.size, 1); + } + else { + // main.js and main2.js + assert.equal(mapOfFilesWritten.size, 2); + } + mapOfFilesWritten.forEach((value, key) => { + assert.equal(value, 1, "Key: " + key); + }); + } + + it("with --outFile and multiple declaration files in the program", () => { + verifyFilesEmittedOnce(/*useOutFile*/ true); + }); + + it("without --outFile and multiple declaration files in the program", () => { + verifyFilesEmittedOnce(/*useOutFile*/ false); + }); + }); + + describe("tsc-watch emit for configured projects", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const configFilePath = "/a/b/tsconfig.json"; + interface InitialStateParams { + /** custom config file options */ + configObj?: any; + /** list of the files that will be emitted for first compilation */ + firstCompilationEmitFiles?: string[]; + /** get the emit file for file - default is multi file emit line */ + getEmitLine?(file: File, host: WatchedSystem): string; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): File[]; + /** initial list of files to emit if not the default list */ + firstReloadFileList?: string[]; + } + function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { + const host = createWatchedSystem([]); + const getOutputName = getEmitLine ? (file: File) => getEmitLine(file, host) : + (file: File) => getEmittedLineForMultiFileOutput(file, host); + + const moduleFile1 = getFileOrFolderEmit({ + path: moduleFile1Path, + content: "export function Foo() { };", + }, getOutputName); + + const file1Consumer1 = getFileOrFolderEmit({ + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }, getOutputName); + + const file1Consumer2 = getFileOrFolderEmit({ + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }, getOutputName); + + const moduleFile2 = getFileOrFolderEmit({ + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;`, + }, getOutputName); + + const globalFile3 = getFileOrFolderEmit({ + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }); + + const additionalFiles = getAdditionalFileOrFolder ? + map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) : + []; + + (configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true; + const configFile = getFileOrFolderEmit({ + path: configFilePath, + content: JSON.stringify(configObj) + }); + + const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; + let allEmittedFiles = getEmittedLines(files); + host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); + + // Initial compile + createWatchOfConfigFile(configFile.path, host); + if (firstCompilationEmitFiles) { + checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); + } + else { + checkOutputContains(host, allEmittedFiles); + } + host.clearOutput(); + + return { + moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, + files, + getFile, + verifyAffectedFiles, + verifyAffectedAllFiles, + getOutputName + }; + + function getFiles(filelist: string[]) { + return map(filelist, getFile); + } + + function getFile(fileName: string) { + return find(files, file => file.path === fileName)!; + } + + function verifyAffectedAllFiles() { + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputContains(host, allEmittedFiles); + host.clearOutput(); + } + + function verifyAffectedFiles(expected: FileOrFolderEmit[], filesToReload?: FileOrFolderEmit[]) { + if (!filesToReload) { + filesToReload = files; + } + else if (filesToReload.length > files.length) { + allEmittedFiles = getEmittedLines(filesToReload); + } + host.reloadFS(filesToReload); + host.checkTimeoutQueueLengthAndRun(1); + checkAffectedLines(host, expected, allEmittedFiles); + host.clearOutput(); + } + } + + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + verifyAffectedFiles + } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` + moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; + verifyAffectedFiles([moduleFile1]); + }); + + it("should be up-to-date with the reference map changes", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + verifyAffectedFiles + } = getInitialState(); + + // Change file1Consumer1 content to `export let y = Foo();` + file1Consumer1.content = `export let y = Foo();`; + verifyAffectedFiles([file1Consumer1]); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer2]); + + // Add the import statements back to file1Consumer1 + file1Consumer1.content = `import {Foo} from "./moduleFile1";let y = Foo();`; + verifyAffectedFiles([file1Consumer1]); + + // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` + moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer2, file1Consumer1]); + + // Multiple file edits in one go: + + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + file1Consumer1.content = `export let y = Foo();`; + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); + }); + + it("should be up-to-date with deleted files", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + files, + verifyAffectedFiles + } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + + // Delete file1Consumer2 + const filesToLoad = mapDefined(files, file => file === file1Consumer2 ? undefined : file); + verifyAffectedFiles([moduleFile1, file1Consumer1], filesToLoad); + }); + + it("should be up-to-date with newly created files", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + files, + verifyAffectedFiles, + getOutputName + } = getInitialState(); + + const file1Consumer3 = getFileOrFolderEmit({ + path: "/a/b/file1Consumer3.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }, getOutputName); + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3)); + }); + + it("should detect changes in non-root files", () => { + const { + moduleFile1, file1Consumer1, + verifyAffectedFiles + } = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1]); + + // change file1 internal, and verify only file1 is affected + moduleFile1.content += "var T1: number;"; + verifyAffectedFiles([moduleFile1]); + }); + + it("should return all files if a global file changed shape", () => { + const { + globalFile3, verifyAffectedAllFiles + } = getInitialState(); + + globalFile3.content += "var T2: string;"; + verifyAffectedAllFiles(); + }); + + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); + + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } }, + getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host) + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); + + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: File = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { + moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile + } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] + }); + + const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path); + file1Consumer1.content += "export var T: number;"; + verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]); + + // Doesnt change the shape of file1Consumer1 + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); + + // Change both files before the timeout + file1Consumer1.content += "export var T2: number;"; + moduleFile1.content = `export var T2: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]); + }); + + it("should work fine for files with circular references", () => { + // TODO: do not exit on such errors? Just continue to watch the files for update in watch mode + + const file1: File = { + path: "/a/b/file1.ts", + content: ` + /// + export var t1 = 10;` + }; + const file2: File = { + path: "/a/b/file2.ts", + content: ` + /// + export var t2 = 10;` + }; + const { + configFile, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [file1.path, file2.path], + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath] + }); + const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path); + + file1Emit.content += "export var t3 = 10;"; + verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]); + + }); + + it("should detect removed code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { + configFile, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath] + }); + + const referenceFile1Emit = getFile(referenceFile1.path); + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); + }); + + it("should detect non-existing code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { + configFile, + moduleFile2, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath] + }); + + const referenceFile1Emit = getFile(referenceFile1.path); + referenceFile1Emit.content += "export var yy = Foo();"; + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); + + // Create module File2 and see both files are saved + verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]); + }); + }); + + describe("tsc-watch emit file content", () => { + interface EmittedFile extends File { + shouldBeWritten: boolean; + } + function getEmittedFiles(files: FileOrFolderEmit[], contents: string[]): EmittedFile[] { + return map(contents, (content, index) => { + return { + content, + path: changeExtension(files[index].path, Extension.Js), + shouldBeWritten: true + }; + } + ); + } + function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) { + for (const { path, content, shouldBeWritten } of emittedFiles) { + if (shouldBeWritten) { + assert.isTrue(host.fileExists(path), `Expected file ${path} to be present`); + assert.equal(host.readFile(path), content, `Contents of file ${path} do not match`); + } + else { + assert.isNotTrue(host.fileExists(path), `Expected file ${path} to be absent`); + } + } + } + + function verifyEmittedFileContents(newLine: string, inputFiles: File[], initialEmittedFileContents: string[], + modifyFiles: (files: FileOrFolderEmit[], emitedFiles: EmittedFile[]) => FileOrFolderEmit[], configFile?: File) { + const host = createWatchedSystem([], { newLine }); + const files = concatenate( + map(inputFiles, file => getFileOrFolderEmit(file, fileToConvert => getEmittedLineForMultiFileOutput(fileToConvert, host))), + configFile ? [libFile, configFile] : [libFile] + ); + const allEmittedFiles = getEmittedLines(files); + host.reloadFS(files); + + // Initial compile + if (configFile) { + createWatchOfConfigFile(configFile.path, host); + } + else { + // First file as the root + createWatchOfFilesAndCompilerOptions([files[0].path], host, { listEmittedFiles: true }); + } + checkOutputContains(host, allEmittedFiles); + + const emittedFiles = getEmittedFiles(files, initialEmittedFileContents); + verifyEmittedFiles(host, emittedFiles); + host.clearOutput(); + + const affectedFiles = modifyFiles(files, emittedFiles); + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(1); + checkAffectedLines(host, affectedFiles, allEmittedFiles); + + verifyEmittedFiles(host, emittedFiles); + } + + function verifyNewLine(newLine: string) { + const lines = ["var x = 1;", "var y = 2;"]; + const fileContent = lines.join(newLine); + const f = { + path: "/a/app.ts", + content: fileContent + }; + + verifyEmittedFileContents(newLine, [f], [fileContent + newLine], modifyFiles); + + function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { + files[0].content = fileContent + newLine + "var z = 3;"; + emittedFiles[0].content = files[0].content + newLine; + return [files[0]]; + } + } + + it("handles new lines: \\n", () => { + verifyNewLine("\n"); + }); + + it("handles new lines: \\r\\n", () => { + verifyNewLine("\r\n"); + }); + + it("should emit specified file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export function Foo() { return 10; }` + }; + + const file2 = { + path: "/a/b/f2.ts", + content: `import {Foo} from "./f1"; export let y = Foo();` + }; + + const file3 = { + path: "/a/b/f3.ts", + content: `import {y} from "./f2"; let x = y;` + }; + + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { listEmittedFiles: true } }) + }; + + verifyEmittedFileContents("\r\n", [file1, file2, file3], [ + `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`, + `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`, + `"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n` + ], modifyFiles, configFile); + + function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { + files[0].content += `export function foo2() { return 2; }`; + emittedFiles[0].content += `function foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`; + emittedFiles[2].shouldBeWritten = false; + return files.slice(0, 2); + } + }); + + it("Elides const enums correctly in incremental compilation", () => { + const currentDirectory = "/user/someone/projects/myproject"; + const file1: File = { + path: `${currentDirectory}/file1.ts`, + content: "export const enum E1 { V = 1 }" + }; + const file2: File = { + path: `${currentDirectory}/file2.ts`, + content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` + }; + const file3: File = { + path: `${currentDirectory}/file3.ts`, + content: `import { E2 } from "./file2"; const v: E2 = E2.V;` + }; + const strictAndEsModule = `"use strict";\nexports.__esModule = true;\n`; + verifyEmittedFileContents("\n", [file3, file2, file1], [ + `${strictAndEsModule}var v = 1 /* V */;\n`, + strictAndEsModule, + strictAndEsModule + ], modifyFiles); + + function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { + files[0].content += `function foo2() { return 2; }`; + emittedFiles[0].content += `function foo2() { return 2; }\n`; + emittedFiles[1].shouldBeWritten = false; + emittedFiles[2].shouldBeWritten = false; + return [files[0]]; + } + }); + + it("file is deleted and created as part of change", () => { + const projectLocation = "/home/username/project"; + const file: File = { + path: `${projectLocation}/app/file.ts`, + content: "var a = 10;" + }; + const fileJs = `${projectLocation}/app/file.js`; + const configFile: File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + include: [ + "app/**/*.ts" + ] + }) + }; + const files = [file, configFile, libFile]; + const host = createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); + createWatchOfConfigFile("tsconfig.json", host); + verifyProgram(); + + file.content += "\nvar b = 10;"; + + host.reloadFS(files, { invokeFileDeleteCreateAsPartInsteadOfChange: true }); + host.runQueuedTimeoutCallbacks(); + verifyProgram(); + + function verifyProgram() { + assert.isTrue(host.fileExists(fileJs)); + assert.equal(host.readFile(fileJs), file.content + "\n"); + } + }); + }); + + describe("tsc-watch with when module emit is specified as node", () => { + it("when instead of filechanged recursive directory watcher is invoked", () => { + const configFile: File = { + path: "/a/rootFolder/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + module: "none", + allowJs: true, + outDir: "Static/scripts/" + }, + include: [ + "Scripts/**/*" + ], + }) + }; + const outputFolder = "/a/rootFolder/project/Static/scripts/"; + const file1: File = { + path: "/a/rootFolder/project/Scripts/TypeScript.ts", + content: "var z = 10;" + }; + const file2: File = { + path: "/a/rootFolder/project/Scripts/Javascript.js", + content: "var zz = 10;" + }; + const files = [configFile, file1, file2, libFile]; + const host = createWatchedSystem(files); + const watch = createWatchOfConfigFile(configFile.path, host); + + checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); + file1.content = "var zz30 = 100;"; + host.reloadFS(files, { invokeDirectoryWatcherInsteadOfFileChanged: true }); + host.runQueuedTimeoutCallbacks(); + + checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); + const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js"); + assert.isTrue(host.fileExists(outputFile1)); + assert.equal(host.readFile(outputFile1), file1.content + host.newLine); + }); + }); +} diff --git a/src/testRunner/unittests/tscWatchHelpers.ts b/src/testRunner/unittests/tscWatchHelpers.ts index 6e3fd40c495..f51ecc4e64d 100644 --- a/src/testRunner/unittests/tscWatchHelpers.ts +++ b/src/testRunner/unittests/tscWatchHelpers.ts @@ -41,45 +41,6 @@ namespace ts.tscWatch { return () => watch.getCurrentProgram().getProgram(); } - //function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) { - // return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`; - //} - - //function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) { - // return `TSFILE: ${filename}${host.newLine}`; - //} - - //interface FileOrFolderEmit extends File { - // output?: string; - //} - - //function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit { - // const result = file as FileOrFolderEmit; - // if (getOutput) { - // result.output = getOutput(file); - // } - // return result; - //} - - //function getEmittedLines(files: FileOrFolderEmit[]) { - // const seen = createMap(); - // const result: string[] = []; - // for (const { output } of files) { - // if (output && !seen.has(output)) { - // seen.set(output, true); - // result.push(output); - // } - // } - // return result; - //} - - //function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) { - // const expectedAffectedFiles = getEmittedLines(affectedFiles); - // const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line); - // checkOutputContains(host, expectedAffectedFiles); - // checkOutputDoesNotContain(host, expectedNonAffectedFiles); - //} - const elapsedRegex = /^Elapsed:: [0-9]+ms/; function checkOutputErrors( host: WatchedSystem, @@ -182,7 +143,7 @@ namespace ts.tscWatch { assert.equal(host.exitCode, expectedExitCode); } - function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic { + export function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic { return { file, start, @@ -194,31 +155,6 @@ namespace ts.tscWatch { }; } - //function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { - // let text = getLocaleSpecificMessage(message); - - // if (arguments.length > 1) { - // text = formatStringFromArgs(text, arguments, 1); - // } - - // return getDiagnosticOfFileFrom(/*file*/ undefined, text, /*start*/ undefined, /*length*/ undefined, message); - //} - - //function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { - // let text = getLocaleSpecificMessage(message); - - // if (arguments.length > 4) { - // text = formatStringFromArgs(text, arguments, 4); - // } - - // return getDiagnosticOfFileFrom(file, text, start, length, message); - //} - - //function getUnknownCompilerOption(program: Program, configFile: File, option: string) { - // const quotedOption = `"${option}"`; - // return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option); - //} - export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { let text = getLocaleSpecificMessage(message); diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index 662f26e8b13..29726b12424 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -1,55 +1,4 @@ namespace ts.tscWatch { - function getEmittedLineForMultiFileOutput(file: File, host: WatchedSystem) { - return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`; - } - - function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) { - return `TSFILE: ${filename}${host.newLine}`; - } - - interface FileOrFolderEmit extends File { - output?: string; - } - - function getFileOrFolderEmit(file: File, getOutput?: (file: File) => string): FileOrFolderEmit { - const result = file as FileOrFolderEmit; - if (getOutput) { - result.output = getOutput(file); - } - return result; - } - - function getEmittedLines(files: FileOrFolderEmit[]) { - const seen = createMap(); - const result: string[] = []; - for (const { output } of files) { - if (output && !seen.has(output)) { - seen.set(output, true); - result.push(output); - } - } - return result; - } - - function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) { - const expectedAffectedFiles = getEmittedLines(affectedFiles); - const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line); - checkOutputContains(host, expectedAffectedFiles); - checkOutputDoesNotContain(host, expectedNonAffectedFiles); - } - - function getDiagnosticOfFileFrom(file: SourceFile | undefined, text: string, start: number | undefined, length: number | undefined, message: DiagnosticMessage): Diagnostic { - return { - file, - start, - length, - - messageText: text, - category: message.category, - code: message.code, - }; - } - function getDiagnosticWithoutFile(message: DiagnosticMessage, ..._args: (string | number)[]): Diagnostic { let text = getLocaleSpecificMessage(message); @@ -1546,684 +1495,6 @@ interface Document { }); }); - describe("tsc-watch emit with outFile or out setting", () => { - function createWatchForOut(out?: string, outFile?: string) { - const host = createWatchedSystem([]); - const config: FileOrFolderEmit = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { listEmittedFiles: true } - }) - }; - - let getOutput: (file: File) => string; - if (out) { - config.content = JSON.stringify({ - compilerOptions: { listEmittedFiles: true, out } - }); - getOutput = __ => getEmittedLineForSingleFileOutput(out, host); - } - else if (outFile) { - config.content = JSON.stringify({ - compilerOptions: { listEmittedFiles: true, outFile } - }); - getOutput = __ => getEmittedLineForSingleFileOutput(outFile, host); - } - else { - getOutput = file => getEmittedLineForMultiFileOutput(file, host); - } - - const f1 = getFileOrFolderEmit({ - path: "/a/a.ts", - content: "let x = 1" - }, getOutput); - const f2 = getFileOrFolderEmit({ - path: "/a/b.ts", - content: "let y = 1" - }, getOutput); - - const files = [f1, f2, config, libFile]; - host.reloadFS(files); - createWatchOfConfigFile(config.path, host); - - const allEmittedLines = getEmittedLines(files); - checkOutputContains(host, allEmittedLines); - host.clearOutput(); - - f1.content = "let x = 11"; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkAffectedLines(host, [f1], allEmittedLines); - } - - it("projectUsesOutFile should not be returned if not set", () => { - createWatchForOut(); - }); - - it("projectUsesOutFile should be true if out is set", () => { - const outJs = "/a/out.js"; - createWatchForOut(outJs); - }); - - it("projectUsesOutFile should be true if outFile is set", () => { - const outJs = "/a/out.js"; - createWatchForOut(/*out*/ undefined, outJs); - }); - - function verifyFilesEmittedOnce(useOutFile: boolean) { - const file1: File = { - path: "/a/b/output/AnotherDependency/file1.d.ts", - content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" - }; - const file2: File = { - path: "/a/b/dependencies/file2.d.ts", - content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" - }; - const file3: File = { - path: "/a/b/project/src/main.ts", - content: "namespace Main { export function fooBar() {} }" - }; - const file4: File = { - path: "/a/b/project/src/main2.ts", - content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" - }; - const configFile: File = { - path: "/a/b/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: useOutFile ? - { outFile: "../output/common.js", target: "es5" } : - { outDir: "../output", target: "es5" }, - files: [file1.path, file2.path, file3.path, file4.path] - }) - }; - const files = [file1, file2, file3, file4]; - const allfiles = files.concat(configFile); - const host = createWatchedSystem(allfiles); - const originalWriteFile = host.writeFile.bind(host); - const mapOfFilesWritten = createMap(); - host.writeFile = (p: string, content: string) => { - const count = mapOfFilesWritten.get(p); - mapOfFilesWritten.set(p, count ? count + 1 : 1); - return originalWriteFile(p, content); - }; - createWatchOfConfigFile(configFile.path, host); - if (useOutFile) { - // Only out file - assert.equal(mapOfFilesWritten.size, 1); - } - else { - // main.js and main2.js - assert.equal(mapOfFilesWritten.size, 2); - } - mapOfFilesWritten.forEach((value, key) => { - assert.equal(value, 1, "Key: " + key); - }); - } - - it("with --outFile and multiple declaration files in the program", () => { - verifyFilesEmittedOnce(/*useOutFile*/ true); - }); - - it("without --outFile and multiple declaration files in the program", () => { - verifyFilesEmittedOnce(/*useOutFile*/ false); - }); - }); - - describe("tsc-watch emit for configured projects", () => { - const file1Consumer1Path = "/a/b/file1Consumer1.ts"; - const moduleFile1Path = "/a/b/moduleFile1.ts"; - const configFilePath = "/a/b/tsconfig.json"; - interface InitialStateParams { - /** custom config file options */ - configObj?: any; - /** list of the files that will be emitted for first compilation */ - firstCompilationEmitFiles?: string[]; - /** get the emit file for file - default is multi file emit line */ - getEmitLine?(file: File, host: WatchedSystem): string; - /** Additional files and folders to add */ - getAdditionalFileOrFolder?(): File[]; - /** initial list of files to emit if not the default list */ - firstReloadFileList?: string[]; - } - function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { - const host = createWatchedSystem([]); - const getOutputName = getEmitLine ? (file: File) => getEmitLine(file, host) : - (file: File) => getEmittedLineForMultiFileOutput(file, host); - - const moduleFile1 = getFileOrFolderEmit({ - path: moduleFile1Path, - content: "export function Foo() { };", - }, getOutputName); - - const file1Consumer1 = getFileOrFolderEmit({ - path: file1Consumer1Path, - content: `import {Foo} from "./moduleFile1"; export var y = 10;`, - }, getOutputName); - - const file1Consumer2 = getFileOrFolderEmit({ - path: "/a/b/file1Consumer2.ts", - content: `import {Foo} from "./moduleFile1"; let z = 10;`, - }, getOutputName); - - const moduleFile2 = getFileOrFolderEmit({ - path: "/a/b/moduleFile2.ts", - content: `export var Foo4 = 10;`, - }, getOutputName); - - const globalFile3 = getFileOrFolderEmit({ - path: "/a/b/globalFile3.ts", - content: `interface GlobalFoo { age: number }` - }); - - const additionalFiles = getAdditionalFileOrFolder ? - map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) : - []; - - (configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true; - const configFile = getFileOrFolderEmit({ - path: configFilePath, - content: JSON.stringify(configObj) - }); - - const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; - let allEmittedFiles = getEmittedLines(files); - host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); - - // Initial compile - createWatchOfConfigFile(configFile.path, host); - if (firstCompilationEmitFiles) { - checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); - } - else { - checkOutputContains(host, allEmittedFiles); - } - host.clearOutput(); - - return { - moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, - files, - getFile, - verifyAffectedFiles, - verifyAffectedAllFiles, - getOutputName - }; - - function getFiles(filelist: string[]) { - return map(filelist, getFile); - } - - function getFile(fileName: string) { - return find(files, file => file.path === fileName)!; - } - - function verifyAffectedAllFiles() { - host.reloadFS(files); - host.checkTimeoutQueueLengthAndRun(1); - checkOutputContains(host, allEmittedFiles); - host.clearOutput(); - } - - function verifyAffectedFiles(expected: FileOrFolderEmit[], filesToReload?: FileOrFolderEmit[]) { - if (!filesToReload) { - filesToReload = files; - } - else if (filesToReload.length > files.length) { - allEmittedFiles = getEmittedLines(filesToReload); - } - host.reloadFS(filesToReload); - host.checkTimeoutQueueLengthAndRun(1); - checkAffectedLines(host, expected, allEmittedFiles); - host.clearOutput(); - } - } - - it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const { - moduleFile1, file1Consumer1, file1Consumer2, - verifyAffectedFiles - } = getInitialState(); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` - moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; - verifyAffectedFiles([moduleFile1]); - }); - - it("should be up-to-date with the reference map changes", () => { - const { - moduleFile1, file1Consumer1, file1Consumer2, - verifyAffectedFiles - } = getInitialState(); - - // Change file1Consumer1 content to `export let y = Foo();` - file1Consumer1.content = `export let y = Foo();`; - verifyAffectedFiles([file1Consumer1]); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer2]); - - // Add the import statements back to file1Consumer1 - file1Consumer1.content = `import {Foo} from "./moduleFile1";let y = Foo();`; - verifyAffectedFiles([file1Consumer1]); - - // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` - moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer2, file1Consumer1]); - - // Multiple file edits in one go: - - // Change file1Consumer1 content to `export let y = Foo();` - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - file1Consumer1.content = `export let y = Foo();`; - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); - }); - - it("should be up-to-date with deleted files", () => { - const { - moduleFile1, file1Consumer1, file1Consumer2, - files, - verifyAffectedFiles - } = getInitialState(); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - moduleFile1.content = `export var T: number;export function Foo() { };`; - - // Delete file1Consumer2 - const filesToLoad = mapDefined(files, file => file === file1Consumer2 ? undefined : file); - verifyAffectedFiles([moduleFile1, file1Consumer1], filesToLoad); - }); - - it("should be up-to-date with newly created files", () => { - const { - moduleFile1, file1Consumer1, file1Consumer2, - files, - verifyAffectedFiles, - getOutputName - } = getInitialState(); - - const file1Consumer3 = getFileOrFolderEmit({ - path: "/a/b/file1Consumer3.ts", - content: `import {Foo} from "./moduleFile1"; let y = Foo();` - }, getOutputName); - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3)); - }); - - it("should detect changes in non-root files", () => { - const { - moduleFile1, file1Consumer1, - verifyAffectedFiles - } = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] }); - - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer1]); - - // change file1 internal, and verify only file1 is affected - moduleFile1.content += "var T1: number;"; - verifyAffectedFiles([moduleFile1]); - }); - - it("should return all files if a global file changed shape", () => { - const { - globalFile3, verifyAffectedAllFiles - } = getInitialState(); - - globalFile3.content += "var T2: string;"; - verifyAffectedAllFiles(); - }); - - it("should always return the file itself if '--isolatedModules' is specified", () => { - const { - moduleFile1, verifyAffectedFiles - } = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } }); - - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1]); - }); - - it("should always return the file itself if '--out' or '--outFile' is specified", () => { - const outFilePath = "/a/b/out.js"; - const { - moduleFile1, verifyAffectedFiles - } = getInitialState({ - configObj: { compilerOptions: { module: "system", outFile: outFilePath } }, - getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host) - }); - - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1]); - }); - - it("should return cascaded affected file list", () => { - const file1Consumer1Consumer1: File = { - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }; - const { - moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile - } = getInitialState({ - getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] - }); - - const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path); - file1Consumer1.content += "export var T: number;"; - verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]); - - // Doesnt change the shape of file1Consumer1 - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); - - // Change both files before the timeout - file1Consumer1.content += "export var T2: number;"; - moduleFile1.content = `export var T2: number;export function Foo() { };`; - verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]); - }); - - it("should work fine for files with circular references", () => { - // TODO: do not exit on such errors? Just continue to watch the files for update in watch mode - - const file1: File = { - path: "/a/b/file1.ts", - content: ` - /// - export var t1 = 10;` - }; - const file2: File = { - path: "/a/b/file2.ts", - content: ` - /// - export var t2 = 10;` - }; - const { - configFile, - getFile, - verifyAffectedFiles - } = getInitialState({ - firstCompilationEmitFiles: [file1.path, file2.path], - getAdditionalFileOrFolder: () => [file1, file2], - firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath] - }); - const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path); - - file1Emit.content += "export var t3 = 10;"; - verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]); - - }); - - it("should detect removed code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` - /// - export var x = Foo();` - }; - const { - configFile, - getFile, - verifyAffectedFiles - } = getInitialState({ - firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path], - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath] - }); - - const referenceFile1Emit = getFile(referenceFile1.path); - verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); - }); - - it("should detect non-existing code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` - /// - export var x = Foo();` - }; - const { - configFile, - moduleFile2, - getFile, - verifyAffectedFiles - } = getInitialState({ - firstCompilationEmitFiles: [referenceFile1.path], - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath] - }); - - const referenceFile1Emit = getFile(referenceFile1.path); - referenceFile1Emit.content += "export var yy = Foo();"; - verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); - - // Create module File2 and see both files are saved - verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]); - }); - }); - - describe("tsc-watch emit file content", () => { - interface EmittedFile extends File { - shouldBeWritten: boolean; - } - function getEmittedFiles(files: FileOrFolderEmit[], contents: string[]): EmittedFile[] { - return map(contents, (content, index) => { - return { - content, - path: changeExtension(files[index].path, Extension.Js), - shouldBeWritten: true - }; - } - ); - } - function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) { - for (const { path, content, shouldBeWritten } of emittedFiles) { - if (shouldBeWritten) { - assert.isTrue(host.fileExists(path), `Expected file ${path} to be present`); - assert.equal(host.readFile(path), content, `Contents of file ${path} do not match`); - } - else { - assert.isNotTrue(host.fileExists(path), `Expected file ${path} to be absent`); - } - } - } - - function verifyEmittedFileContents(newLine: string, inputFiles: File[], initialEmittedFileContents: string[], - modifyFiles: (files: FileOrFolderEmit[], emitedFiles: EmittedFile[]) => FileOrFolderEmit[], configFile?: File) { - const host = createWatchedSystem([], { newLine }); - const files = concatenate( - map(inputFiles, file => getFileOrFolderEmit(file, fileToConvert => getEmittedLineForMultiFileOutput(fileToConvert, host))), - configFile ? [libFile, configFile] : [libFile] - ); - const allEmittedFiles = getEmittedLines(files); - host.reloadFS(files); - - // Initial compile - if (configFile) { - createWatchOfConfigFile(configFile.path, host); - } - else { - // First file as the root - createWatchOfFilesAndCompilerOptions([files[0].path], host, { listEmittedFiles: true }); - } - checkOutputContains(host, allEmittedFiles); - - const emittedFiles = getEmittedFiles(files, initialEmittedFileContents); - verifyEmittedFiles(host, emittedFiles); - host.clearOutput(); - - const affectedFiles = modifyFiles(files, emittedFiles); - host.reloadFS(files); - host.checkTimeoutQueueLengthAndRun(1); - checkAffectedLines(host, affectedFiles, allEmittedFiles); - - verifyEmittedFiles(host, emittedFiles); - } - - function verifyNewLine(newLine: string) { - const lines = ["var x = 1;", "var y = 2;"]; - const fileContent = lines.join(newLine); - const f = { - path: "/a/app.ts", - content: fileContent - }; - - verifyEmittedFileContents(newLine, [f], [fileContent + newLine], modifyFiles); - - function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { - files[0].content = fileContent + newLine + "var z = 3;"; - emittedFiles[0].content = files[0].content + newLine; - return [files[0]]; - } - } - - it("handles new lines: \\n", () => { - verifyNewLine("\n"); - }); - - it("handles new lines: \\r\\n", () => { - verifyNewLine("\r\n"); - }); - - it("should emit specified file", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export function Foo() { return 10; }` - }; - - const file2 = { - path: "/a/b/f2.ts", - content: `import {Foo} from "./f1"; export let y = Foo();` - }; - - const file3 = { - path: "/a/b/f3.ts", - content: `import {y} from "./f2"; let x = y;` - }; - - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { listEmittedFiles: true } }) - }; - - verifyEmittedFileContents("\r\n", [file1, file2, file3], [ - `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`, - `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`, - `"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n` - ], modifyFiles, configFile); - - function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { - files[0].content += `export function foo2() { return 2; }`; - emittedFiles[0].content += `function foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`; - emittedFiles[2].shouldBeWritten = false; - return files.slice(0, 2); - } - }); - - it("Elides const enums correctly in incremental compilation", () => { - const currentDirectory = "/user/someone/projects/myproject"; - const file1: File = { - path: `${currentDirectory}/file1.ts`, - content: "export const enum E1 { V = 1 }" - }; - const file2: File = { - path: `${currentDirectory}/file2.ts`, - content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` - }; - const file3: File = { - path: `${currentDirectory}/file3.ts`, - content: `import { E2 } from "./file2"; const v: E2 = E2.V;` - }; - const strictAndEsModule = `"use strict";\nexports.__esModule = true;\n`; - verifyEmittedFileContents("\n", [file3, file2, file1], [ - `${strictAndEsModule}var v = 1 /* V */;\n`, - strictAndEsModule, - strictAndEsModule - ], modifyFiles); - - function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { - files[0].content += `function foo2() { return 2; }`; - emittedFiles[0].content += `function foo2() { return 2; }\n`; - emittedFiles[1].shouldBeWritten = false; - emittedFiles[2].shouldBeWritten = false; - return [files[0]]; - } - }); - - it("file is deleted and created as part of change", () => { - const projectLocation = "/home/username/project"; - const file: File = { - path: `${projectLocation}/app/file.ts`, - content: "var a = 10;" - }; - const fileJs = `${projectLocation}/app/file.js`; - const configFile: File = { - path: `${projectLocation}/tsconfig.json`, - content: JSON.stringify({ - include: [ - "app/**/*.ts" - ] - }) - }; - const files = [file, configFile, libFile]; - const host = createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); - createWatchOfConfigFile("tsconfig.json", host); - verifyProgram(); - - file.content += "\nvar b = 10;"; - - host.reloadFS(files, { invokeFileDeleteCreateAsPartInsteadOfChange: true }); - host.runQueuedTimeoutCallbacks(); - verifyProgram(); - - function verifyProgram() { - assert.isTrue(host.fileExists(fileJs)); - assert.equal(host.readFile(fileJs), file.content + "\n"); - } - }); - }); - - describe("tsc-watch with when module emit is specified as node", () => { - it("when instead of filechanged recursive directory watcher is invoked", () => { - const configFile: File = { - path: "/a/rootFolder/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - module: "none", - allowJs: true, - outDir: "Static/scripts/" - }, - include: [ - "Scripts/**/*" - ], - }) - }; - const outputFolder = "/a/rootFolder/project/Static/scripts/"; - const file1: File = { - path: "/a/rootFolder/project/Scripts/TypeScript.ts", - content: "var z = 10;" - }; - const file2: File = { - path: "/a/rootFolder/project/Scripts/Javascript.js", - content: "var zz = 10;" - }; - const files = [configFile, file1, file2, libFile]; - const host = createWatchedSystem(files); - const watch = createWatchOfConfigFile(configFile.path, host); - - checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); - file1.content = "var zz30 = 100;"; - host.reloadFS(files, { invokeDirectoryWatcherInsteadOfFileChanged: true }); - host.runQueuedTimeoutCallbacks(); - - checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); - const outputFile1 = changeExtension((outputFolder + getBaseFileName(file1.path)), ".js"); - assert.isTrue(host.fileExists(outputFile1)); - assert.equal(host.readFile(outputFile1), file1.content + host.newLine); - }); - }); - describe("tsc-watch console clearing", () => { const currentDirectoryLog = "Current directory: / CaseSensitiveFileNames: false\n"; const fileWatcherAddedLog = [ From 53a6968f06a1944a6519a378c06b8c30ae015bd6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:14:10 -0800 Subject: [PATCH 078/120] More refactoring for compile on save --- src/testRunner/unittests/compileOnSave.ts | 49 +++++++++++++++++++ .../unittests/tsserverProjectSystem.ts | 48 ------------------ 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/testRunner/unittests/compileOnSave.ts b/src/testRunner/unittests/compileOnSave.ts index 07a0aa5d3ae..2352ac58c19 100644 --- a/src/testRunner/unittests/compileOnSave.ts +++ b/src/testRunner/unittests/compileOnSave.ts @@ -502,6 +502,54 @@ namespace ts.projectSystem { ]); }); }); + + describe("tsserverProjectSystem emit with outFile or out setting", () => { + function test(opts: CompilerOptions, expectedUsesOutFile: boolean) { + const f1 = { + path: "/a/a.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/b.ts", + content: "let y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: opts, + compileOnSave: true + }) + }; + const host = createServerHost([f1, f2, config]); + const session = projectSystem.createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "open", + arguments: { file: f1.path } + }); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + const { response } = session.executeCommand({ + seq: 2, + type: "request", + command: "compileOnSaveAffectedFileList", + arguments: { file: f1.path } + }); + assert.equal((response).length, 1, "expected output for 1 project"); + assert.equal((response)[0].fileNames.length, 2, "expected output for 1 project"); + assert.equal((response)[0].projectUsesOutFile, expectedUsesOutFile, "usesOutFile"); + } + + it("projectUsesOutFile should not be returned if not set", () => { + test({}, /*expectedUsesOutFile*/ false); + }); + it("projectUsesOutFile should be true if outFile is set", () => { + test({ outFile: "/a/out.js" }, /*expectedUsesOutFile*/ true); + }); + it("projectUsesOutFile should be true if out is set", () => { + test({ out: "/a/out.js" }, /*expectedUsesOutFile*/ true); + }); + }); }); describe("compileOnSave:: EmitFile test", () => { @@ -646,4 +694,5 @@ namespace ts.projectSystem { } }); }); + } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 3b7c276cfc4..122d9459e75 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -5607,54 +5607,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem emit with outFile or out setting", () => { - function test(opts: CompilerOptions, expectedUsesOutFile: boolean) { - const f1 = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/b.ts", - content: "let y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: opts, - compileOnSave: true - }) - }; - const host = createServerHost([f1, f2, config]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "open", - arguments: { file: f1.path } - }); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); - const { response } = session.executeCommand({ - seq: 2, - type: "request", - command: "compileOnSaveAffectedFileList", - arguments: { file: f1.path } - }); - assert.equal((response).length, 1, "expected output for 1 project"); - assert.equal((response)[0].fileNames.length, 2, "expected output for 1 project"); - assert.equal((response)[0].projectUsesOutFile, expectedUsesOutFile, "usesOutFile"); - } - - it("projectUsesOutFile should not be returned if not set", () => { - test({}, /*expectedUsesOutFile*/ false); - }); - it("projectUsesOutFile should be true if outFile is set", () => { - test({ outFile: "/a/out.js" }, /*expectedUsesOutFile*/ true); - }); - it("projectUsesOutFile should be true if out is set", () => { - test({ out: "/a/out.js" }, /*expectedUsesOutFile*/ true); - }); - }); - describe("tsserverProjectSystem import helpers", () => { it("should not crash in tsserver", () => { const f1 = { From 1b6db32ecd07121063d9ee4eafa2a1795c7dd070 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:22:14 -0800 Subject: [PATCH 079/120] Move error tests from tsserver project system to projectErrors --- src/testRunner/unittests/projectErrors.ts | 281 ++++++++++++++++ src/testRunner/unittests/tsserverHelpers.ts | 44 +-- .../unittests/tsserverProjectSystem.ts | 310 ------------------ 3 files changed, 303 insertions(+), 332 deletions(-) diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/projectErrors.ts index d243e9d5ea4..6eb364b569d 100644 --- a/src/testRunner/unittests/projectErrors.ts +++ b/src/testRunner/unittests/projectErrors.ts @@ -199,4 +199,285 @@ namespace ts.projectSystem { } }); }); + + describe("tsserver:: Project Errors are reported as appropriate", () => { + function createErrorLogger() { + let hasError = false; + const errorLogger: server.Logger = { + close: noop, + hasLevel: () => true, + loggingEnabled: () => true, + perftrc: noop, + info: noop, + msg: (_s, type) => { + if (type === server.Msg.Err) { + hasError = true; + } + }, + startGroup: noop, + endGroup: noop, + getLogFileName: () => undefined + }; + return { + errorLogger, + hasError: () => hasError + }; + } + + it("document is not contained in project", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: "/a/b/tsconfig.json", + content: "{" + }; + const host = createServerHost([file1, corruptedConfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + + const project = projectService.findProject(corruptedConfig.path)!; + checkProjectRootFiles(project, [file1.path]); + }); + + describe("when opening new file that doesnt exist on disk yet", () => { + function verifyNonExistentFile(useProjectRoot: boolean) { + const folderPath = "/user/someuser/projects/someFolder"; + const fileInRoot: File = { + path: `/src/somefile.d.ts`, + content: "class c { }" + }; + const fileInProjectRoot: File = { + path: `${folderPath}/src/somefile.d.ts`, + content: "class c { }" + }; + const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); + const { hasError, errorLogger } = createErrorLogger(); + const session = createSession(host, { canUseEvents: true, logger: errorLogger, useInferredProjectPerProjectRoot: true }); + + const projectService = session.getProjectService(); + const untitledFile = "untitled:Untitled-1"; + const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; + const refPathNotFound2 = "./src/somefile.d.ts"; + const fileContent = `/// +/// `; + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: untitledFile, + fileContent, + scriptKindName: "TS", + projectRootPath: useProjectRoot ? folderPath : undefined + } + }); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const infoForUntitledAtProjectRoot = projectService.getScriptInfoForPath(`${folderPath.toLowerCase()}/${untitledFile.toLowerCase()}` as Path); + const infoForUnitiledAtRoot = projectService.getScriptInfoForPath(`/${untitledFile.toLowerCase()}` as Path); + const infoForSomefileAtProjectRoot = projectService.getScriptInfoForPath(`/${folderPath.toLowerCase()}/src/somefile.d.ts` as Path); + const infoForSomefileAtRoot = projectService.getScriptInfoForPath(`${fileInRoot.path.toLowerCase()}` as Path); + if (useProjectRoot) { + assert.isDefined(infoForUntitledAtProjectRoot); + assert.isUndefined(infoForUnitiledAtRoot); + } + else { + assert.isDefined(infoForUnitiledAtRoot); + assert.isUndefined(infoForUntitledAtProjectRoot); + } + assert.isUndefined(infoForSomefileAtRoot); + assert.isUndefined(infoForSomefileAtProjectRoot); + + // Since this is not js project so no typings are queued + host.checkTimeoutQueueLength(0); + + const newTimeoutId = host.getNextTimeoutId(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [untitledFile] + } + }); + host.checkTimeoutQueueLength(1); + + // Run the last one = get error request + host.runQueuedTimeoutCallbacks(newTimeoutId); + + assert.isFalse(hasError()); + host.checkTimeoutQueueLength(0); + checkErrorMessage(session, "syntaxDiag", { file: untitledFile, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + assert.isFalse(hasError()); + const errorOffset = fileContent.indexOf(refPathNotFound1) + 1; + checkErrorMessage(session, "semanticDiag", { + file: untitledFile, + diagnostics: [ + createDiagnostic({ line: 1, offset: errorOffset }, { line: 1, offset: errorOffset + refPathNotFound1.length }, Diagnostics.File_0_not_found, [refPathNotFound1], "error"), + createDiagnostic({ line: 2, offset: errorOffset }, { line: 2, offset: errorOffset + refPathNotFound2.length }, Diagnostics.File_0_not_found, [refPathNotFound2.substr(2)], "error") + ] + }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + assert.isFalse(hasError()); + checkErrorMessage(session, "suggestionDiag", { file: untitledFile, diagnostics: [] }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + } + + it("has projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ true); + }); + + it("does not have projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ false); + }); + }); + + it("folder rename updates project structure and reports no errors", () => { + const projectDir = "/a/b/projects/myproject"; + const app: File = { + path: `${projectDir}/bar/app.ts`, + content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }" + }; + const foo: File = { + path: `${projectDir}/foo/foo.ts`, + content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }" + }; + const configFile: File = { + path: `${projectDir}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }) + }; + const host = createServerHost([app, foo, configFile]); + const session = createSession(host, { canUseEvents: true, }); + const projectService = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: app.path, } + }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isDefined(projectService.configuredProjects.get(configFile.path)); + verifyErrorsInApp(); + + host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`); + host.runQueuedTimeoutCallbacks(); + host.runQueuedTimeoutCallbacks(); + verifyErrorsInApp(); + + function verifyErrorsInApp() { + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [app.path] + } + }); + host.checkTimeoutQueueLengthAndRun(1); + checkErrorMessage(session, "syntaxDiag", { file: app.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + checkErrorMessage(session, "semanticDiag", { file: app.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + checkErrorMessage(session, "suggestionDiag", { file: app.path, diagnostics: [] }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + } + }); + + it("Getting errors before opening file", () => { + const file: File = { + path: "/a/b/project/file.ts", + content: "let x: number = false;" + }; + const host = createServerHost([file, libFile]); + const { hasError, errorLogger } = createErrorLogger(); + const session = createSession(host, { canUseEvents: true, logger: errorLogger }); + + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path] + } + }); + + host.runQueuedImmediateCallbacks(); + assert.isFalse(hasError()); + checkCompleteEvent(session, 1, expectedSequenceId); + session.clearMessages(); + }); + + it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { + const projectRoot = "/user/username/projects/myproject"; + const app: File = { + path: `${projectRoot}/src/client/app.js`, + content: "" + }; + const serverUtilities: File = { + path: `${projectRoot}/src/server/utilities.js`, + content: `function getHostName() { return "hello"; } export { getHostName };` + }; + const backendTest: File = { + path: `${projectRoot}/test/backend/index.js`, + content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` + }; + const files = [libFile, app, serverUtilities, backendTest]; + const host = createServerHost(files); + const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true }); + openFilesForSession([{ file: app, projectRootPath: projectRoot }], session); + const service = session.getProjectService(); + checkNumberOfProjects(service, { inferredProjects: 1 }); + const project = service.inferredProjects[0]; + checkProjectActualFiles(project, [libFile.path, app.path]); + openFilesForSession([{ file: backendTest, projectRootPath: projectRoot }], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(project, files.map(f => f.path)); + checkErrors([backendTest.path, app.path]); + closeFilesForSession([backendTest], session); + openFilesForSession([{ file: serverUtilities.path, projectRootPath: projectRoot }], session); + checkErrors([serverUtilities.path, app.path]); + + function checkErrors(openFiles: [string, string]) { + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: protocol.CommandTypes.Geterr, + arguments: { + delay: 0, + files: openFiles + } + }); + + for (const openFile of openFiles) { + session.clearMessages(); + host.checkTimeoutQueueLength(3); + host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1); + + checkErrorMessage(session, "syntaxDiag", { file: openFile, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + checkErrorMessage(session, "semanticDiag", { file: openFile, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + checkErrorMessage(session, "suggestionDiag", { file: openFile, diagnostics: [] }); + } + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + } + }); + }); } diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserverHelpers.ts index 5162f17d6ae..f5113b768fc 100644 --- a/src/testRunner/unittests/tsserverHelpers.ts +++ b/src/testRunner/unittests/tsserverHelpers.ts @@ -430,9 +430,9 @@ namespace ts.projectSystem { checkArray(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles); } - //function checkProjectRootFiles(project: server.Project, expectedFiles: ReadonlyArray) { - // checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); - //} + export function checkProjectRootFiles(project: server.Project, expectedFiles: ReadonlyArray) { + checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); + } export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { dir = normalizePath(dir); @@ -613,17 +613,17 @@ namespace ts.projectSystem { // verifyDiagnostics(actual, []); //} - //function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void { - // checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent); - //} + export function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void { + checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent); + } - //function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic { - // return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined }; - //} + export function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic { + return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined }; + } - //function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void { - // checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent); - //} + export function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void { + checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent); + } //function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { // checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); @@ -635,18 +635,18 @@ namespace ts.projectSystem { // } //} - //function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { - // const events = session.events; - // assert.deepEqual(events[index], expectedEvent, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`); + export function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { + const events = session.events; + assert.deepEqual(events[index], expectedEvent, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`); - // const outputs = session.host.getOutput(); - // assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine)); + const outputs = session.host.getOutput(); + assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine)); - // if (isMostRecent) { - // assert.strictEqual(events.length, index + 1, JSON.stringify(events)); - // assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); - // } - //} + if (isMostRecent) { + assert.strictEqual(events.length, index + 1, JSON.stringify(events)); + assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); + } + } //function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { // return { diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 122d9459e75..3ccb7b2ffa0 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -41,10 +41,6 @@ namespace ts.projectSystem { } } - function checkProjectRootFiles(project: server.Project, expectedFiles: ReadonlyArray) { - checkArray(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); - } - function getNodeModuleDirectories(dir: string) { return getRootsToWatchWithAncestorDirectory(dir, nodeModules); } @@ -134,18 +130,6 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } - function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void { - checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent); - } - - function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic { - return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined }; - } - - function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void { - checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent); - } - function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); } @@ -156,19 +140,6 @@ namespace ts.projectSystem { } } - function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { - const events = session.events; - assert.deepEqual(events[index], expectedEvent, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`); - - const outputs = session.host.getOutput(); - assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine)); - - if (isMostRecent) { - assert.strictEqual(events.length, index + 1, JSON.stringify(events)); - assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); - } - } - describe("tsserverProjectSystem general functionality", () => { const commonFile1: File = { path: "/a/b/commonFile1.ts", @@ -3377,287 +3348,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem Proper errors", () => { - function createErrorLogger() { - let hasError = false; - const errorLogger: server.Logger = { - close: noop, - hasLevel: () => true, - loggingEnabled: () => true, - perftrc: noop, - info: noop, - msg: (_s, type) => { - if (type === server.Msg.Err) { - hasError = true; - } - }, - startGroup: noop, - endGroup: noop, - getLogFileName: () => undefined - }; - return { - errorLogger, - hasError: () => hasError - }; - } - - it("document is not contained in project", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const corruptedConfig = { - path: "/a/b/tsconfig.json", - content: "{" - }; - const host = createServerHost([file1, corruptedConfig]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - - const project = projectService.findProject(corruptedConfig.path)!; - checkProjectRootFiles(project, [file1.path]); - }); - - describe("when opening new file that doesnt exist on disk yet", () => { - function verifyNonExistentFile(useProjectRoot: boolean) { - const folderPath = "/user/someuser/projects/someFolder"; - const fileInRoot: File = { - path: `/src/somefile.d.ts`, - content: "class c { }" - }; - const fileInProjectRoot: File = { - path: `${folderPath}/src/somefile.d.ts`, - content: "class c { }" - }; - const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); - const { hasError, errorLogger } = createErrorLogger(); - const session = createSession(host, { canUseEvents: true, logger: errorLogger, useInferredProjectPerProjectRoot: true }); - - const projectService = session.getProjectService(); - const untitledFile = "untitled:Untitled-1"; - const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; - const refPathNotFound2 = "./src/somefile.d.ts"; - const fileContent = `/// -/// `; - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: untitledFile, - fileContent, - scriptKindName: "TS", - projectRootPath: useProjectRoot ? folderPath : undefined - } - }); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const infoForUntitledAtProjectRoot = projectService.getScriptInfoForPath(`${folderPath.toLowerCase()}/${untitledFile.toLowerCase()}` as Path); - const infoForUnitiledAtRoot = projectService.getScriptInfoForPath(`/${untitledFile.toLowerCase()}` as Path); - const infoForSomefileAtProjectRoot = projectService.getScriptInfoForPath(`/${folderPath.toLowerCase()}/src/somefile.d.ts` as Path); - const infoForSomefileAtRoot = projectService.getScriptInfoForPath(`${fileInRoot.path.toLowerCase()}` as Path); - if (useProjectRoot) { - assert.isDefined(infoForUntitledAtProjectRoot); - assert.isUndefined(infoForUnitiledAtRoot); - } - else { - assert.isDefined(infoForUnitiledAtRoot); - assert.isUndefined(infoForUntitledAtProjectRoot); - } - assert.isUndefined(infoForSomefileAtRoot); - assert.isUndefined(infoForSomefileAtProjectRoot); - - // Since this is not js project so no typings are queued - host.checkTimeoutQueueLength(0); - - const newTimeoutId = host.getNextTimeoutId(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [untitledFile] - } - }); - host.checkTimeoutQueueLength(1); - - // Run the last one = get error request - host.runQueuedTimeoutCallbacks(newTimeoutId); - - assert.isFalse(hasError()); - host.checkTimeoutQueueLength(0); - checkErrorMessage(session, "syntaxDiag", { file: untitledFile, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - assert.isFalse(hasError()); - const errorOffset = fileContent.indexOf(refPathNotFound1) + 1; - checkErrorMessage(session, "semanticDiag", { - file: untitledFile, - diagnostics: [ - createDiagnostic({ line: 1, offset: errorOffset }, { line: 1, offset: errorOffset + refPathNotFound1.length }, Diagnostics.File_0_not_found, [refPathNotFound1], "error"), - createDiagnostic({ line: 2, offset: errorOffset }, { line: 2, offset: errorOffset + refPathNotFound2.length }, Diagnostics.File_0_not_found, [refPathNotFound2.substr(2)], "error") - ] - }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - assert.isFalse(hasError()); - checkErrorMessage(session, "suggestionDiag", { file: untitledFile, diagnostics: [] }); - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - } - - it("has projectRoot", () => { - verifyNonExistentFile(/*useProjectRoot*/ true); - }); - - it("does not have projectRoot", () => { - verifyNonExistentFile(/*useProjectRoot*/ false); - }); - }); - - it("folder rename updates project structure and reports no errors", () => { - const projectDir = "/a/b/projects/myproject"; - const app: File = { - path: `${projectDir}/bar/app.ts`, - content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }" - }; - const foo: File = { - path: `${projectDir}/foo/foo.ts`, - content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }" - }; - const configFile: File = { - path: `${projectDir}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }) - }; - const host = createServerHost([app, foo, configFile]); - const session = createSession(host, { canUseEvents: true, }); - const projectService = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: app.path, } - }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.isDefined(projectService.configuredProjects.get(configFile.path)); - verifyErrorsInApp(); - - host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`); - host.runQueuedTimeoutCallbacks(); - host.runQueuedTimeoutCallbacks(); - verifyErrorsInApp(); - - function verifyErrorsInApp() { - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [app.path] - } - }); - host.checkTimeoutQueueLengthAndRun(1); - checkErrorMessage(session, "syntaxDiag", { file: app.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - checkErrorMessage(session, "semanticDiag", { file: app.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - checkErrorMessage(session, "suggestionDiag", { file: app.path, diagnostics: [] }); - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - } - }); - - it("Getting errors before opening file", () => { - const file: File = { - path: "/a/b/project/file.ts", - content: "let x: number = false;" - }; - const host = createServerHost([file, libFile]); - const { hasError, errorLogger } = createErrorLogger(); - const session = createSession(host, { canUseEvents: true, logger: errorLogger }); - - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path] - } - }); - - host.runQueuedImmediateCallbacks(); - assert.isFalse(hasError()); - checkCompleteEvent(session, 1, expectedSequenceId); - session.clearMessages(); - }); - - it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { - const projectRoot = "/user/username/projects/myproject"; - const app: File = { - path: `${projectRoot}/src/client/app.js`, - content: "" - }; - const serverUtilities: File = { - path: `${projectRoot}/src/server/utilities.js`, - content: `function getHostName() { return "hello"; } export { getHostName };` - }; - const backendTest: File = { - path: `${projectRoot}/test/backend/index.js`, - content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` - }; - const files = [libFile, app, serverUtilities, backendTest]; - const host = createServerHost(files); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true }); - openFilesForSession([{ file: app, projectRootPath: projectRoot }], session); - const service = session.getProjectService(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, app.path]); - openFilesForSession([{ file: backendTest, projectRootPath: projectRoot }], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(project, files.map(f => f.path)); - checkErrors([backendTest.path, app.path]); - closeFilesForSession([backendTest], session); - openFilesForSession([{ file: serverUtilities.path, projectRootPath: projectRoot }], session); - checkErrors([serverUtilities.path, app.path]); - - function checkErrors(openFiles: [string, string]) { - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: protocol.CommandTypes.Geterr, - arguments: { - delay: 0, - files: openFiles - } - }); - - for (const openFile of openFiles) { - session.clearMessages(); - host.checkTimeoutQueueLength(3); - host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1); - - checkErrorMessage(session, "syntaxDiag", { file: openFile, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - checkErrorMessage(session, "semanticDiag", { file: openFile, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - checkErrorMessage(session, "suggestionDiag", { file: openFile, diagnostics: [] }); - } - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - } - }); - }); - describe("tsserverProjectSystem autoDiscovery", () => { it("does not depend on extension", () => { const file1 = { From 37a080bca98d5ca724366c319b5f8503e27b652a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:25:19 -0800 Subject: [PATCH 080/120] tsserver's typingInstaller test into typingsInstaller unittest --- .../unittests/tsserverProjectSystem.ts | 78 ------------------- src/testRunner/unittests/typingsInstaller.ts | 78 +++++++++++++++++++ 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 3ccb7b2ffa0..69de01cf969 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -7329,84 +7329,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem typingsInstaller on inferred Project", () => { - it("when projectRootPath is provided", () => { - const projects = "/users/username/projects"; - const projectRootPath = `${projects}/san2`; - const file: File = { - path: `${projectRootPath}/x.js`, - content: "const aaaaaaav = 1;" - }; - - const currentDirectory = `${projects}/anotherProject`; - const packageJsonInCurrentDirectory: File = { - path: `${currentDirectory}/package.json`, - content: JSON.stringify({ - devDependencies: { - pkgcurrentdirectory: "" - }, - }) - }; - const packageJsonOfPkgcurrentdirectory: File = { - path: `${currentDirectory}/node_modules/pkgcurrentdirectory/package.json`, - content: JSON.stringify({ - name: "pkgcurrentdirectory", - main: "index.js", - typings: "index.d.ts" - }) - }; - const indexOfPkgcurrentdirectory: File = { - path: `${currentDirectory}/node_modules/pkgcurrentdirectory/index.d.ts`, - content: "export function foo() { }" - }; - - const typingsCache = `/users/username/Library/Caches/typescript/2.7`; - const typingsCachePackageJson: File = { - path: `${typingsCache}/package.json`, - content: JSON.stringify({ - devDependencies: { - }, - }) - }; - const typingsCachePackageLockJson: File = { - path: `${typingsCache}/package-lock.json`, - content: JSON.stringify({ - dependencies: { - }, - }) - }; - - const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson, typingsCachePackageLockJson]; - const host = createServerHost(files, { currentDirectory }); - - const typesRegistry = createTypesRegistry("pkgcurrentdirectory"); - const typingsInstaller = new TestTypingsInstaller(typingsCache, /*throttleLimit*/ 5, host, typesRegistry); - - const projectService = createProjectService(host, { typingsInstaller }); - - projectService.setCompilerOptionsForInferredProjects({ - module: ModuleKind.CommonJS, - target: ScriptTarget.ES2016, - jsx: JsxEmit.Preserve, - experimentalDecorators: true, - allowJs: true, - allowSyntheticDefaultImports: true, - allowNonTsExtensions: true - }); - - projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRootPath); - - const project = projectService.inferredProjects[0]; - assert.isDefined(project); - - // Ensure that we use result from types cache when getting ls - assert.isDefined(project.getLanguageService()); - - // Verify that the pkgcurrentdirectory from the current directory isnt picked up - checkProjectActualFiles(project, [file.path]); - }); - }); - describe("tsserverProjectSystem with symLinks", () => { it("rename in common file renames all project", () => { const projects = "/users/username/projects"; diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index 2b18af6a71f..f0cd9549e6c 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -1774,4 +1774,82 @@ namespace ts.projectSystem { `, ["foo"], [fooAA, fooAB, fooAC]); }); }); + + describe("typingsInstaller:: tsserver:: with inferred Project", () => { + it("when projectRootPath is provided", () => { + const projects = "/users/username/projects"; + const projectRootPath = `${projects}/san2`; + const file: File = { + path: `${projectRootPath}/x.js`, + content: "const aaaaaaav = 1;" + }; + + const currentDirectory = `${projects}/anotherProject`; + const packageJsonInCurrentDirectory: File = { + path: `${currentDirectory}/package.json`, + content: JSON.stringify({ + devDependencies: { + pkgcurrentdirectory: "" + }, + }) + }; + const packageJsonOfPkgcurrentdirectory: File = { + path: `${currentDirectory}/node_modules/pkgcurrentdirectory/package.json`, + content: JSON.stringify({ + name: "pkgcurrentdirectory", + main: "index.js", + typings: "index.d.ts" + }) + }; + const indexOfPkgcurrentdirectory: File = { + path: `${currentDirectory}/node_modules/pkgcurrentdirectory/index.d.ts`, + content: "export function foo() { }" + }; + + const typingsCache = `/users/username/Library/Caches/typescript/2.7`; + const typingsCachePackageJson: File = { + path: `${typingsCache}/package.json`, + content: JSON.stringify({ + devDependencies: { + }, + }) + }; + const typingsCachePackageLockJson: File = { + path: `${typingsCache}/package-lock.json`, + content: JSON.stringify({ + dependencies: { + }, + }) + }; + + const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson, typingsCachePackageLockJson]; + const host = createServerHost(files, { currentDirectory }); + + const typesRegistry = createTypesRegistry("pkgcurrentdirectory"); + const typingsInstaller = new TestTypingsInstaller(typingsCache, /*throttleLimit*/ 5, host, typesRegistry); + + const projectService = createProjectService(host, { typingsInstaller }); + + projectService.setCompilerOptionsForInferredProjects({ + module: ModuleKind.CommonJS, + target: ScriptTarget.ES2016, + jsx: JsxEmit.Preserve, + experimentalDecorators: true, + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true + }); + + projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRootPath); + + const project = projectService.inferredProjects[0]; + assert.isDefined(project); + + // Ensure that we use result from types cache when getting ls + assert.isDefined(project.getLanguageService()); + + // Verify that the pkgcurrentdirectory from the current directory isnt picked up + checkProjectActualFiles(project, [file.path]); + }); + }); } From 03d66dd2ef066b29e43c6ebce0cdf733e37cc5d4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:27:23 -0800 Subject: [PATCH 081/120] Emit error tsserver tests into projectErrors --- src/testRunner/unittests/projectErrors.ts | 83 +++++++++++++++++++ .../unittests/tsserverProjectSystem.ts | 83 ------------------- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/projectErrors.ts index 6eb364b569d..15bbab2a551 100644 --- a/src/testRunner/unittests/projectErrors.ts +++ b/src/testRunner/unittests/projectErrors.ts @@ -480,4 +480,87 @@ namespace ts.projectSystem { } }); }); + + describe("tsserver:: Project Errors dont include overwrite emit error", () => { + it("for inferred project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = createServerHost([f1, libFile]); + const session = createSession(host); + openFilesForSession([f1], session); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const projectName = projectService.inferredProjects[0].getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: projectName } + }).response as ReadonlyArray; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsForInferredProjects, + seq: 3, + arguments: { options: { module: ModuleKind.CommonJS } } + }); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName: projectName } + }).response as ReadonlyArray; + assert.isTrue(diagsAfterUpdate.length === 0); + }); + + it("for external project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = createServerHost([f1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + const projectFileName = "/a/b/project.csproj"; + const externalFiles = toExternalFiles([f1.path]); + projectService.openExternalProject({ + projectFileName, + rootFiles: externalFiles, + options: {} + }); + + checkNumberOfProjects(projectService, { externalProjects: 1 }); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + }).response as ReadonlyArray; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: server.CommandNames.OpenExternalProject, + seq: 3, + arguments: { + projectFileName, + rootFiles: externalFiles, + options: { module: ModuleKind.CommonJS } + } + }); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName } + }).response as ReadonlyArray; + assert.isTrue(diagsAfterUpdate.length === 0); + }); + }); } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 69de01cf969..214ddabdb85 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -5214,89 +5214,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem No overwrite emit error", () => { - it("for inferred project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - openFilesForSession([f1], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const projectName = projectService.inferredProjects[0].getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName: projectName } - }).response as ReadonlyArray; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsForInferredProjects, - seq: 3, - arguments: { options: { module: ModuleKind.CommonJS } } - }); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName: projectName } - }).response as ReadonlyArray; - assert.isTrue(diagsAfterUpdate.length === 0); - }); - - it("for external project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - const projectFileName = "/a/b/project.csproj"; - const externalFiles = toExternalFiles([f1.path]); - projectService.openExternalProject({ - projectFileName, - rootFiles: externalFiles, - options: {} - }); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName } - }).response as ReadonlyArray; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.OpenExternalProject, - seq: 3, - arguments: { - projectFileName, - rootFiles: externalFiles, - options: { module: ModuleKind.CommonJS } - } - }); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName } - }).response as ReadonlyArray; - assert.isTrue(diagsAfterUpdate.length === 0); - }); - }); - describe("tsserverProjectSystem import helpers", () => { it("should not crash in tsserver", () => { const f1 = { From 39ec69e26e972757a626f61ac2fce156f7b6e4fa Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:34:16 -0800 Subject: [PATCH 082/120] Separate out tests for project loading events into their own unittest file --- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/tsserverHelpers.ts | 140 ++++----- .../unittests/tsserverProjectLoadingEvents.ts | 193 +++++++++++++ .../unittests/tsserverProjectSystem.ts | 268 ------------------ 4 files changed, 266 insertions(+), 336 deletions(-) create mode 100644 src/testRunner/unittests/tsserverProjectLoadingEvents.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index a95bc106fb1..3b8308af890 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -88,6 +88,7 @@ "unittests/tsconfigParsing.ts", "unittests/tscWatchEmit.ts", "unittests/tscWatchMode.ts", + "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", "unittests/typingsInstaller.ts", "unittests/versionCache.ts", diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserverHelpers.ts index f5113b768fc..18e6f5c6194 100644 --- a/src/testRunner/unittests/tsserverHelpers.ts +++ b/src/testRunner/unittests/tsserverHelpers.ts @@ -15,13 +15,13 @@ namespace ts.projectSystem { export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; - //const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - //function mapOutputToJson(s: string) { - // return convertToObject( - // parseJsonText("json.json", s.replace(outputEventRegex, "")), - // [] - // ); - //} + const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; + export function mapOutputToJson(s: string) { + return convertToObject( + parseJsonText("json.json", s.replace(outputEventRegex, "")), + [] + ); + } export const customTypesMap = { path: "/typesMap.json", @@ -330,39 +330,39 @@ namespace ts.projectSystem { return new TestSession({ ...sessionOptions, ...opts }); } - //function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { - // const events: T[] = []; - // const session = createSession(host, { - // eventHandler: e => { - // if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { - // events.push(e as T); - // } - // } - // }); + export function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { + const events: T[] = []; + const session = createSession(host, { + eventHandler: e => { + if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { + events.push(e as T); + } + } + }); - // return { session, events }; - //} + return { session, events }; + } - //function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { - // const session = createSession(host, { canUseEvents: true, ...opts }); + export function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { + const session = createSession(host, { canUseEvents: true, ...opts }); - // return { - // session, - // getEvents, - // clearEvents - // }; + return { + session, + getEvents, + clearEvents + }; - // function getEvents() { - // return mapDefined(host.getOutput(), s => { - // const e = mapOutputToJson(s); - // return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; - // }); - // } + function getEvents() { + return mapDefined(host.getOutput(), s => { + const e = mapOutputToJson(s); + return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; + }); + } - // function clearEvents() { - // session.clearMessages(); - // } - //} + function clearEvents() { + session.clearMessages(); + } + } export interface CreateProjectServiceParameters { cancellationToken?: HostCancellationToken; @@ -467,23 +467,25 @@ namespace ts.projectSystem { // checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); //} - //function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { - // const start = str.indexOf(substring); - // Debug.assert(start !== -1); - // return protocolToLocation(str)(start); - //} - //function protocolToLocation(text: string): (pos: number) => protocol.Location { - // const lineStarts = computeLineStarts(text); - // return pos => { - // const x = computeLineAndCharacterOfPosition(lineStarts, pos); - // return { line: x.line + 1, offset: x.character + 1 }; - // }; - //} - //function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { - // const span = textSpanFromSubstring(str, substring, options); - // const toLocation = protocolToLocation(str); - // return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; - //} + export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { + const start = str.indexOf(substring); + Debug.assert(start !== -1); + return protocolToLocation(str)(start); + } + + function protocolToLocation(text: string): (pos: number) => protocol.Location { + const lineStarts = computeLineStarts(text); + return pos => { + const x = computeLineAndCharacterOfPosition(lineStarts, pos); + return { line: x.line + 1, offset: x.character + 1 }; + }; + } + + export function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { + const span = textSpanFromSubstring(str, substring, options); + const toLocation = protocolToLocation(str); + return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; + } //function protocolRenameSpanFromSubstring( // str: string, // substring: string, @@ -492,11 +494,12 @@ namespace ts.projectSystem { //): protocol.RenameTextSpan { // return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; //} - //function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { - // const start = nthIndexOf(str, substring, options ? options.index : 0); - // Debug.assert(start !== -1); - // return createTextSpan(start, substring.length); - //} + + export function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { + const start = nthIndexOf(str, substring, options ? options.index : 0); + Debug.assert(start !== -1); + return createTextSpan(start, substring.length); + } //function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { // return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; //} @@ -509,18 +512,19 @@ namespace ts.projectSystem { //function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { // return documentSpanFromSubstring(file, substring, options); //} - //interface SpanFromSubstringOptions { - // readonly index: number; - //} - //function nthIndexOf(str: string, substr: string, n: number): number { - // let index = -1; - // for (; n >= 0; n--) { - // index = str.indexOf(substr, index + 1); - // if (index === -1) return -1; - // } - // return index; - //} + export interface SpanFromSubstringOptions { + readonly index: number; + } + + function nthIndexOf(str: string, substr: string, n: number): number { + let index = -1; + for (; n >= 0; n--) { + index = str.indexOf(substr, index + 1); + if (index === -1) return -1; + } + return index; + } /** * Test server cancellation token used to mock host token cancellation requests. diff --git a/src/testRunner/unittests/tsserverProjectLoadingEvents.ts b/src/testRunner/unittests/tsserverProjectLoadingEvents.ts new file mode 100644 index 00000000000..0ff256bd692 --- /dev/null +++ b/src/testRunner/unittests/tsserverProjectLoadingEvents.ts @@ -0,0 +1,193 @@ +namespace ts.projectSystem { + describe("tsserverProjectLoadingEvents:: ProjectLoadingStart and ProjectLoadingFinish events", () => { + const projectRoot = "/user/username/projects"; + const aTs: File = { + path: `${projectRoot}/a/a.ts`, + content: "export class A { }" + }; + const configA: File = { + path: `${projectRoot}/a/tsconfig.json`, + content: "{}" + }; + const bTsPath = `${projectRoot}/b/b.ts`; + const configBPath = `${projectRoot}/b/tsconfig.json`; + const files = [libFile, aTs, configA]; + + function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { + session: TestSession; + getNumberOfEvents: () => number; + clearEvents: () => void; + verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; + }) { + function createSessionToVerifyEvent(files: ReadonlyArray) { + const host = createServerHost(files); + const originalReadFile = host.readFile; + const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); + host.readFile = file => { + if (file === configA.path || file === configBPath) { + assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); + } + return originalReadFile.call(host, file); + }; + const service = session.getProjectService(); + return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; + + function verifyEvent(project: server.Project, reason: string) { + verifyProjectLoadEvents([ + { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, + { eventName: server.ProjectLoadingFinishEvent, data: { project } } + ]); + clearEvents(); + } + + function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects }); + const project = service.configuredProjects.get(configPath)!; + assert.isDefined(project); + verifyEvent(project, `Creating possible configured project for ${file.path} to open`); + } + } + + it("when project is created by open file", () => { + const bTs: File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: File = { + path: configBPath, + content: "{}" + }; + const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(aTs, configA.path, 1); + verifyEventWithOpenTs(bTs, configB.path, 2); + }); + + it("when change is detected in the config file", () => { + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); + verifyEventWithOpenTs(aTs, configA.path, 1); + + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configA.path)!; + verifyEvent(project, `Change in config file detected`); + }); + + it("when opening original location project", () => { + const aDTs: File = { + path: `${projectRoot}/a/a.d.ts`, + content: `export declare class A { +} +//# sourceMappingURL=a.d.ts.map +` + }; + const aDTsMap: File = { + path: `${projectRoot}/a/a.d.ts.map`, + content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` + }; + const bTs: File = { + path: bTsPath, + content: `import {A} from "../a/a"; new A();` + }; + const configB: File = { + path: configBPath, + content: JSON.stringify({ + references: [{ path: "../a" }] + }) + }; + + const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); + + session.executeCommandSeq({ + command: protocol.CommandTypes.References, + arguments: { + file: bTs.path, + ...protocolLocationFromSubstring(bTs.content, "A()") + } + }); + + checkNumberOfProjects(service, { configuredProjects: 2 }); + const project = service.configuredProjects.get(configA.path)!; + assert.isDefined(project); + verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`); + }); + + describe("with external projects and config files ", () => { + const projectFileName = `${projectRoot}/a/project.csproj`; + + function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { + const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([aTs.path, configA.path]), + options: {} + }); + checkNumberOfProjects(service, { configuredProjects: 1 }); + return { session, service, verifyEvent, getNumberOfEvents }; + + function verifyEvent() { + const projectA = service.configuredProjects.get(configA.path)!; + assert.isDefined(projectA); + verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); + } + } + + it("when lazyConfiguredProjectsFromExternalProject is false", () => { + const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); + verifyEvent(); + }); + + it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { + const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); + + openFilesForSession([aTs], session); + verifyEvent(); + }); + + it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + verifyEvent(); + }); + }); + } + + describe("when using event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); + return { + session, + getNumberOfEvents: () => events.length, + clearEvents: () => events.length = 0, + verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) + }; + }); + }); + + describe("when using default event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); + return { + session, + getNumberOfEvents: () => getEvents().length, + clearEvents, + verifyProjectLoadEvents + }; + + function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { + const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); + const mappedExpected = expected.map(e => { + const { project, ...rest } = e.data; + return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; + }); + assert.deepEqual(actual, mappedExpected); + } + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 214ddabdb85..74522bcca8d 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -1,46 +1,4 @@ namespace ts.projectSystem { - const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - function mapOutputToJson(s: string) { - return convertToObject( - parseJsonText("json.json", s.replace(outputEventRegex, "")), - [] - ); - } - - function createSessionWithEventTracking(host: server.ServerHost, eventName: T["eventName"], ...eventNames: T["eventName"][]) { - const events: T[] = []; - const session = createSession(host, { - eventHandler: e => { - if (e.eventName === eventName || eventNames.some(eventName => e.eventName === eventName)) { - events.push(e as T); - } - } - }); - - return { session, events }; - } - - function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { - const session = createSession(host, { canUseEvents: true, ...opts }); - - return { - session, - getEvents, - clearEvents - }; - - function getEvents() { - return mapDefined(host.getOutput(), s => { - const e = mapOutputToJson(s); - return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; - }); - } - - function clearEvents() { - session.clearMessages(); - } - } - function getNodeModuleDirectories(dir: string) { return getRootsToWatchWithAncestorDirectory(dir, nodeModules); } @@ -57,23 +15,6 @@ namespace ts.projectSystem { checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); } - function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { - const start = str.indexOf(substring); - Debug.assert(start !== -1); - return protocolToLocation(str)(start); - } - function protocolToLocation(text: string): (pos: number) => protocol.Location { - const lineStarts = computeLineStarts(text); - return pos => { - const x = computeLineAndCharacterOfPosition(lineStarts, pos); - return { line: x.line + 1, offset: x.character + 1 }; - }; - } - function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { - const span = textSpanFromSubstring(str, substring, options); - const toLocation = protocolToLocation(str); - return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; - } function protocolRenameSpanFromSubstring( str: string, substring: string, @@ -82,11 +23,6 @@ namespace ts.projectSystem { ): protocol.RenameTextSpan { return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; } - function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { - const start = nthIndexOf(str, substring, options ? options.index : 0); - Debug.assert(start !== -1); - return createTextSpan(start, substring.length); - } function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; } @@ -99,18 +35,6 @@ namespace ts.projectSystem { function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { return documentSpanFromSubstring(file, substring, options); } - interface SpanFromSubstringOptions { - readonly index: number; - } - - function nthIndexOf(str: string, substr: string, n: number): number { - let index = -1; - for (; n >= 0; n--) { - index = str.indexOf(substr, index + 1); - if (index === -1) return -1; - } - return index; - } interface ErrorInformation { diagnosticMessage: DiagnosticMessage; @@ -7843,198 +7767,6 @@ new C();` }); }); - describe("tsserverProjectSystem ProjectLoadingStart and ProjectLoadingFinish events", () => { - const projectRoot = "/user/username/projects"; - const aTs: File = { - path: `${projectRoot}/a/a.ts`, - content: "export class A { }" - }; - const configA: File = { - path: `${projectRoot}/a/tsconfig.json`, - content: "{}" - }; - const bTsPath = `${projectRoot}/b/b.ts`; - const configBPath = `${projectRoot}/b/tsconfig.json`; - const files = [libFile, aTs, configA]; - - function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { - session: TestSession; - getNumberOfEvents: () => number; - clearEvents: () => void; - verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; - }) { - function createSessionToVerifyEvent(files: ReadonlyArray) { - const host = createServerHost(files); - const originalReadFile = host.readFile; - const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); - host.readFile = file => { - if (file === configA.path || file === configBPath) { - assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); - } - return originalReadFile.call(host, file); - }; - const service = session.getProjectService(); - return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; - - function verifyEvent(project: server.Project, reason: string) { - verifyProjectLoadEvents([ - { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, - { eventName: server.ProjectLoadingFinishEvent, data: { project } } - ]); - clearEvents(); - } - - function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects }); - const project = service.configuredProjects.get(configPath)!; - assert.isDefined(project); - verifyEvent(project, `Creating possible configured project for ${file.path} to open`); - } - } - - it("when project is created by open file", () => { - const bTs: File = { - path: bTsPath, - content: "export class B {}" - }; - const configB: File = { - path: configBPath, - content: "{}" - }; - const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); - verifyEventWithOpenTs(aTs, configA.path, 1); - verifyEventWithOpenTs(bTs, configB.path, 2); - }); - - it("when change is detected in the config file", () => { - const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); - verifyEventWithOpenTs(aTs, configA.path, 1); - - host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); - const project = service.configuredProjects.get(configA.path)!; - verifyEvent(project, `Change in config file detected`); - }); - - it("when opening original location project", () => { - const aDTs: File = { - path: `${projectRoot}/a/a.d.ts`, - content: `export declare class A { -} -//# sourceMappingURL=a.d.ts.map -` - }; - const aDTsMap: File = { - path: `${projectRoot}/a/a.d.ts.map`, - content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` - }; - const bTs: File = { - path: bTsPath, - content: `import {A} from "../a/a"; new A();` - }; - const configB: File = { - path: configBPath, - content: JSON.stringify({ - references: [{ path: "../a" }] - }) - }; - - const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); - verifyEventWithOpenTs(bTs, configB.path, 1); - - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: { - file: bTs.path, - ...protocolLocationFromSubstring(bTs.content, "A()") - } - }); - - checkNumberOfProjects(service, { configuredProjects: 2 }); - const project = service.configuredProjects.get(configA.path)!; - assert.isDefined(project); - verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`); - }); - - describe("with external projects and config files ", () => { - const projectFileName = `${projectRoot}/a/project.csproj`; - - function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { - const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([aTs.path, configA.path]), - options: {} - }); - checkNumberOfProjects(service, { configuredProjects: 1 }); - return { session, service, verifyEvent, getNumberOfEvents }; - - function verifyEvent() { - const projectA = service.configuredProjects.get(configA.path)!; - assert.isDefined(projectA); - verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); - } - } - - it("when lazyConfiguredProjectsFromExternalProject is false", () => { - const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); - verifyEvent(); - }); - - it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { - const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(getNumberOfEvents(), 0); - - openFilesForSession([aTs], session); - verifyEvent(); - }); - - it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(getNumberOfEvents(), 0); - - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - verifyEvent(); - }); - }); - } - - describe("when using event handler", () => { - verifyProjectLoadingStartAndFinish(host => { - const { session, events } = createSessionWithEventTracking(host, server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent); - return { - session, - getNumberOfEvents: () => events.length, - clearEvents: () => events.length = 0, - verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) - }; - }); - }); - - describe("when using default event handler", () => { - verifyProjectLoadingStartAndFinish(host => { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); - return { - session, - getNumberOfEvents: () => getEvents().length, - clearEvents, - verifyProjectLoadEvents - }; - - function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { - const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); - const mappedExpected = expected.map(e => { - const { project, ...rest } = e.data; - return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; - }); - assert.deepEqual(actual, mappedExpected); - } - }); - }); - }); - describe("tsserverProjectSystem syntax operations", () => { function navBarFull(session: TestSession, file: File) { return JSON.stringify(session.executeCommandSeq({ From 04e190abb7a5bd899c32116c3b383334a26609b3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:43:07 -0800 Subject: [PATCH 083/120] ProjectUpdatedInBackground in a separate unittest --- src/testRunner/tsconfig.json | 1 + .../unittests/tsserverProjectSystem.ts | 590 ----------------- ...tsserverProjectUpdatedInBackgroundEvent.ts | 591 ++++++++++++++++++ 3 files changed, 592 insertions(+), 590 deletions(-) create mode 100644 src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 3b8308af890..6c18df789f6 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -90,6 +90,7 @@ "unittests/tscWatchMode.ts", "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", + "unittests/tsserverProjectUpdatedInBackgroundEvent.ts", "unittests/typingsInstaller.ts", "unittests/versionCache.ts", "unittests/watchEnvironment.ts", diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 74522bcca8d..c098eede598 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -6580,596 +6580,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem ProjectsChangedInBackground", () => { - function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { - assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); - const seen = createMap(); - forEach(actual, f => { - assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); - seen.set(f, true); - assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); - }); - } - - function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) { - return (file: File) => { - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: file.path - } - }); - verifyProjectsUpdatedInBackgroundEventHandler([]); - }; - } - - interface ProjectsUpdatedInBackgroundEventVerifier { - session: TestSession; - verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void; - verifyInitialOpen(file: File): void; - } - - function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) { - it("when adding new file", () => { - const commonFile1: File = { - path: "/a/b/file1.ts", - content: "export var x = 10;" - }; - const commonFile2: File = { - path: "/a/b/file2.ts", - content: "export var y = 10;" - }; - const commonFile3: File = { - path: "/a/b/file3.ts", - content: "export var z = 10;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const openFiles = [commonFile1.path]; - const host = createServerHost([commonFile1, libFile, configFile]); - const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); - verifyInitialOpen(commonFile1); - - host.reloadFS([commonFile1, libFile, configFile, commonFile2]); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - - host.reloadFS([commonFile1, commonFile2, libFile, configFile, commonFile3]); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - }); - - describe("with --out or --outFile setting", () => { - function verifyEventWithOutSettings(compilerOptions: CompilerOptions = {}) { - const config: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions - }) - }; - - const f1: File = { - path: "/a/a.ts", - content: "export let x = 1" - }; - const f2: File = { - path: "/a/b.ts", - content: "export let y = 1" - }; - - const openFiles = [f1.path]; - const files = [f1, config, libFile]; - const host = createServerHost(files); - const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); - verifyInitialOpen(f1); - - files.push(f2); - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - - f2.content = "export let x = 11"; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - } - - it("when both options are not set", () => { - verifyEventWithOutSettings(); - }); - - it("when --out is set", () => { - const outJs = "/a/out.js"; - verifyEventWithOutSettings({ out: outJs }); - }); - - it("when --outFile is set", () => { - const outJs = "/a/out.js"; - verifyEventWithOutSettings({ outFile: outJs }); - }); - }); - - describe("with modules and configured project", () => { - const file1Consumer1Path = "/a/b/file1Consumer1.ts"; - const moduleFile1Path = "/a/b/moduleFile1.ts"; - const configFilePath = "/a/b/tsconfig.json"; - interface InitialStateParams { - /** custom config file options */ - configObj?: any; - /** Additional files and folders to add */ - getAdditionalFileOrFolder?(): File[]; - /** initial list of files to reload in fs and first file in this list being the file to open */ - firstReloadFileList?: string[]; - } - function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { - const moduleFile1: File = { - path: moduleFile1Path, - content: "export function Foo() { };", - }; - - const file1Consumer1: File = { - path: file1Consumer1Path, - content: `import {Foo} from "./moduleFile1"; export var y = 10;`, - }; - - const file1Consumer2: File = { - path: "/a/b/file1Consumer2.ts", - content: `import {Foo} from "./moduleFile1"; let z = 10;`, - }; - - const moduleFile2: File = { - path: "/a/b/moduleFile2.ts", - content: `export var Foo4 = 10;`, - }; - - const globalFile3: File = { - path: "/a/b/globalFile3.ts", - content: `interface GlobalFoo { age: number }` - }; - - const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; - const configFile = { - path: configFilePath, - content: JSON.stringify(configObj || { compilerOptions: {} }) - }; - - const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile]; - - const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; - const host = createServerHost([filesToReload[0], configFile]); - - // Initial project creation - const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); - const openFiles = [filesToReload[0].path]; - verifyInitialOpen(filesToReload[0]); - - // Since this is first event, it will have all the files - verifyProjectsUpdatedInBackgroundEvent(filesToReload); - - return { - moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, - files, - updateContentOfOpenFile, - verifyNoProjectsUpdatedInBackgroundEvent, - verifyProjectsUpdatedInBackgroundEvent - }; - - function getFiles(filelist: string[]) { - return map(filelist, getFile); - } - - function getFile(fileName: string) { - return find(files, file => file.path === fileName)!; - } - - function verifyNoProjectsUpdatedInBackgroundEvent(filesToReload?: File[]) { - host.reloadFS(filesToReload || files); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([]); - } - - function verifyProjectsUpdatedInBackgroundEvent(filesToReload?: File[]) { - host.reloadFS(filesToReload || files); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - } - - function updateContentOfOpenFile(file: File, newContent: string) { - session.executeCommandSeq({ - command: server.CommandNames.Change, - arguments: { - file: file.path, - insertString: newContent, - endLine: 1, - endOffset: file.content.length, - line: 1, - offset: 1 - } - }); - file.content = newContent; - } - } - - it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` - moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should be up-to-date with the reference map changes", () => { - const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); - - // Change file1Consumer1 content to `export let y = Foo();` - updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); - verifyNoProjectsUpdatedInBackgroundEvent(); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - - // Add the import statements back to file1Consumer1 - updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); - verifyNoProjectsUpdatedInBackgroundEvent(); - - // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` - moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - - // Multiple file edits in one go: - - // Change file1Consumer1 content to `export let y = Foo();` - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should be up-to-date with deleted files", () => { - const { moduleFile1, file1Consumer2, files, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - moduleFile1.content = `export var T: number;export function Foo() { };`; - - // Delete file1Consumer2 - const filesToLoad = filter(files, file => file !== file1Consumer2); - verifyProjectsUpdatedInBackgroundEvent(filesToLoad); - }); - - it("should be up-to-date with newly created files", () => { - const { moduleFile1, files, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); - - const file1Consumer3: File = { - path: "/a/b/file1Consumer3.ts", - content: `import {Foo} from "./moduleFile1"; let y = Foo();` - }; - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(files.concat(file1Consumer3)); - }); - - it("should detect changes in non-root files", () => { - const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { files: [file1Consumer1Path] }, - }); - - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - - // change file1 internal, and verify only file1 is affected - moduleFile1.content += "var T1: number;"; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should return all files if a global file changed shape", () => { - const { globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - - globalFile3.content += "var T2: string;"; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should always return the file itself if '--isolatedModules' is specified", () => { - const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { compilerOptions: { isolatedModules: true } } - }); - - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should always return the file itself if '--out' or '--outFile' is specified", () => { - const outFilePath = "/a/b/out.js"; - const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { compilerOptions: { module: "system", outFile: outFilePath } } - }); - - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should return cascaded affected file list", () => { - const file1Consumer1Consumer1: File = { - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }; - const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] - }); - - updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); - verifyNoProjectsUpdatedInBackgroundEvent(); - - // Doesnt change the shape of file1Consumer1 - moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - - // Change both files before the timeout - updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); - moduleFile1.content = `export var T2: number;export function Foo() { };`; - verifyProjectsUpdatedInBackgroundEvent(); - }); - - it("should work fine for files with circular references", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: ` - /// - export var t1 = 10;` - }; - const file2: File = { - path: "/a/b/file2.ts", - content: ` - /// - export var t2 = 10;` - }; - const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [file1, file2], - firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] - }); - - file2.content += "export var t3 = 10;"; - verifyProjectsUpdatedInBackgroundEvent([file1, file2, libFile, configFile]); - }); - - it("should detect removed code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` - /// - export var x = Foo();` - }; - const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] - }); - - verifyProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]); - }); - - it("should detect non-existing code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` - /// - export var x = Foo();` - }; - const { configFile, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] - }); - - updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); - verifyNoProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]); - - // Create module File2 and see both files are saved - verifyProjectsUpdatedInBackgroundEvent([libFile, moduleFile2, referenceFile1, configFile]); - }); - }); - - describe("resolution when resolution cache size", () => { - function verifyWithMaxCacheLimit(limitHit: boolean, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { - const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; - const file1: File = { - path: rootFolder + "a/b/project/file1.ts", - content: 'import a from "file2"' - }; - const file2: File = { - path: rootFolder + "a/b/node_modules/file2.d.ts", - content: "export class a { }" - }; - const file3: File = { - path: rootFolder + "a/b/project/file3.ts", - content: "export class c { }" - }; - const configFile: File = { - path: rootFolder + "a/b/project/tsconfig.json", - content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) - }; - - const projectFiles = [file1, file3, libFile, configFile]; - const openFiles = [file1.path]; - const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ? - // Folders of node_modules lookup not in changedRoot - ["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) : - // Folder of tsconfig - ["/a/b/project", "/a/b/project/node_modules"]; - const host = createServerHost(projectFiles); - const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); - const projectService = session.getProjectService(); - verifyInitialOpen(file1); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path)!; - verifyProject(); - if (limitHit) { - (project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1; - } - - file3.content += "export class d {}"; - host.reloadFS(projectFiles); - host.checkTimeoutQueueLengthAndRun(2); - - // Since this is first event - verifyProject(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - - projectFiles.push(file2); - host.reloadFS(projectFiles); - host.runQueuedTimeoutCallbacks(); - if (useSlashRootAsSomeNotRootFolderInUserDirectory) { - watchedRecursiveDirectories.length = 3; - } - else { - // file2 addition wont be detected - projectFiles.pop(); - assert.isTrue(host.fileExists(file2.path)); - } - verifyProject(); - - verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }] : []); - - function verifyProject() { - checkProjectActualFiles(project, map(projectFiles, file => file.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); - } - } - - it("limit not hit and project is not at root level", () => { - verifyWithMaxCacheLimit(/*limitHit*/ false, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); - }); - - it("limit hit and project is not at root level", () => { - verifyWithMaxCacheLimit(/*limitHit*/ true, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); - }); - - it("limit not hit and project is at root level", () => { - verifyWithMaxCacheLimit(/*limitHit*/ false, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); - }); - - it("limit hit and project is at root level", () => { - verifyWithMaxCacheLimit(/*limitHit*/ true, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); - }); - }); - } - - describe("when event handler is set in the session", () => { - verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); - - function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { - const { session, events: projectChangedEvents } = createSessionWithEventTracking(host, server.ProjectsUpdatedInBackgroundEvent); - return { - session, - verifyProjectsUpdatedInBackgroundEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) - }; - - function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) { - return JSON.stringify(event && { eventName: event.eventName, data: event.data }); - } - - function eventsToString(events: ReadonlyArray) { - return "[" + map(events, eventToString).join(",") + "]"; - } - - function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: ReadonlyArray) { - assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); - forEach(projectChangedEvents, (actualEvent, i) => { - const expectedEvent = expectedEvents[i]; - assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); - verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); - }); - - // Verified the events, reset them - projectChangedEvents.length = 0; - } - } - }); - - describe("when event handler is not set but session is created with canUseEvents = true", () => { - describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { - verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents); - }); - - describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { - verifyProjectsUpdatedInBackgroundEvent(host => createSessionThatUsesEvents(host, /*noGetErrOnBackgroundUpdate*/ true)); - }); - - - function createSessionThatUsesEvents(host: TestServerHost, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectsUpdatedInBackgroundEvent, { noGetErrOnBackgroundUpdate }); - - return { - session, - verifyProjectsUpdatedInBackgroundEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) - }; - - function verifyProjectsUpdatedInBackgroundEventHandler(expected: ReadonlyArray) { - const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => { - return { - openFiles: e.data.openFiles - }; - }); - const events = getEvents(); - assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); - forEach(events, (actualEvent, i) => { - const expectedEvent = expectedEvents[i]; - verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); - }); - - // Verified the events, reset them - clearEvents(); - - if (events.length) { - host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate - } - } - } - }); - }); - describe("tsserverProjectSystem with symLinks", () => { it("rename in common file renames all project", () => { const projects = "/users/username/projects"; diff --git a/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts b/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts new file mode 100644 index 00000000000..3e8382fff58 --- /dev/null +++ b/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts @@ -0,0 +1,591 @@ +namespace ts.projectSystem { + describe("tsserverProjectUpdatedInBackgroundEvent:: ProjectsUpdatedInBackground", () => { + function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { + assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); + const seen = createMap(); + forEach(actual, f => { + assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); + seen.set(f, true); + assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); + }); + } + + function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) { + return (file: File) => { + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: file.path + } + }); + verifyProjectsUpdatedInBackgroundEventHandler([]); + }; + } + + interface ProjectsUpdatedInBackgroundEventVerifier { + session: TestSession; + verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void; + verifyInitialOpen(file: File): void; + } + + function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) { + it("when adding new file", () => { + const commonFile1: File = { + path: "/a/b/file1.ts", + content: "export var x = 10;" + }; + const commonFile2: File = { + path: "/a/b/file2.ts", + content: "export var y = 10;" + }; + const commonFile3: File = { + path: "/a/b/file3.ts", + content: "export var z = 10;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const openFiles = [commonFile1.path]; + const host = createServerHost([commonFile1, libFile, configFile]); + const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + verifyInitialOpen(commonFile1); + + host.reloadFS([commonFile1, libFile, configFile, commonFile2]); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + + host.reloadFS([commonFile1, commonFile2, libFile, configFile, commonFile3]); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + }); + + describe("with --out or --outFile setting", () => { + function verifyEventWithOutSettings(compilerOptions: CompilerOptions = {}) { + const config: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions + }) + }; + + const f1: File = { + path: "/a/a.ts", + content: "export let x = 1" + }; + const f2: File = { + path: "/a/b.ts", + content: "export let y = 1" + }; + + const openFiles = [f1.path]; + const files = [f1, config, libFile]; + const host = createServerHost(files); + const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); + verifyInitialOpen(f1); + + files.push(f2); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + + f2.content = "export let x = 11"; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + } + + it("when both options are not set", () => { + verifyEventWithOutSettings(); + }); + + it("when --out is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ out: outJs }); + }); + + it("when --outFile is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ outFile: outJs }); + }); + }); + + describe("with modules and configured project", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const configFilePath = "/a/b/tsconfig.json"; + interface InitialStateParams { + /** custom config file options */ + configObj?: any; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): File[]; + /** initial list of files to reload in fs and first file in this list being the file to open */ + firstReloadFileList?: string[]; + } + function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { + const moduleFile1: File = { + path: moduleFile1Path, + content: "export function Foo() { };", + }; + + const file1Consumer1: File = { + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }; + + const file1Consumer2: File = { + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }; + + const moduleFile2: File = { + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;`, + }; + + const globalFile3: File = { + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }; + + const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; + const configFile = { + path: configFilePath, + content: JSON.stringify(configObj || { compilerOptions: {} }) + }; + + const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile]; + + const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; + const host = createServerHost([filesToReload[0], configFile]); + + // Initial project creation + const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + const openFiles = [filesToReload[0].path]; + verifyInitialOpen(filesToReload[0]); + + // Since this is first event, it will have all the files + verifyProjectsUpdatedInBackgroundEvent(filesToReload); + + return { + moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, + files, + updateContentOfOpenFile, + verifyNoProjectsUpdatedInBackgroundEvent, + verifyProjectsUpdatedInBackgroundEvent + }; + + function getFiles(filelist: string[]) { + return map(filelist, getFile); + } + + function getFile(fileName: string) { + return find(files, file => file.path === fileName)!; + } + + function verifyNoProjectsUpdatedInBackgroundEvent(filesToReload?: File[]) { + host.reloadFS(filesToReload || files); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([]); + } + + function verifyProjectsUpdatedInBackgroundEvent(filesToReload?: File[]) { + host.reloadFS(filesToReload || files); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + } + + function updateContentOfOpenFile(file: File, newContent: string) { + session.executeCommandSeq({ + command: server.CommandNames.Change, + arguments: { + file: file.path, + insertString: newContent, + endLine: 1, + endOffset: file.content.length, + line: 1, + offset: 1 + } + }); + file.content = newContent; + } + } + + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` + moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should be up-to-date with the reference map changes", () => { + const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); + + // Change file1Consumer1 content to `export let y = Foo();` + updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); + verifyNoProjectsUpdatedInBackgroundEvent(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + + // Add the import statements back to file1Consumer1 + updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); + verifyNoProjectsUpdatedInBackgroundEvent(); + + // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` + moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + + // Multiple file edits in one go: + + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should be up-to-date with deleted files", () => { + const { moduleFile1, file1Consumer2, files, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + + // Delete file1Consumer2 + const filesToLoad = filter(files, file => file !== file1Consumer2); + verifyProjectsUpdatedInBackgroundEvent(filesToLoad); + }); + + it("should be up-to-date with newly created files", () => { + const { moduleFile1, files, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); + + const file1Consumer3: File = { + path: "/a/b/file1Consumer3.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }; + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(files.concat(file1Consumer3)); + }); + + it("should detect changes in non-root files", () => { + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { files: [file1Consumer1Path] }, + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + + // change file1 internal, and verify only file1 is affected + moduleFile1.content += "var T1: number;"; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should return all files if a global file changed shape", () => { + const { globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + + globalFile3.content += "var T2: string;"; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { compilerOptions: { isolatedModules: true } } + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } } + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: File = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] + }); + + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); + verifyNoProjectsUpdatedInBackgroundEvent(); + + // Doesnt change the shape of file1Consumer1 + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + + // Change both files before the timeout + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); + moduleFile1.content = `export var T2: number;export function Foo() { };`; + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should work fine for files with circular references", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: ` + /// + export var t1 = 10;` + }; + const file2: File = { + path: "/a/b/file2.ts", + content: ` + /// + export var t2 = 10;` + }; + const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] + }); + + file2.content += "export var t3 = 10;"; + verifyProjectsUpdatedInBackgroundEvent([file1, file2, libFile, configFile]); + }); + + it("should detect removed code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] + }); + + verifyProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]); + }); + + it("should detect non-existing code file", () => { + const referenceFile1: File = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { configFile, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] + }); + + updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); + verifyNoProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]); + + // Create module File2 and see both files are saved + verifyProjectsUpdatedInBackgroundEvent([libFile, moduleFile2, referenceFile1, configFile]); + }); + }); + + describe("resolution when resolution cache size", () => { + function verifyWithMaxCacheLimit(limitHit: boolean, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { + const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; + const file1: File = { + path: rootFolder + "a/b/project/file1.ts", + content: 'import a from "file2"' + }; + const file2: File = { + path: rootFolder + "a/b/node_modules/file2.d.ts", + content: "export class a { }" + }; + const file3: File = { + path: rootFolder + "a/b/project/file3.ts", + content: "export class c { }" + }; + const configFile: File = { + path: rootFolder + "a/b/project/tsconfig.json", + content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) + }; + + const projectFiles = [file1, file3, libFile, configFile]; + const openFiles = [file1.path]; + const watchedRecursiveDirectories = useSlashRootAsSomeNotRootFolderInUserDirectory ? + // Folders of node_modules lookup not in changedRoot + ["a/b/project", "a/b/project/node_modules", "a/b/node_modules", "a/node_modules", "node_modules"].map(v => rootFolder + v) : + // Folder of tsconfig + ["/a/b/project", "/a/b/project/node_modules"]; + const host = createServerHost(projectFiles); + const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); + const projectService = session.getProjectService(); + verifyInitialOpen(file1); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path)!; + verifyProject(); + if (limitHit) { + (project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1; + } + + file3.content += "export class d {}"; + host.reloadFS(projectFiles); + host.checkTimeoutQueueLengthAndRun(2); + + // Since this is first event + verifyProject(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + + projectFiles.push(file2); + host.reloadFS(projectFiles); + host.runQueuedTimeoutCallbacks(); + if (useSlashRootAsSomeNotRootFolderInUserDirectory) { + watchedRecursiveDirectories.length = 3; + } + else { + // file2 addition wont be detected + projectFiles.pop(); + assert.isTrue(host.fileExists(file2.path)); + } + verifyProject(); + + verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }] : []); + + function verifyProject() { + checkProjectActualFiles(project, map(projectFiles, file => file.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + } + } + + it("limit not hit and project is not at root level", () => { + verifyWithMaxCacheLimit(/*limitHit*/ false, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); + }); + + it("limit hit and project is not at root level", () => { + verifyWithMaxCacheLimit(/*limitHit*/ true, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); + }); + + it("limit not hit and project is at root level", () => { + verifyWithMaxCacheLimit(/*limitHit*/ false, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); + }); + + it("limit hit and project is at root level", () => { + verifyWithMaxCacheLimit(/*limitHit*/ true, /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); + }); + }); + } + + describe("when event handler is set in the session", () => { + verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); + + function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { + const { session, events: projectChangedEvents } = createSessionWithEventTracking(host, server.ProjectsUpdatedInBackgroundEvent); + return { + session, + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) + }; + + function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) { + return JSON.stringify(event && { eventName: event.eventName, data: event.data }); + } + + function eventsToString(events: ReadonlyArray) { + return "[" + map(events, eventToString).join(",") + "]"; + } + + function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: ReadonlyArray) { + assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); + forEach(projectChangedEvents, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); + verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); + }); + + // Verified the events, reset them + projectChangedEvents.length = 0; + } + } + }); + + describe("when event handler is not set but session is created with canUseEvents = true", () => { + describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { + verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents); + }); + + describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { + verifyProjectsUpdatedInBackgroundEvent(host => createSessionThatUsesEvents(host, /*noGetErrOnBackgroundUpdate*/ true)); + }); + + + function createSessionThatUsesEvents(host: TestServerHost, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { + const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, server.ProjectsUpdatedInBackgroundEvent, { noGetErrOnBackgroundUpdate }); + + return { + session, + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) + }; + + function verifyProjectsUpdatedInBackgroundEventHandler(expected: ReadonlyArray) { + const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => { + return { + openFiles: e.data.openFiles + }; + }); + const events = getEvents(); + assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); + forEach(events, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); + }); + + // Verified the events, reset them + clearEvents(); + + if (events.length) { + host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate + } + } + } + }); + }); +} From f6ac9495962c803b718b88fe87a27fa385bf19f4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 12:47:08 -0800 Subject: [PATCH 084/120] Separate out LargeFileReferencedEvent test --- src/testRunner/tsconfig.json | 1 + .../tsserverLargeFileReferencedEvent.ts | 76 +++++++++++++++++++ .../unittests/tsserverProjectSystem.ts | 75 ------------------ 3 files changed, 77 insertions(+), 75 deletions(-) create mode 100644 src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 6c18df789f6..ba46706ee16 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -88,6 +88,7 @@ "unittests/tsconfigParsing.ts", "unittests/tscWatchEmit.ts", "unittests/tscWatchMode.ts", + "unittests/tsserverLargeFileReferencedEvent.ts", "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", "unittests/tsserverProjectUpdatedInBackgroundEvent.ts", diff --git a/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts b/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts new file mode 100644 index 00000000000..f33f32dfe58 --- /dev/null +++ b/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts @@ -0,0 +1,76 @@ +namespace ts.projectSystem { + describe("tsserverLargeFileReferencedEvent:: LargeFileReferencedEvent with large file", () => { + const projectRoot = "/user/username/projects/project"; + + function getLargeFile(useLargeTsFile: boolean) { + return `src/large.${useLargeTsFile ? "ts" : "js"}`; + } + + function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) { + const largeFile: File = { + path: `${projectRoot}/${getLargeFile(useLargeTsFile)}`, + content: "export var x = 10;", + fileSize: server.maxFileSize + 1 + }; + files.push(largeFile); + const host = createServerHost(files); + const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking(host, server.LargeFileReferencedEvent); + + return { session, verifyLargeFile }; + + function verifyLargeFile(project: server.Project) { + checkProjectActualFiles(project, files.map(f => f.path)); + + // large file for non ts file should be empty and for ts file should have content + const service = session.getProjectService(); + const info = service.getScriptInfo(largeFile.path)!; + assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : ""); + + assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? emptyArray : [{ + eventName: server.LargeFileReferencedEvent, + data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } + }]); + } + } + + function verifyLargeFile(useLargeTsFile: boolean) { + it("when large file is included by tsconfig", () => { + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) + }; + const files = [file, libFile, tsconfig]; + const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + verifyLargeFile(service.configuredProjects.get(tsconfig.path)!); + }); + + it("when large file is included by module resolution", () => { + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: `export var y = 10;import {x} from "./large"` + }; + const files = [file, libFile]; + const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + verifyLargeFile(service.inferredProjects[0]); + }); + } + + describe("large file is ts file", () => { + verifyLargeFile(/*useLargeTsFile*/ true); + }); + + describe("large file is js file", () => { + verifyLargeFile(/*useLargeTsFile*/ false); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index c098eede598..0c6382f13c4 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -7102,81 +7102,6 @@ new C();` }); }); - describe("tsserverProjectSystem with large file", () => { - const projectRoot = "/user/username/projects/project"; - - function getLargeFile(useLargeTsFile: boolean) { - return `src/large.${useLargeTsFile ? "ts" : "js"}`; - } - - function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) { - const largeFile: File = { - path: `${projectRoot}/${getLargeFile(useLargeTsFile)}`, - content: "export var x = 10;", - fileSize: server.maxFileSize + 1 - }; - files.push(largeFile); - const host = createServerHost(files); - const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking(host, server.LargeFileReferencedEvent); - - return { session, verifyLargeFile }; - - function verifyLargeFile(project: server.Project) { - checkProjectActualFiles(project, files.map(f => f.path)); - - // large file for non ts file should be empty and for ts file should have content - const service = session.getProjectService(); - const info = service.getScriptInfo(largeFile.path)!; - assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : ""); - - assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? emptyArray : [{ - eventName: server.LargeFileReferencedEvent, - data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } - }]); - } - } - - function verifyLargeFile(useLargeTsFile: boolean) { - it("when large file is included by tsconfig", () => { - const file: File = { - path: `${projectRoot}/src/file.ts`, - content: "export var y = 10;" - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) - }; - const files = [file, libFile, tsconfig]; - const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - verifyLargeFile(service.configuredProjects.get(tsconfig.path)!); - }); - - it("when large file is included by module resolution", () => { - const file: File = { - path: `${projectRoot}/src/file.ts`, - content: `export var y = 10;import {x} from "./large"` - }; - const files = [file, libFile]; - const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - verifyLargeFile(service.inferredProjects[0]); - }); - } - - describe("large file is ts file", () => { - verifyLargeFile(/*useLargeTsFile*/ true); - }); - - describe("large file is js file", () => { - verifyLargeFile(/*useLargeTsFile*/ false); - }); - }); - describe("tsserverProjectSystem syntax operations", () => { function navBarFull(session: TestSession, file: File) { return JSON.stringify(session.executeCommandSeq({ From 96c73701c16b0d859b8b7983701468548d1b6bbe Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 13:03:13 -0800 Subject: [PATCH 085/120] More refactoring for resloutionCache and project errors --- src/testRunner/unittests/projectErrors.ts | 283 +++++++ src/testRunner/unittests/resolutionCache.ts | 401 ++++++++++ src/testRunner/unittests/tsserverHelpers.ts | 44 +- .../unittests/tsserverProjectSystem.ts | 712 ------------------ 4 files changed, 706 insertions(+), 734 deletions(-) diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/projectErrors.ts index 15bbab2a551..9ff7278e78c 100644 --- a/src/testRunner/unittests/projectErrors.ts +++ b/src/testRunner/unittests/projectErrors.ts @@ -563,4 +563,287 @@ namespace ts.projectSystem { assert.isTrue(diagsAfterUpdate.length === 0); }); }); + + describe("tsserver:: Project Errors for Configure file diagnostics events", () => { + function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic { + const d = Diagnostics.Unknown_compiler_option_0; + const start = configFile.content.indexOf(prop) - 1; // start at "prop" + return { + fileName: configFile.path, + start, + length: prop.length + 2, + messageText: formatStringFromArgs(d.message, [prop]), + category: d.category, + code: d.code, + reportsUnnecessary: undefined + }; + } + + function getFileNotFoundDiagnostic(configFile: File, relativeFileName: string): ConfigFileDiagnostic { + const findString = `{"path":"./${relativeFileName}"}`; + const d = Diagnostics.File_0_not_found; + const start = configFile.content.indexOf(findString); + return { + fileName: configFile.path, + start, + length: findString.length, + messageText: formatStringFromArgs(d.message, [`${getDirectoryPath(configFile.path)}/${relativeFileName}`]), + category: d.category, + code: d.code, + reportsUnnecessary: undefined + }; + } + + it("are generated when the config file has errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "foo": "bar", + "allowJS": true + } + }` + }; + const serverEventManager = new TestServerEventManager([file, libFile, configFile]); + openFilesForSession([file], serverEventManager.session); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [ + getUnknownCompilerOptionDiagnostic(configFile, "foo"), + getUnknownCompilerOptionDiagnostic(configFile, "allowJS") + ]); + }); + + it("are generated when the config file doesn't have errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {} + }` + }; + const serverEventManager = new TestServerEventManager([file, libFile, configFile]); + openFilesForSession([file], serverEventManager.session); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray); + }); + + it("are generated when the config file changes", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {} + }` + }; + + const files = [file, libFile, configFile]; + const serverEventManager = new TestServerEventManager(files); + openFilesForSession([file], serverEventManager.session); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray); + + configFile.content = `{ + "compilerOptions": { + "haha": 123 + } + }`; + serverEventManager.host.reloadFS(files); + serverEventManager.host.runQueuedTimeoutCallbacks(); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, [ + getUnknownCompilerOptionDiagnostic(configFile, "haha") + ]); + + configFile.content = `{ + "compilerOptions": {} + }`; + serverEventManager.host.reloadFS(files); + serverEventManager.host.runQueuedTimeoutCallbacks(); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, emptyArray); + }); + + it("are not generated when the config file does not include file opened and config file has errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const file2: File = { + path: "/a/b/test.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "foo": "bar", + "allowJS": true + }, + "files": ["app.ts"] + }` + }; + const serverEventManager = new TestServerEventManager([file, file2, libFile, configFile]); + openFilesForSession([file2], serverEventManager.session); + serverEventManager.hasZeroEvent("configFileDiag"); + }); + + it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "foo": "bar", + "allowJS": true + } + }` + }; + const serverEventManager = new TestServerEventManager([file, libFile, configFile], /*suppressDiagnosticEvents*/ true); + openFilesForSession([file], serverEventManager.session); + serverEventManager.hasZeroEvent("configFileDiag"); + }); + + it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const file2: File = { + path: "/a/b/test.ts", + content: "let x = 10" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "files": ["app.ts"] + }` + }; + + const serverEventManager = new TestServerEventManager([file, file2, libFile, configFile]); + openFilesForSession([file2], serverEventManager.session); + serverEventManager.hasZeroEvent("configFileDiag"); + }); + + it("contains the project reference errors", () => { + const file: File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const noSuchTsconfig = "no-such-tsconfig.json"; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "files": ["app.ts"], + "references": [{"path":"./${noSuchTsconfig}"}] + }` + }; + + const serverEventManager = new TestServerEventManager([file, libFile, configFile]); + openFilesForSession([file], serverEventManager.session); + serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [ + getFileNotFoundDiagnostic(configFile, noSuchTsconfig) + ]); + }); + }); + + describe("tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { + it("when options change", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFileContentBeforeComment = `{`; + const configFileContentComment = ` + // comment`; + const configFileContentAfterComment = ` + "compilerOptions": { + "allowJs": true, + "declaration": true + } + }`; + const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; + const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; + + const configFile = { + path: "/a/b/tsconfig.json", + content: configFileContentWithComment + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host); + openFilesForSession([file], session); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const projectName = configuredProjectAt(projectService, 0).getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + }).response as ReadonlyArray; + assert.isTrue(diags.length === 2); + + configFile.content = configFileContentWithoutCommentLine; + host.reloadFS([file, configFile]); + + const diagsAfterEdit = session.executeCommand({ + type: "request", + command: server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + }).response as ReadonlyArray; + assert.isTrue(diagsAfterEdit.length === 2); + + verifyDiagnostic(diags[0], diagsAfterEdit[0]); + verifyDiagnostic(diags[1], diagsAfterEdit[1]); + + function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) { + assert.equal(beforeEditDiag.message, afterEditDiag.message); + assert.equal(beforeEditDiag.code, afterEditDiag.code); + assert.equal(beforeEditDiag.category, afterEditDiag.category); + assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); + assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); + assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); + assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); + } + }); + }); + + describe("tsserver:: Project Errors with config file change", () => { + it("Updates diagnostics when '--noUnusedLabels' changes", () => { + const aTs: File = { path: "/a.ts", content: "label: while (1) {}" }; + const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; + const tsconfig: File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; + + const host = createServerHost([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + + host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); + host.runQueuedTimeoutCallbacks(); + + const response = executeSessionRequest(session, protocol.CommandTypes.SemanticDiagnosticsSync, { file: aTs.path }) as protocol.Diagnostic[] | undefined; + assert.deepEqual(response, [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 + "label".length }, + text: "Unused label.", + category: "error", + code: Diagnostics.Unused_label.code, + relatedInformation: undefined, + reportsUnnecessary: true, + source: undefined, + }, + ]); + }); + }); } diff --git a/src/testRunner/unittests/resolutionCache.ts b/src/testRunner/unittests/resolutionCache.ts index fbc792481fc..ab6c6830618 100644 --- a/src/testRunner/unittests/resolutionCache.ts +++ b/src/testRunner/unittests/resolutionCache.ts @@ -978,4 +978,405 @@ export const x = 10;` }); }); }); + + describe("resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { + it("should restore the states for inferred projects", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const host = createServerHost([moduleFile, file1]); + const session = createSession(host); + + openFilesForSession([file1], session); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + + const moduleFileOldPath = moduleFile.path; + const moduleFileNewPath = "/a/b/moduleFile1.ts"; + moduleFile.path = moduleFileNewPath; + host.reloadFS([moduleFile, file1]); + host.runQueuedTimeoutCallbacks(); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); + assert.equal(diags.length, 1); + + moduleFile.path = moduleFileOldPath; + host.reloadFS([moduleFile, file1]); + host.runQueuedTimeoutCallbacks(); + + // Make a change to trigger the program rebuild + const changeRequest = makeSessionRequest( + server.CommandNames.Change, + { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } + ); + session.executeCommand(changeRequest); + host.runQueuedTimeoutCallbacks(); + + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("should restore the states for configured projects", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([moduleFile, file1, configFile]); + const session = createSession(host); + + openFilesForSession([file1], session); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + + const moduleFileOldPath = moduleFile.path; + const moduleFileNewPath = "/a/b/moduleFile1.ts"; + moduleFile.path = moduleFileNewPath; + host.reloadFS([moduleFile, file1, configFile]); + host.runQueuedTimeoutCallbacks(); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); + + moduleFile.path = moduleFileOldPath; + host.reloadFS([moduleFile, file1, configFile]); + host.runQueuedTimeoutCallbacks(); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("should property handle missing config files", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const projectName = "project1"; + const host = createServerHost([f1]); + const projectService = createProjectService(host); + projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); + + // should have one external project since config file is missing + projectService.checkNumberOfProjects({ externalProjects: 1 }); + + host.reloadFS([f1, config]); + projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + }); + + it("types should load from config file path if config exists", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) + }; + const node = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: "declare var process: any" + }; + const cwd = { + path: "/a/c" + }; + const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path }); + const projectService = createProjectService(host); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]); + }); + }); + + describe("resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { + it("should remove the `module not found` error", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const host = createServerHost([file1]); + const session = createSession(host); + openFilesForSession([file1], session); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); + + host.reloadFS([file1, moduleFile]); + host.runQueuedTimeoutCallbacks(); + + // Make a change to trigger the program rebuild + const changeRequest = makeSessionRequest( + server.CommandNames.Change, + { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } + ); + session.executeCommand(changeRequest); + + // Recheck + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("npm install @types works", () => { + const folderPath = "/a/b/projects/temp"; + const file1: File = { + path: `${folderPath}/a.ts`, + content: 'import f = require("pad"); f;' + }; + const files = [file1, libFile]; + const host = createServerHost(files); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: file1.path, + fileContent: file1.content, + scriptKindName: "TS", + projectRootPath: folderPath + } + }); + checkNumberOfProjects(service, { inferredProjects: 1 }); + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file1.path] + } + }); + + host.checkTimeoutQueueLengthAndRun(1); + checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + const startOffset = file1.content.indexOf('"') + 1; + checkErrorMessage(session, "semanticDiag", { + file: file1.path, + diagnostics: [ + createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0, ["pad"]) + ], + }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + checkErrorMessage(session, "suggestionDiag", { file: file1.path, diagnostics: [] }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + + const padIndex: File = { + path: `${folderPath}/node_modules/@types/pad/index.d.ts`, + content: "export = pad;declare function pad(length: number, text: string, char ?: string): string;" + }; + files.push(padIndex); + host.reloadFS(files, { ignoreWatchInvokedWithTriggerAsFileCreate: true }); + host.runQueuedTimeoutCallbacks(); + checkProjectUpdatedInBackgroundEvent(session, [file1.path]); + session.clearMessages(); + + host.runQueuedTimeoutCallbacks(); + checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] }); + }); + + it("suggestion diagnostics", () => { + const file: File = { + path: "/a.js", + content: "function f(p) {}", + }; + + const host = createServerHost([file]); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: file.path, fileContent: file.content }, + }); + + checkNumberOfProjects(service, { inferredProjects: 1 }); + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + host.checkTimeoutQueueLengthAndRun(2); + + checkProjectUpdatedInBackgroundEvent(session, [file.path]); + session.clearMessages(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path], + } + }); + + host.checkTimeoutQueueLengthAndRun(1); + + checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "suggestionDiag", { + file: file.path, + diagnostics: [ + createDiagnostic({ line: 1, offset: 12 }, { line: 1, offset: 13 }, Diagnostics._0_is_declared_but_its_value_is_never_read, ["p"], "suggestion", /*reportsUnnecessary*/ true), + ], + }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + }); + + it("disable suggestion diagnostics", () => { + const file: File = { + path: "/a.js", + content: 'require("b")', + }; + + const host = createServerHost([file]); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: file.path, fileContent: file.content }, + }); + + session.executeCommandSeq({ + command: server.CommandNames.Configure, + arguments: { + preferences: { disableSuggestions: true } + }, + }); + + checkNumberOfProjects(service, { inferredProjects: 1 }); + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + host.checkTimeoutQueueLengthAndRun(2); + + checkProjectUpdatedInBackgroundEvent(session, [file.path]); + session.clearMessages(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path], + } + }); + + host.checkTimeoutQueueLengthAndRun(1); + + checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); + // No suggestion event, we're done. + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + }); + + it("suppressed diagnostic events", () => { + const file: File = { + path: "/a.ts", + content: "1 = 2;", + }; + + const host = createServerHost([file]); + const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true }); + const service = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: file.path, fileContent: file.content }, + }); + + checkNumberOfProjects(service, { inferredProjects: 1 }); + + host.checkTimeoutQueueLength(0); + checkNoDiagnosticEvents(session); + + session.clearMessages(); + + let expectedSequenceId = session.getNextSeq(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path], + } + }); + + host.checkTimeoutQueueLength(0); + checkNoDiagnosticEvents(session); + + checkCompleteEvent(session, 1, expectedSequenceId); + + session.clearMessages(); + + expectedSequenceId = session.getNextSeq(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + file: file.path, + } + }); + + host.checkTimeoutQueueLength(0); + checkNoDiagnosticEvents(session); + + checkCompleteEvent(session, 1, expectedSequenceId); + + session.clearMessages(); + }); + }); } diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserverHelpers.ts index 18e6f5c6194..b6154f46b71 100644 --- a/src/testRunner/unittests/tsserverHelpers.ts +++ b/src/testRunner/unittests/tsserverHelpers.ts @@ -599,23 +599,23 @@ namespace ts.projectSystem { } } - //interface ErrorInformation { - // diagnosticMessage: DiagnosticMessage; - // errorTextArguments?: string[]; - //} + export interface ErrorInformation { + diagnosticMessage: DiagnosticMessage; + errorTextArguments?: string[]; + } - //function getProtocolDiagnosticMessage({ diagnosticMessage, errorTextArguments = [] }: ErrorInformation) { - // return formatStringFromArgs(diagnosticMessage.message, errorTextArguments); - //} + function getProtocolDiagnosticMessage({ diagnosticMessage, errorTextArguments = [] }: ErrorInformation) { + return formatStringFromArgs(diagnosticMessage.message, errorTextArguments); + } - //function verifyDiagnostics(actual: server.protocol.Diagnostic[], expected: ErrorInformation[]) { - // const expectedErrors = expected.map(getProtocolDiagnosticMessage); - // assert.deepEqual(actual.map(diag => flattenDiagnosticMessageText(diag.text, "\n")), expectedErrors); - //} + export function verifyDiagnostics(actual: ReadonlyArray, expected: ReadonlyArray) { + const expectedErrors = expected.map(getProtocolDiagnosticMessage); + assert.deepEqual(actual.map(diag => flattenDiagnosticMessageText(diag.text, "\n")), expectedErrors); + } - //function verifyNoDiagnostics(actual: server.protocol.Diagnostic[]) { - // verifyDiagnostics(actual, []); - //} + export function verifyNoDiagnostics(actual: server.protocol.Diagnostic[]) { + verifyDiagnostics(actual, []); + } export function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void { checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent); @@ -629,15 +629,15 @@ namespace ts.projectSystem { checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent); } - //function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { - // checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); - //} + export function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { + checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); + } - //function checkNoDiagnosticEvents(session: TestSession) { - // for (const event of session.events) { - // assert.isFalse(event.event.endsWith("Diag"), JSON.stringify(event)); - // } - //} + export function checkNoDiagnosticEvents(session: TestSession) { + for (const event of session.events) { + assert.isFalse(event.event.endsWith("Diag"), JSON.stringify(event)); + } + } export function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) { const events = session.events; diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 0c6382f13c4..624b11d8d99 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -36,34 +36,6 @@ namespace ts.projectSystem { return documentSpanFromSubstring(file, substring, options); } - interface ErrorInformation { - diagnosticMessage: DiagnosticMessage; - errorTextArguments?: string[]; - } - - function getProtocolDiagnosticMessage({ diagnosticMessage, errorTextArguments = [] }: ErrorInformation) { - return formatStringFromArgs(diagnosticMessage.message, errorTextArguments); - } - - function verifyDiagnostics(actual: server.protocol.Diagnostic[], expected: ErrorInformation[]) { - const expectedErrors = expected.map(getProtocolDiagnosticMessage); - assert.deepEqual(actual.map(diag => flattenDiagnosticMessageText(diag.text, "\n")), expectedErrors); - } - - function verifyNoDiagnostics(actual: server.protocol.Diagnostic[]) { - verifyDiagnostics(actual, []); - } - - function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) { - checkNthEvent(session, server.toEvent("projectsUpdatedInBackground", { openFiles }), 0, /*isMostRecent*/ true); - } - - function checkNoDiagnosticEvents(session: TestSession) { - for (const event of session.events) { - assert.isFalse(event.event.endsWith("Diag"), JSON.stringify(event)); - } - } - describe("tsserverProjectSystem general functionality", () => { const commonFile1: File = { path: "/a/b/commonFile1.ts", @@ -3860,597 +3832,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem rename a module file and rename back", () => { - it("should restore the states for inferred projects", () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: "import * as T from './moduleFile'; T.bar();" - }; - const host = createServerHost([moduleFile, file1]); - const session = createSession(host); - - openFilesForSession([file1], session); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - - const moduleFileOldPath = moduleFile.path; - const moduleFileNewPath = "/a/b/moduleFile1.ts"; - moduleFile.path = moduleFileNewPath; - host.reloadFS([moduleFile, file1]); - host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } - ]); - assert.equal(diags.length, 1); - - moduleFile.path = moduleFileOldPath; - host.reloadFS([moduleFile, file1]); - host.runQueuedTimeoutCallbacks(); - - // Make a change to trigger the program rebuild - const changeRequest = makeSessionRequest( - server.CommandNames.Change, - { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } - ); - session.executeCommand(changeRequest); - host.runQueuedTimeoutCallbacks(); - - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); - - it("should restore the states for configured projects", () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: "import * as T from './moduleFile'; T.bar();" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([moduleFile, file1, configFile]); - const session = createSession(host); - - openFilesForSession([file1], session); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - - const moduleFileOldPath = moduleFile.path; - const moduleFileNewPath = "/a/b/moduleFile1.ts"; - moduleFile.path = moduleFileNewPath; - host.reloadFS([moduleFile, file1, configFile]); - host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } - ]); - - moduleFile.path = moduleFileOldPath; - host.reloadFS([moduleFile, file1, configFile]); - host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); - - it("should property handle missing config files", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const projectName = "project1"; - const host = createServerHost([f1]); - const projectService = createProjectService(host); - projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); - - // should have one external project since config file is missing - projectService.checkNumberOfProjects({ externalProjects: 1 }); - - host.reloadFS([f1, config]); - projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - }); - - it("types should load from config file path if config exists", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) - }; - const node = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: "declare var process: any" - }; - const cwd = { - path: "/a/c" - }; - const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path }); - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]); - }); - }); - - describe("tsserverProjectSystem add the missing module file for inferred project", () => { - it("should remove the `module not found` error", () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: "import * as T from './moduleFile'; T.bar();" - }; - const host = createServerHost([file1]); - const session = createSession(host); - openFilesForSession([file1], session); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } - ]); - - host.reloadFS([file1, moduleFile]); - host.runQueuedTimeoutCallbacks(); - - // Make a change to trigger the program rebuild - const changeRequest = makeSessionRequest( - server.CommandNames.Change, - { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } - ); - session.executeCommand(changeRequest); - - // Recheck - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); - - it("npm install @types works", () => { - const folderPath = "/a/b/projects/temp"; - const file1: File = { - path: `${folderPath}/a.ts`, - content: 'import f = require("pad"); f;' - }; - const files = [file1, libFile]; - const host = createServerHost(files); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: file1.path, - fileContent: file1.content, - scriptKindName: "TS", - projectRootPath: folderPath - } - }); - checkNumberOfProjects(service, { inferredProjects: 1 }); - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file1.path] - } - }); - - host.checkTimeoutQueueLengthAndRun(1); - checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - const startOffset = file1.content.indexOf('"') + 1; - checkErrorMessage(session, "semanticDiag", { - file: file1.path, - diagnostics: [ - createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0, ["pad"]) - ], - }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - checkErrorMessage(session, "suggestionDiag", { file: file1.path, diagnostics: [] }); - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - - const padIndex: File = { - path: `${folderPath}/node_modules/@types/pad/index.d.ts`, - content: "export = pad;declare function pad(length: number, text: string, char ?: string): string;" - }; - files.push(padIndex); - host.reloadFS(files, { ignoreWatchInvokedWithTriggerAsFileCreate: true }); - host.runQueuedTimeoutCallbacks(); - checkProjectUpdatedInBackgroundEvent(session, [file1.path]); - session.clearMessages(); - - host.runQueuedTimeoutCallbacks(); - checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] }); - }); - - it("suggestion diagnostics", () => { - const file: File = { - path: "/a.js", - content: "function f(p) {}", - }; - - const host = createServerHost([file]); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: file.path, fileContent: file.content }, - }); - - checkNumberOfProjects(service, { inferredProjects: 1 }); - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - host.checkTimeoutQueueLengthAndRun(2); - - checkProjectUpdatedInBackgroundEvent(session, [file.path]); - session.clearMessages(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path], - } - }); - - host.checkTimeoutQueueLengthAndRun(1); - - checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "suggestionDiag", { - file: file.path, - diagnostics: [ - createDiagnostic({ line: 1, offset: 12 }, { line: 1, offset: 13 }, Diagnostics._0_is_declared_but_its_value_is_never_read, ["p"], "suggestion", /*reportsUnnecessary*/ true), - ], - }); - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - }); - - it("disable suggestion diagnostics", () => { - const file: File = { - path: "/a.js", - content: 'require("b")', - }; - - const host = createServerHost([file]); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: file.path, fileContent: file.content }, - }); - - session.executeCommandSeq({ - command: server.CommandNames.Configure, - arguments: { - preferences: { disableSuggestions: true } - }, - }); - - checkNumberOfProjects(service, { inferredProjects: 1 }); - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - host.checkTimeoutQueueLengthAndRun(2); - - checkProjectUpdatedInBackgroundEvent(session, [file.path]); - session.clearMessages(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path], - } - }); - - host.checkTimeoutQueueLengthAndRun(1); - - checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); - // No suggestion event, we're done. - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - }); - - it("suppressed diagnostic events", () => { - const file: File = { - path: "/a.ts", - content: "1 = 2;", - }; - - const host = createServerHost([file]); - const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true }); - const service = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: file.path, fileContent: file.content }, - }); - - checkNumberOfProjects(service, { inferredProjects: 1 }); - - host.checkTimeoutQueueLength(0); - checkNoDiagnosticEvents(session); - - session.clearMessages(); - - let expectedSequenceId = session.getNextSeq(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path], - } - }); - - host.checkTimeoutQueueLength(0); - checkNoDiagnosticEvents(session); - - checkCompleteEvent(session, 1, expectedSequenceId); - - session.clearMessages(); - - expectedSequenceId = session.getNextSeq(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - file: file.path, - } - }); - - host.checkTimeoutQueueLength(0); - checkNoDiagnosticEvents(session); - - checkCompleteEvent(session, 1, expectedSequenceId); - - session.clearMessages(); - }); - }); - - describe("tsserverProjectSystem Configure file diagnostics events", () => { - function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic { - const d = Diagnostics.Unknown_compiler_option_0; - const start = configFile.content.indexOf(prop) - 1; // start at "prop" - return { - fileName: configFile.path, - start, - length: prop.length + 2, - messageText: formatStringFromArgs(d.message, [prop]), - category: d.category, - code: d.code, - reportsUnnecessary: undefined - }; - } - - function getFileNotFoundDiagnostic(configFile: File, relativeFileName: string): ConfigFileDiagnostic { - const findString = `{"path":"./${relativeFileName}"}`; - const d = Diagnostics.File_0_not_found; - const start = configFile.content.indexOf(findString); - return { - fileName: configFile.path, - start, - length: findString.length, - messageText: formatStringFromArgs(d.message, [`${getDirectoryPath(configFile.path)}/${relativeFileName}`]), - category: d.category, - code: d.code, - reportsUnnecessary: undefined - }; - } - - it("are generated when the config file has errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ - "compilerOptions": { - "foo": "bar", - "allowJS": true - } - }` - }; - const serverEventManager = new TestServerEventManager([file, libFile, configFile]); - openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [ - getUnknownCompilerOptionDiagnostic(configFile, "foo"), - getUnknownCompilerOptionDiagnostic(configFile, "allowJS") - ]); - }); - - it("are generated when the config file doesn't have errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ - "compilerOptions": {} - }` - }; - const serverEventManager = new TestServerEventManager([file, libFile, configFile]); - openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray); - }); - - it("are generated when the config file changes", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{ - "compilerOptions": {} - }` - }; - - const files = [file, libFile, configFile]; - const serverEventManager = new TestServerEventManager(files); - openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, emptyArray); - - configFile.content = `{ - "compilerOptions": { - "haha": 123 - } - }`; - serverEventManager.host.reloadFS(files); - serverEventManager.host.runQueuedTimeoutCallbacks(); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, [ - getUnknownCompilerOptionDiagnostic(configFile, "haha") - ]); - - configFile.content = `{ - "compilerOptions": {} - }`; - serverEventManager.host.reloadFS(files); - serverEventManager.host.runQueuedTimeoutCallbacks(); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, configFile.path, emptyArray); - }); - - it("are not generated when the config file does not include file opened and config file has errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const file2: File = { - path: "/a/b/test.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ - "compilerOptions": { - "foo": "bar", - "allowJS": true - }, - "files": ["app.ts"] - }` - }; - const serverEventManager = new TestServerEventManager([file, file2, libFile, configFile]); - openFilesForSession([file2], serverEventManager.session); - serverEventManager.hasZeroEvent("configFileDiag"); - }); - - it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ - "compilerOptions": { - "foo": "bar", - "allowJS": true - } - }` - }; - const serverEventManager = new TestServerEventManager([file, libFile, configFile], /*suppressDiagnosticEvents*/ true); - openFilesForSession([file], serverEventManager.session); - serverEventManager.hasZeroEvent("configFileDiag"); - }); - - it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const file2: File = { - path: "/a/b/test.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ - "files": ["app.ts"] - }` - }; - - const serverEventManager = new TestServerEventManager([file, file2, libFile, configFile]); - openFilesForSession([file2], serverEventManager.session); - serverEventManager.hasZeroEvent("configFileDiag"); - }); - - it("contains the project reference errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const noSuchTsconfig = "no-such-tsconfig.json"; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ - "files": ["app.ts"], - "references": [{"path":"./${noSuchTsconfig}"}] - }` - }; - - const serverEventManager = new TestServerEventManager([file, libFile, configFile]); - openFilesForSession([file], serverEventManager.session); - serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [ - getFileNotFoundDiagnostic(configFile, noSuchTsconfig) - ]); - }); - }); - describe("tsserverProjectSystem skipLibCheck", () => { it("should be turned on for js-only inferred projects", () => { const file1 = { @@ -5698,70 +5079,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem Options Diagnostic locations reported correctly with changes in configFile contents", () => { - it("when options change", () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFileContentBeforeComment = `{`; - const configFileContentComment = ` - // comment`; - const configFileContentAfterComment = ` - "compilerOptions": { - "allowJs": true, - "declaration": true - } - }`; - const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; - const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; - - const configFile = { - path: "/a/b/tsconfig.json", - content: configFileContentWithComment - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host); - openFilesForSession([file], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const projectName = configuredProjectAt(projectService, 0).getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.SemanticDiagnosticsSync, - seq: 2, - arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } - }).response as ReadonlyArray; - assert.isTrue(diags.length === 2); - - configFile.content = configFileContentWithoutCommentLine; - host.reloadFS([file, configFile]); - - const diagsAfterEdit = session.executeCommand({ - type: "request", - command: server.CommandNames.SemanticDiagnosticsSync, - seq: 2, - arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } - }).response as ReadonlyArray; - assert.isTrue(diagsAfterEdit.length === 2); - - verifyDiagnostic(diags[0], diagsAfterEdit[0]); - verifyDiagnostic(diags[1], diagsAfterEdit[1]); - - function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) { - assert.equal(beforeEditDiag.message, afterEditDiag.message); - assert.equal(beforeEditDiag.code, afterEditDiag.code); - assert.equal(beforeEditDiag.category, afterEditDiag.category); - assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); - assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); - assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); - assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); - } - }); - }); - describe("tsserverProjectSystem refactors", () => { it("use formatting options", () => { const file = { @@ -8735,35 +8052,6 @@ fn5(); }); }); - describe("tsserverProjectSystem config file change", () => { - it("Updates diagnostics when '--noUnusedLabels' changes", () => { - const aTs: File = { path: "/a.ts", content: "label: while (1) {}" }; - const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; - const tsconfig: File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; - - const host = createServerHost([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - - host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); - host.runQueuedTimeoutCallbacks(); - - const response = executeSessionRequest(session, protocol.CommandTypes.SemanticDiagnosticsSync, { file: aTs.path }) as protocol.Diagnostic[] | undefined; - assert.deepEqual(response, [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 + "label".length }, - text: "Unused label.", - category: "error", - code: Diagnostics.Unused_label.code, - relatedInformation: undefined, - reportsUnnecessary: true, - source: undefined, - }, - ]); - }); - }); - describe("tsserverProjectSystem with metadata in response", () => { const metadata = "Extra Info"; function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { From 160c73f9420bcc9dfa556d2aef70850ec809f19a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 13:12:14 -0800 Subject: [PATCH 086/120] Separate out caching file system test --- src/testRunner/tsconfig.json | 1 + .../tsserverCachingFileSystemInformation.ts | 704 +++++++++++++++++ src/testRunner/unittests/tsserverHelpers.ts | 10 +- .../unittests/tsserverProjectSystem.ts | 707 ------------------ 4 files changed, 708 insertions(+), 714 deletions(-) create mode 100644 src/testRunner/unittests/tsserverCachingFileSystemInformation.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index ba46706ee16..daad5a29b6a 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -88,6 +88,7 @@ "unittests/tsconfigParsing.ts", "unittests/tscWatchEmit.ts", "unittests/tscWatchMode.ts", + "unittests/tsserverCachingFileSystemInformation.ts", "unittests/tsserverLargeFileReferencedEvent.ts", "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", diff --git a/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts b/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts new file mode 100644 index 00000000000..65e1ec564d3 --- /dev/null +++ b/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts @@ -0,0 +1,704 @@ +namespace ts.projectSystem { + function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { + return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); + } + + describe("tsserverCachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { + enum CalledMapsWithSingleArg { + fileExists = "fileExists", + directoryExists = "directoryExists", + getDirectories = "getDirectories", + readFile = "readFile" + } + enum CalledMapsWithFiveArgs { + readDirectory = "readDirectory" + } + type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; + function createCallsTrackingHost(host: TestServerHost) { + const calledMaps: Record> & Record, ReadonlyArray, ReadonlyArray, number]>> = { + fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), + directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), + getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories), + readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile), + readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) + }; + + return { + verifyNoCall, + verifyCalledOnEachEntryNTimes, + verifyCalledOnEachEntry, + verifyNoHostCalls, + verifyNoHostCallsExceptFileExistsOnce, + verifyCalledOn, + clear + }; + + function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { + const calledMap = createMultiMap(); + const cb = (host)[prop].bind(host); + (host)[prop] = (f: string) => { + calledMap.add(f, /*value*/ true); + return cb(f); + }; + return calledMap; + } + + function setCallsTrackingWithFiveArgFn(prop: CalledMapsWithFiveArgs) { + const calledMap = createMultiMap<[U, V, W, X]>(); + const cb = (host)[prop].bind(host); + (host)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { + calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217 + return cb(f, arg1, arg2, arg3, arg4); + }; + return calledMap; + } + + function verifyCalledOn(callback: CalledMaps, name: string) { + const calledMap = calledMaps[callback]; + const result = calledMap.get(name); + assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); + } + + function verifyNoCall(callback: CalledMaps) { + const calledMap = calledMaps[callback]; + assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`); + } + + function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map) { + TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys); + } + + function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: ReadonlyArray, nTimes: number) { + TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys, nTimes); + } + + function verifyNoHostCalls() { + iterateOnCalledMaps(key => verifyNoCall(key)); + } + + function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: ReadonlyArray) { + verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1); + verifyNoCall(CalledMapsWithSingleArg.directoryExists); + verifyNoCall(CalledMapsWithSingleArg.getDirectories); + verifyNoCall(CalledMapsWithSingleArg.readFile); + verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + } + + function clear() { + iterateOnCalledMaps(key => calledMaps[key].clear()); + } + + function iterateOnCalledMaps(cb: (key: CalledMaps) => void) { + for (const key in CalledMapsWithSingleArg) { + cb(key as CalledMapsWithSingleArg); + } + for (const key in CalledMapsWithFiveArgs) { + cb(key as CalledMapsWithFiveArgs); + } + } + } + + it("works using legacy resolution logic", () => { + let rootContent = `import {x} from "f1"`; + const root: File = { + path: "/c/d/f0.ts", + content: rootContent + }; + + const imported: File = { + path: "/c/f1.ts", + content: `foo()` + }; + + const host = createServerHost([root, imported]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); + projectService.openClientFile(root.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + // ensure that imported file was found + verifyImportedDiagnostics(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // trigger synchronization to make sure that import will be fetched from the cache + // ensure file has correct number of errors after edit + editContent(`import {x} from "f1"; + var x: string = 1;`); + verifyImportedDiagnostics(); + callsTrackingHost.verifyNoHostCalls(); + + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + editContent(`import {x} from "f2"`); + try { + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + verifyImportedDiagnostics(); + assert.isTrue(false, `should not find file '${imported.path}'`); + } + catch (e) { + assert.isTrue(e.message.indexOf(`Could not find file: '${imported.path}'.`) === 0); + } + const f2Lookups = getLocationsForModuleLookup("f2"); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); + const f2DirLookups = getLocationsForDirectoryLookup(); + callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); + callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + + editContent(`import {x} from "f1"`); + verifyImportedDiagnostics(); + const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); + f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; + const f1DirLookups = ["/c/d", "/c", ...mapCombinedPathsInAncestor(getDirectoryPath(root.path), nodeModulesAtTypes, returnTrue)]; + vertifyF1Lookups(); + + // setting compiler options discards module resolution cache + callsTrackingHost.clear(); + projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 }); + verifyImportedDiagnostics(); + vertifyF1Lookups(); + + function vertifyF1Lookups() { + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f1Lookups, 1); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, f1DirLookups, 1); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); + callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + } + + function editContent(newContent: string) { + callsTrackingHost.clear(); + rootScriptInfo.editContent(0, rootContent.length, newContent); + rootContent = newContent; + } + + function verifyImportedDiagnostics() { + const diags = project.getLanguageService().getSemanticDiagnostics(imported.path); + assert.equal(diags.length, 1); + const diag = diags[0]; + assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code); + assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'."); + } + + function getLocationsForModuleLookup(module: string) { + const locations: string[] = []; + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + locations.push( + combinePaths(ancestor, `${module}.ts`), + combinePaths(ancestor, `${module}.tsx`), + combinePaths(ancestor, `${module}.d.ts`) + ); + }); + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + locations.push( + combinePaths(ancestor, `${module}.js`), + combinePaths(ancestor, `${module}.jsx`) + ); + }); + return locations; + } + + function getLocationsForDirectoryLookup() { + const result = createMap(); + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + // To resolve modules + result.set(ancestor, 2); + // for type roots + result.set(combinePaths(ancestor, nodeModules), 1); + result.set(combinePaths(ancestor, nodeModulesAtTypes), 1); + }); + return result; + } + }); + + it("loads missing files from disk", () => { + const root: File = { + path: "/c/foo.ts", + content: `import {y} from "bar"` + }; + + const imported: File = { + path: "/c/bar.d.ts", + content: `export var y = 1` + }; + + const host = createServerHost([root]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); + const callsTrackingHost = createCallsTrackingHost(host); + projectService.openClientFile(root.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + let diags = project.getLanguageService().getSemanticDiagnostics(root.path); + assert.equal(diags.length, 1); + const diag = diags[0]; + assert.equal(diag.code, Diagnostics.Cannot_find_module_0.code); + assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'."); + callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); + + + callsTrackingHost.clear(); + host.reloadFS([root, imported]); + host.runQueuedTimeoutCallbacks(); + diags = project.getLanguageService().getSemanticDiagnostics(root.path); + assert.equal(diags.length, 0); + callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); + }); + + it("when calling goto definition of module", () => { + const clientFile: File = { + path: "/a/b/controllers/vessels/client.ts", + content: ` + import { Vessel } from '~/models/vessel'; + const v = new Vessel(); + ` + }; + const anotherModuleFile: File = { + path: "/a/b/utils/db.ts", + content: "export class Bookshelf { }" + }; + const moduleFile: File = { + path: "/a/b/models/vessel.ts", + content: ` + import { Bookshelf } from '~/utils/db'; + export class Vessel extends Bookshelf {} + ` + }; + const tsconfigFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + target: "es6", + module: "es6", + baseUrl: "./", // all paths are relative to the baseUrl + paths: { + "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` + } + }, + exclude: [ + "api", + "build", + "node_modules", + "public", + "seeds", + "sql_updates", + "tests.build" + ] + }) + }; + const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; + const host = createServerHost(projectFiles); + const session = createSession(host); + const projectService = session.getProjectService(); + const { configFileName } = projectService.openClientFile(clientFile.path); + + assert.isDefined(configFileName, `should find config`); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = projectService.configuredProjects.get(tsconfigFile.path)!; + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + + const callsTrackingHost = createCallsTrackingHost(host); + + // Get definitions shouldnt make host requests + const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { + file: clientFile.path, + position: clientFile.content.indexOf("/vessel") + 1, + line: undefined!, // TODO: GH#18217 + offset: undefined! // TODO: GH#18217 + }); + const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[]; + assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); + callsTrackingHost.verifyNoHostCalls(); + + // Open the file should call only file exists on module directory and use cached value for parental directory + const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); + assert.equal(config2, configFileName); + callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); + }); + + describe("WatchDirectories for config file with", () => { + function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; + const canonicalFrontendDir = toCanonical(frontendDir); + const file1: File = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" + }; + const file2: File = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" + }; + const file3: File = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" + }; + const es2016LibFile: File = { + path: "/a/lib/lib.es2016.full.d.ts", + content: libFile.content + }; + const typeRoots = ["types", "node_modules/@types"]; + const types = ["node", "jest"]; + const tsconfigFile: File = { + path: `${frontendDir}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + strict: true, + strictNullChecks: true, + target: "es2016", + module: "commonjs", + moduleResolution: "node", + sourceMap: true, + noEmitOnError: true, + experimentalDecorators: true, + emitDecoratorMetadata: true, + types, + noUnusedLocals: true, + outDir: "./compiled", + typeRoots, + baseUrl: ".", + paths: { + "*": [ + "types/*" + ] + } + }, + include: [ + "src/**/*" + ], + exclude: [ + "node_modules", + "compiled" + ] + }) + }; + const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; + const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host); + const canonicalConfigPath = toCanonical(tsconfigFile.path); + const { configFileName } = projectService.openClientFile(file1.path); + assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217) + checkNumberOfConfiguredProjects(projectService, 1); + const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir))); + + const project = projectService.configuredProjects.get(canonicalConfigPath)!; + verifyProjectAndWatchedDirectories(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // Create file cookie.ts + projectFiles.push(file3); + host.reloadFS(projectFiles); + host.runQueuedTimeoutCallbacks(); + + const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); + const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, [canonicalFile3Path], numberOfTimesWatchInvoked); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, [canonicalFile3Path], numberOfTimesWatchInvoked); + callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); + callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.readFile, [file3.path], 1); + callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + + callsTrackingHost.clear(); + + const { configFileName: configFile2 } = projectService.openClientFile(file3.path); + assert.equal(configFile2, configFileName); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + callsTrackingHost.verifyNoHostCalls(); + + function getFilePathIfNotOpen(f: File) { + const path = toCanonical(f.path); + const info = projectService.getScriptInfoForPath(toCanonical(f.path)); + return info && info.isScriptOpen() ? undefined : path; + } + + function verifyProjectAndWatchedDirectories() { + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); + checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); + } + } + + it("case insensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); + + it("case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); + }); + + describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { + function runFailedLookupTest(resolution: "Node" | "Classic") { + const projectLocation = "/proj"; + const file1: File = { + path: `${projectLocation}/foo/boo/app.ts`, + content: `import * as debug from "debug"` + }; + const file2: File = { + path: `${projectLocation}/foo/boo/moo/app.ts`, + content: `import * as debug from "debug"` + }; + const tsconfig: File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"], + moduleResolution: resolution + }) + }; + + const files = [file1, file2, tsconfig, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(file1.path); + + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]); + + const debugTypesFile: File = { + path: `${projectLocation}/node_modules/debug/index.d.ts`, + content: "export {}" + }; + files.push(debugTypesFile); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []); + } + + it("Includes the parent folder FLLs in node module resolution mode", () => { + runFailedLookupTest("Node"); + }); + it("Includes the parent folder FLLs in classic module resolution mode", () => { + runFailedLookupTest("Classic"); + }); + }); + + describe("Verify npm install in directory with tsconfig file works when", () => { + function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { + const root = "/user/username/rootfolder/otherfolder"; + const getRootedFileOrFolder = (fileOrFolder: File) => { + fileOrFolder.path = root + fileOrFolder.path; + return fileOrFolder; + }; + const app: File = getRootedFileOrFolder({ + path: "/a/b/app.ts", + content: "import _ from 'lodash';" + }); + const tsconfigJson: File = getRootedFileOrFolder({ + path: "/a/b/tsconfig.json", + content: '{ "compilerOptions": { } }' + }); + const packageJson: File = getRootedFileOrFolder({ + path: "/a/b/package.json", + content: ` +{ + "name": "test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "lodash", + "rxjs" + }, + "devDependencies": { + "@types/lodash", + "typescript" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} +` + }); + const appFolder = getDirectoryPath(app.path); + const projectFiles = [app, libFile, tsconfigJson]; + const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path)); + const otherFiles = [packageJson]; + const host = createServerHost(projectFiles.concat(otherFiles)); + const projectService = createProjectService(host); + const { configFileName } = projectService.openClientFile(app.path); + assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217 + const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder))); + verifyProject(); + + let npmInstallComplete = false; + + // Simulate npm install + const filesAndFoldersToAdd: File[] = [ + { path: "/a/b/node_modules" }, + { path: "/a/b/node_modules/.staging/@types" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"tslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"tslint\": \"latest\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, + ].map(getRootedFileOrFolder); + verifyAfterPartialOrCompleteNpmInstall(2); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" } + ].map(getRootedFileOrFolder)); + // Since we added/removed in .staging no timeout + verifyAfterPartialOrCompleteNpmInstall(0); + + // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" + filesAndFoldersToAdd.length--; + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } + ].map(getRootedFileOrFolder)); + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"tslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"tslint --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } + ].map(getRootedFileOrFolder)); + verifyAfterPartialOrCompleteNpmInstall(0); + + // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 + filesAndFoldersToAdd.length--; + // and add few more folders/files + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/symbol-observable" }, + { path: "/a/b/node_modules/@types" }, + { path: "/a/b/node_modules/@types/lodash" }, + { path: "/a/b/node_modules/lodash" }, + { path: "/a/b/node_modules/rxjs" }, + { path: "/a/b/node_modules/typescript" }, + { path: "/a/b/node_modules/.bin" } + ].map(getRootedFileOrFolder)); + // From the type root update + verifyAfterPartialOrCompleteNpmInstall(2); + + forEach(filesAndFoldersToAdd, f => { + f.path = f.path + .replace("/a/b/node_modules/.staging", "/a/b/node_modules") + .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); + }); + + const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts"; + projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!); + // we would now not have failed lookup in the parent of appFolder since lodash is available + recursiveWatchedDirectories.length = 2; + // npm installation complete, timeout after reload fs + npmInstallComplete = true; + verifyAfterPartialOrCompleteNpmInstall(2); + + function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { + host.reloadFS(projectFiles.concat(otherFiles, filesAndFoldersToAdd)); + if (npmInstallComplete || timeoutDuringPartialInstallation) { + host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); + } + else { + host.checkTimeoutQueueLength(2); + } + verifyProject(); + } + + function verifyProject() { + checkNumberOfConfiguredProjects(projectService, 1); + + const project = projectService.configuredProjects.get(tsconfigJson.path)!; + const projectFilePaths = map(projectFiles, f => f.path); + checkProjectActualFiles(project, projectFilePaths); + + const filesWatched = filter(projectFilePaths, p => p !== app.path && p.indexOf("/a/b/node_modules") === -1); + checkWatchedFiles(host, filesWatched); + checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); + } + } + + it("timeouts occur inbetween installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); + }); + + it("timeout occurs after installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); + }); + }); + + it("when node_modules dont receive event for the @types file addition", () => { + const projectLocation = "/user/username/folder/myproject"; + const app: File = { + path: `${projectLocation}/app.ts`, + content: `import * as debug from "debug"` + }; + const tsconfig: File = { + path: `${projectLocation}/tsconfig.json`, + content: "" + }; + + const files = [app, tsconfig, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(app.path); + + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]); + + const debugTypesFile: File = { + path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, + content: "export {}" + }; + files.push(debugTypesFile); + // Do not invoke recursive directory watcher for anything other than node_module/@types + const invoker = host.invokeWatchedDirectoriesRecursiveCallback; + host.invokeWatchedDirectoriesRecursiveCallback = (fullPath, relativePath) => { + if (fullPath.endsWith("@types")) { + invoker.call(host, fullPath, relativePath); + } + }; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserverHelpers.ts index b6154f46b71..f042a43738a 100644 --- a/src/testRunner/unittests/tsserverHelpers.ts +++ b/src/testRunner/unittests/tsserverHelpers.ts @@ -450,19 +450,15 @@ namespace ts.projectSystem { } export const nodeModules = "node_modules"; - //function getNodeModuleDirectories(dir: string) { - // return getRootsToWatchWithAncestorDirectory(dir, nodeModules); - //} + export function getNodeModuleDirectories(dir: string) { + return getRootsToWatchWithAncestorDirectory(dir, nodeModules); + } export const nodeModulesAtTypes = "node_modules/@types"; export function getTypeRootsFromLocation(currentDirectory: string) { return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); } - //function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { - // return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); - //} - //function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { // checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); //} diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 624b11d8d99..f30e8ad5d9c 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -1,12 +1,4 @@ namespace ts.projectSystem { - function getNodeModuleDirectories(dir: string) { - return getRootsToWatchWithAncestorDirectory(dir, nodeModules); - } - - function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { - return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); - } - function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); } @@ -5198,705 +5190,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem CachingFileSystemInformation", () => { - enum CalledMapsWithSingleArg { - fileExists = "fileExists", - directoryExists = "directoryExists", - getDirectories = "getDirectories", - readFile = "readFile" - } - enum CalledMapsWithFiveArgs { - readDirectory = "readDirectory" - } - type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; - function createCallsTrackingHost(host: TestServerHost) { - const calledMaps: Record> & Record, ReadonlyArray, ReadonlyArray, number]>> = { - fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), - directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), - getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories), - readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile), - readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) - }; - - return { - verifyNoCall, - verifyCalledOnEachEntryNTimes, - verifyCalledOnEachEntry, - verifyNoHostCalls, - verifyNoHostCallsExceptFileExistsOnce, - verifyCalledOn, - clear - }; - - function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { - const calledMap = createMultiMap(); - const cb = (host)[prop].bind(host); - (host)[prop] = (f: string) => { - calledMap.add(f, /*value*/ true); - return cb(f); - }; - return calledMap; - } - - function setCallsTrackingWithFiveArgFn(prop: CalledMapsWithFiveArgs) { - const calledMap = createMultiMap<[U, V, W, X]>(); - const cb = (host)[prop].bind(host); - (host)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { - calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217 - return cb(f, arg1, arg2, arg3, arg4); - }; - return calledMap; - } - - function verifyCalledOn(callback: CalledMaps, name: string) { - const calledMap = calledMaps[callback]; - const result = calledMap.get(name); - assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); - } - - function verifyNoCall(callback: CalledMaps) { - const calledMap = calledMaps[callback]; - assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`); - } - - function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map) { - TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys); - } - - function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: ReadonlyArray, nTimes: number) { - TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys, nTimes); - } - - function verifyNoHostCalls() { - iterateOnCalledMaps(key => verifyNoCall(key)); - } - - function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: ReadonlyArray) { - verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1); - verifyNoCall(CalledMapsWithSingleArg.directoryExists); - verifyNoCall(CalledMapsWithSingleArg.getDirectories); - verifyNoCall(CalledMapsWithSingleArg.readFile); - verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - } - - function clear() { - iterateOnCalledMaps(key => calledMaps[key].clear()); - } - - function iterateOnCalledMaps(cb: (key: CalledMaps) => void) { - for (const key in CalledMapsWithSingleArg) { - cb(key as CalledMapsWithSingleArg); - } - for (const key in CalledMapsWithFiveArgs) { - cb(key as CalledMapsWithFiveArgs); - } - } - } - - it("works using legacy resolution logic", () => { - let rootContent = `import {x} from "f1"`; - const root: File = { - path: "/c/d/f0.ts", - content: rootContent - }; - - const imported: File = { - path: "/c/f1.ts", - content: `foo()` - }; - - const host = createServerHost([root, imported]); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - projectService.openClientFile(root.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - const rootScriptInfo = project.getRootScriptInfos()[0]; - assert.equal(rootScriptInfo.fileName, root.path); - - // ensure that imported file was found - verifyImportedDiagnostics(); - - const callsTrackingHost = createCallsTrackingHost(host); - - // trigger synchronization to make sure that import will be fetched from the cache - // ensure file has correct number of errors after edit - editContent(`import {x} from "f1"; - var x: string = 1;`); - verifyImportedDiagnostics(); - callsTrackingHost.verifyNoHostCalls(); - - // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk - editContent(`import {x} from "f2"`); - try { - // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk - verifyImportedDiagnostics(); - assert.isTrue(false, `should not find file '${imported.path}'`); - } - catch (e) { - assert.isTrue(e.message.indexOf(`Could not find file: '${imported.path}'.`) === 0); - } - const f2Lookups = getLocationsForModuleLookup("f2"); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); - const f2DirLookups = getLocationsForDirectoryLookup(); - callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - - editContent(`import {x} from "f1"`); - verifyImportedDiagnostics(); - const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); - f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; - const f1DirLookups = ["/c/d", "/c", ...mapCombinedPathsInAncestor(getDirectoryPath(root.path), nodeModulesAtTypes, returnTrue)]; - vertifyF1Lookups(); - - // setting compiler options discards module resolution cache - callsTrackingHost.clear(); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 }); - verifyImportedDiagnostics(); - vertifyF1Lookups(); - - function vertifyF1Lookups() { - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f1Lookups, 1); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, f1DirLookups, 1); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.readFile); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - } - - function editContent(newContent: string) { - callsTrackingHost.clear(); - rootScriptInfo.editContent(0, rootContent.length, newContent); - rootContent = newContent; - } - - function verifyImportedDiagnostics() { - const diags = project.getLanguageService().getSemanticDiagnostics(imported.path); - assert.equal(diags.length, 1); - const diag = diags[0]; - assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code); - assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'."); - } - - function getLocationsForModuleLookup(module: string) { - const locations: string[] = []; - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - locations.push( - combinePaths(ancestor, `${module}.ts`), - combinePaths(ancestor, `${module}.tsx`), - combinePaths(ancestor, `${module}.d.ts`) - ); - }); - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - locations.push( - combinePaths(ancestor, `${module}.js`), - combinePaths(ancestor, `${module}.jsx`) - ); - }); - return locations; - } - - function getLocationsForDirectoryLookup() { - const result = createMap(); - forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { - // To resolve modules - result.set(ancestor, 2); - // for type roots - result.set(combinePaths(ancestor, nodeModules), 1); - result.set(combinePaths(ancestor, nodeModulesAtTypes), 1); - }); - return result; - } - }); - - it("loads missing files from disk", () => { - const root: File = { - path: "/c/foo.ts", - content: `import {y} from "bar"` - }; - - const imported: File = { - path: "/c/bar.d.ts", - content: `export var y = 1` - }; - - const host = createServerHost([root]); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - const callsTrackingHost = createCallsTrackingHost(host); - projectService.openClientFile(root.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - const rootScriptInfo = project.getRootScriptInfos()[0]; - assert.equal(rootScriptInfo.fileName, root.path); - - let diags = project.getLanguageService().getSemanticDiagnostics(root.path); - assert.equal(diags.length, 1); - const diag = diags[0]; - assert.equal(diag.code, Diagnostics.Cannot_find_module_0.code); - assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'."); - callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); - - - callsTrackingHost.clear(); - host.reloadFS([root, imported]); - host.runQueuedTimeoutCallbacks(); - diags = project.getLanguageService().getSemanticDiagnostics(root.path); - assert.equal(diags.length, 0); - callsTrackingHost.verifyCalledOn(CalledMapsWithSingleArg.fileExists, imported.path); - }); - - it("when calling goto definition of module", () => { - const clientFile: File = { - path: "/a/b/controllers/vessels/client.ts", - content: ` - import { Vessel } from '~/models/vessel'; - const v = new Vessel(); - ` - }; - const anotherModuleFile: File = { - path: "/a/b/utils/db.ts", - content: "export class Bookshelf { }" - }; - const moduleFile: File = { - path: "/a/b/models/vessel.ts", - content: ` - import { Bookshelf } from '~/utils/db'; - export class Vessel extends Bookshelf {} - ` - }; - const tsconfigFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - target: "es6", - module: "es6", - baseUrl: "./", // all paths are relative to the baseUrl - paths: { - "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` - } - }, - exclude: [ - "api", - "build", - "node_modules", - "public", - "seeds", - "sql_updates", - "tests.build" - ] - }) - }; - const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; - const host = createServerHost(projectFiles); - const session = createSession(host); - const projectService = session.getProjectService(); - const { configFileName } = projectService.openClientFile(clientFile.path); - - assert.isDefined(configFileName, `should find config`); - checkNumberOfConfiguredProjects(projectService, 1); - - const project = projectService.configuredProjects.get(tsconfigFile.path)!; - checkProjectActualFiles(project, map(projectFiles, f => f.path)); - - const callsTrackingHost = createCallsTrackingHost(host); - - // Get definitions shouldnt make host requests - const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { - file: clientFile.path, - position: clientFile.content.indexOf("/vessel") + 1, - line: undefined!, // TODO: GH#18217 - offset: undefined! // TODO: GH#18217 - }); - const response = session.executeCommand(getDefinitionRequest).response as server.protocol.FileSpan[]; - assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); - callsTrackingHost.verifyNoHostCalls(); - - // Open the file should call only file exists on module directory and use cached value for parental directory - const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); - assert.equal(config2, configFileName); - callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); - }); - - describe("WatchDirectories for config file with", () => { - function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const frontendDir = "/Users/someuser/work/applications/frontend"; - const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; - const canonicalFrontendDir = toCanonical(frontendDir); - const file1: File = { - path: `${frontendDir}/src/app/utils/Analytic.ts`, - content: "export class SomeClass { };" - }; - const file2: File = { - path: `${frontendDir}/src/app/redux/configureStore.ts`, - content: "export class configureStore { }" - }; - const file3: File = { - path: `${frontendDir}/src/app/utils/Cookie.ts`, - content: "export class Cookie { }" - }; - const es2016LibFile: File = { - path: "/a/lib/lib.es2016.full.d.ts", - content: libFile.content - }; - const typeRoots = ["types", "node_modules/@types"]; - const types = ["node", "jest"]; - const tsconfigFile: File = { - path: `${frontendDir}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - strict: true, - strictNullChecks: true, - target: "es2016", - module: "commonjs", - moduleResolution: "node", - sourceMap: true, - noEmitOnError: true, - experimentalDecorators: true, - emitDecoratorMetadata: true, - types, - noUnusedLocals: true, - outDir: "./compiled", - typeRoots, - baseUrl: ".", - paths: { - "*": [ - "types/*" - ] - } - }, - include: [ - "src/**/*" - ], - exclude: [ - "node_modules", - "compiled" - ] - }) - }; - const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; - const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host); - const canonicalConfigPath = toCanonical(tsconfigFile.path); - const { configFileName } = projectService.openClientFile(file1.path); - assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217) - checkNumberOfConfiguredProjects(projectService, 1); - const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir))); - - const project = projectService.configuredProjects.get(canonicalConfigPath)!; - verifyProjectAndWatchedDirectories(); - - const callsTrackingHost = createCallsTrackingHost(host); - - // Create file cookie.ts - projectFiles.push(file3); - host.reloadFS(projectFiles); - host.runQueuedTimeoutCallbacks(); - - const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); - const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, [canonicalFile3Path], numberOfTimesWatchInvoked); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.directoryExists, [canonicalFile3Path], numberOfTimesWatchInvoked); - callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); - callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.readFile, [file3.path], 1); - callsTrackingHost.verifyNoCall(CalledMapsWithFiveArgs.readDirectory); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - - callsTrackingHost.clear(); - - const { configFileName: configFile2 } = projectService.openClientFile(file3.path); - assert.equal(configFile2, configFileName); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - callsTrackingHost.verifyNoHostCalls(); - - function getFilePathIfNotOpen(f: File) { - const path = toCanonical(f.path); - const info = projectService.getScriptInfoForPath(toCanonical(f.path)); - return info && info.isScriptOpen() ? undefined : path; - } - - function verifyProjectAndWatchedDirectories() { - checkProjectActualFiles(project, map(projectFiles, f => f.path)); - checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); - checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); - } - } - - it("case insensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - }); - - it("case sensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); - }); - - describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { - function runFailedLookupTest(resolution: "Node" | "Classic") { - const projectLocation = "/proj"; - const file1: File = { - path: `${projectLocation}/foo/boo/app.ts`, - content: `import * as debug from "debug"` - }; - const file2: File = { - path: `${projectLocation}/foo/boo/moo/app.ts`, - content: `import * as debug from "debug"` - }; - const tsconfig: File = { - path: `${projectLocation}/tsconfig.json`, - content: JSON.stringify({ - files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"], - moduleResolution: resolution - }) - }; - - const files = [file1, file2, tsconfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(file1.path); - - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]); - - const debugTypesFile: File = { - path: `${projectLocation}/node_modules/debug/index.d.ts`, - content: "export {}" - }; - files.push(debugTypesFile); - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []); - } - - it("Includes the parent folder FLLs in node module resolution mode", () => { - runFailedLookupTest("Node"); - }); - it("Includes the parent folder FLLs in classic module resolution mode", () => { - runFailedLookupTest("Classic"); - }); - }); - - describe("Verify npm install in directory with tsconfig file works when", () => { - function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { - const root = "/user/username/rootfolder/otherfolder"; - const getRootedFileOrFolder = (fileOrFolder: File) => { - fileOrFolder.path = root + fileOrFolder.path; - return fileOrFolder; - }; - const app: File = getRootedFileOrFolder({ - path: "/a/b/app.ts", - content: "import _ from 'lodash';" - }); - const tsconfigJson: File = getRootedFileOrFolder({ - path: "/a/b/tsconfig.json", - content: '{ "compilerOptions": { } }' - }); - const packageJson: File = getRootedFileOrFolder({ - path: "/a/b/package.json", - content: ` -{ - "name": "test", - "version": "1.0.0", - "description": "", - "main": "index.js", - "dependencies": { - "lodash", - "rxjs" - }, - "devDependencies": { - "@types/lodash", - "typescript" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC" -} -` - }); - const appFolder = getDirectoryPath(app.path); - const projectFiles = [app, libFile, tsconfigJson]; - const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path)); - const otherFiles = [packageJson]; - const host = createServerHost(projectFiles.concat(otherFiles)); - const projectService = createProjectService(host); - const { configFileName } = projectService.openClientFile(app.path); - assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217 - const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder))); - verifyProject(); - - let npmInstallComplete = false; - - // Simulate npm install - const filesAndFoldersToAdd: File[] = [ - { path: "/a/b/node_modules" }, - { path: "/a/b/node_modules/.staging/@types" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"tslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"tslint\": \"latest\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, - ].map(getRootedFileOrFolder); - verifyAfterPartialOrCompleteNpmInstall(2); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" } - ].map(getRootedFileOrFolder)); - // Since we added/removed in .staging no timeout - verifyAfterPartialOrCompleteNpmInstall(0); - - // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" - filesAndFoldersToAdd.length--; - verifyAfterPartialOrCompleteNpmInstall(0); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } - ].map(getRootedFileOrFolder)); - verifyAfterPartialOrCompleteNpmInstall(0); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"tslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"tslint --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } - ].map(getRootedFileOrFolder)); - verifyAfterPartialOrCompleteNpmInstall(0); - - // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 - filesAndFoldersToAdd.length--; - // and add few more folders/files - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/symbol-observable" }, - { path: "/a/b/node_modules/@types" }, - { path: "/a/b/node_modules/@types/lodash" }, - { path: "/a/b/node_modules/lodash" }, - { path: "/a/b/node_modules/rxjs" }, - { path: "/a/b/node_modules/typescript" }, - { path: "/a/b/node_modules/.bin" } - ].map(getRootedFileOrFolder)); - // From the type root update - verifyAfterPartialOrCompleteNpmInstall(2); - - forEach(filesAndFoldersToAdd, f => { - f.path = f.path - .replace("/a/b/node_modules/.staging", "/a/b/node_modules") - .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); - }); - - const lodashIndexPath = root + "/a/b/node_modules/@types/lodash/index.d.ts"; - projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)!); - // we would now not have failed lookup in the parent of appFolder since lodash is available - recursiveWatchedDirectories.length = 2; - // npm installation complete, timeout after reload fs - npmInstallComplete = true; - verifyAfterPartialOrCompleteNpmInstall(2); - - function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { - host.reloadFS(projectFiles.concat(otherFiles, filesAndFoldersToAdd)); - if (npmInstallComplete || timeoutDuringPartialInstallation) { - host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); - } - else { - host.checkTimeoutQueueLength(2); - } - verifyProject(); - } - - function verifyProject() { - checkNumberOfConfiguredProjects(projectService, 1); - - const project = projectService.configuredProjects.get(tsconfigJson.path)!; - const projectFilePaths = map(projectFiles, f => f.path); - checkProjectActualFiles(project, projectFilePaths); - - const filesWatched = filter(projectFilePaths, p => p !== app.path && p.indexOf("/a/b/node_modules") === -1); - checkWatchedFiles(host, filesWatched); - checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); - } - } - - it("timeouts occur inbetween installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); - }); - - it("timeout occurs after installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); - }); - }); - - it("when node_modules dont receive event for the @types file addition", () => { - const projectLocation = "/user/username/folder/myproject"; - const app: File = { - path: `${projectLocation}/app.ts`, - content: `import * as debug from "debug"` - }; - const tsconfig: File = { - path: `${projectLocation}/tsconfig.json`, - content: "" - }; - - const files = [app, tsconfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(app.path); - - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug'."]); - - const debugTypesFile: File = { - path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, - content: "export {}" - }; - files.push(debugTypesFile); - // Do not invoke recursive directory watcher for anything other than node_module/@types - const invoker = host.invokeWatchedDirectoriesRecursiveCallback; - host.invokeWatchedDirectoriesRecursiveCallback = (fullPath, relativePath) => { - if (fullPath.endsWith("@types")) { - invoker.call(host, fullPath, relativePath); - } - }; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []); - }); - }); - describe("tsserverProjectSystem with symLinks", () => { it("rename in common file renames all project", () => { const projects = "/users/username/projects"; From 5234b8b18e08d6835b90d3b667a77be9c3ccf9b6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 13:23:26 -0800 Subject: [PATCH 087/120] Move more tests into resolutionCache and project errors --- src/testRunner/unittests/projectErrors.ts | 166 ++-- src/testRunner/unittests/resolutionCache.ts | 848 +++++++++--------- .../unittests/tsserverProjectSystem.ts | 46 - 3 files changed, 530 insertions(+), 530 deletions(-) diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/projectErrors.ts index 9ff7278e78c..cb82a9eeca6 100644 --- a/src/testRunner/unittests/projectErrors.ts +++ b/src/testRunner/unittests/projectErrors.ts @@ -481,89 +481,6 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: Project Errors dont include overwrite emit error", () => { - it("for inferred project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - openFilesForSession([f1], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const projectName = projectService.inferredProjects[0].getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName: projectName } - }).response as ReadonlyArray; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsForInferredProjects, - seq: 3, - arguments: { options: { module: ModuleKind.CommonJS } } - }); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName: projectName } - }).response as ReadonlyArray; - assert.isTrue(diagsAfterUpdate.length === 0); - }); - - it("for external project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - const projectFileName = "/a/b/project.csproj"; - const externalFiles = toExternalFiles([f1.path]); - projectService.openExternalProject({ - projectFileName, - rootFiles: externalFiles, - options: {} - }); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName } - }).response as ReadonlyArray; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.OpenExternalProject, - seq: 3, - arguments: { - projectFileName, - rootFiles: externalFiles, - options: { module: ModuleKind.CommonJS } - } - }); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName } - }).response as ReadonlyArray; - assert.isTrue(diagsAfterUpdate.length === 0); - }); - }); - describe("tsserver:: Project Errors for Configure file diagnostics events", () => { function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic { const d = Diagnostics.Unknown_compiler_option_0; @@ -754,6 +671,89 @@ namespace ts.projectSystem { }); }); + describe("tsserver:: Project Errors dont include overwrite emit error", () => { + it("for inferred project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = createServerHost([f1, libFile]); + const session = createSession(host); + openFilesForSession([f1], session); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const projectName = projectService.inferredProjects[0].getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: projectName } + }).response as ReadonlyArray; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsForInferredProjects, + seq: 3, + arguments: { options: { module: ModuleKind.CommonJS } } + }); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName: projectName } + }).response as ReadonlyArray; + assert.isTrue(diagsAfterUpdate.length === 0); + }); + + it("for external project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = createServerHost([f1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + const projectFileName = "/a/b/project.csproj"; + const externalFiles = toExternalFiles([f1.path]); + projectService.openExternalProject({ + projectFileName, + rootFiles: externalFiles, + options: {} + }); + + checkNumberOfProjects(projectService, { externalProjects: 1 }); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + }).response as ReadonlyArray; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: server.CommandNames.OpenExternalProject, + seq: 3, + arguments: { + projectFileName, + rootFiles: externalFiles, + options: { module: ModuleKind.CommonJS } + } + }); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName } + }).response as ReadonlyArray; + assert.isTrue(diagsAfterUpdate.length === 0); + }); + }); + describe("tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { it("when options change", () => { const file = { diff --git a/src/testRunner/unittests/resolutionCache.ts b/src/testRunner/unittests/resolutionCache.ts index ab6c6830618..d97770e21b5 100644 --- a/src/testRunner/unittests/resolutionCache.ts +++ b/src/testRunner/unittests/resolutionCache.ts @@ -495,6 +495,453 @@ namespace ts.projectSystem { }); }); + describe("resolutionCache:: tsserverProjectSystem watching @types", () => { + it("works correctly when typings are added or removed", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const t1 = { + path: "/a/b/node_modules/@types/lib1/index.d.ts", + content: "export let a: number" + }; + const t2 = { + path: "/a/b/node_modules/@types/lib2/index.d.ts", + content: "export let b: number" + }; + const tsconfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + exclude: ["node_modules"] + }) + }; + const host = createServerHost([f1, t1, tsconfig]); + const projectService = createProjectService(host); + + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t1.path, tsconfig.path]); + + // delete t1 + host.reloadFS([f1, tsconfig]); + // run throttled operation + host.runQueuedTimeoutCallbacks(); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); + + // create t2 + host.reloadFS([f1, tsconfig, t2]); + // run throttled operation + host.runQueuedTimeoutCallbacks(); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t2.path, tsconfig.path]); + }); + }); + + describe("resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { + it("should remove the `module not found` error", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const host = createServerHost([file1]); + const session = createSession(host); + openFilesForSession([file1], session); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); + + host.reloadFS([file1, moduleFile]); + host.runQueuedTimeoutCallbacks(); + + // Make a change to trigger the program rebuild + const changeRequest = makeSessionRequest( + server.CommandNames.Change, + { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } + ); + session.executeCommand(changeRequest); + + // Recheck + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("npm install @types works", () => { + const folderPath = "/a/b/projects/temp"; + const file1: File = { + path: `${folderPath}/a.ts`, + content: 'import f = require("pad"); f;' + }; + const files = [file1, libFile]; + const host = createServerHost(files); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: file1.path, + fileContent: file1.content, + scriptKindName: "TS", + projectRootPath: folderPath + } + }); + checkNumberOfProjects(service, { inferredProjects: 1 }); + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file1.path] + } + }); + + host.checkTimeoutQueueLengthAndRun(1); + checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + const startOffset = file1.content.indexOf('"') + 1; + checkErrorMessage(session, "semanticDiag", { + file: file1.path, + diagnostics: [ + createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0, ["pad"]) + ], + }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + checkErrorMessage(session, "suggestionDiag", { file: file1.path, diagnostics: [] }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + + const padIndex: File = { + path: `${folderPath}/node_modules/@types/pad/index.d.ts`, + content: "export = pad;declare function pad(length: number, text: string, char ?: string): string;" + }; + files.push(padIndex); + host.reloadFS(files, { ignoreWatchInvokedWithTriggerAsFileCreate: true }); + host.runQueuedTimeoutCallbacks(); + checkProjectUpdatedInBackgroundEvent(session, [file1.path]); + session.clearMessages(); + + host.runQueuedTimeoutCallbacks(); + checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(); + checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] }); + }); + + it("suggestion diagnostics", () => { + const file: File = { + path: "/a.js", + content: "function f(p) {}", + }; + + const host = createServerHost([file]); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: file.path, fileContent: file.content }, + }); + + checkNumberOfProjects(service, { inferredProjects: 1 }); + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + host.checkTimeoutQueueLengthAndRun(2); + + checkProjectUpdatedInBackgroundEvent(session, [file.path]); + session.clearMessages(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path], + } + }); + + host.checkTimeoutQueueLengthAndRun(1); + + checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "suggestionDiag", { + file: file.path, + diagnostics: [ + createDiagnostic({ line: 1, offset: 12 }, { line: 1, offset: 13 }, Diagnostics._0_is_declared_but_its_value_is_never_read, ["p"], "suggestion", /*reportsUnnecessary*/ true), + ], + }); + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + }); + + it("disable suggestion diagnostics", () => { + const file: File = { + path: "/a.js", + content: 'require("b")', + }; + + const host = createServerHost([file]); + const session = createSession(host, { canUseEvents: true }); + const service = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: file.path, fileContent: file.content }, + }); + + session.executeCommandSeq({ + command: server.CommandNames.Configure, + arguments: { + preferences: { disableSuggestions: true } + }, + }); + + checkNumberOfProjects(service, { inferredProjects: 1 }); + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + host.checkTimeoutQueueLengthAndRun(2); + + checkProjectUpdatedInBackgroundEvent(session, [file.path]); + session.clearMessages(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path], + } + }); + + host.checkTimeoutQueueLengthAndRun(1); + + checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); + // No suggestion event, we're done. + checkCompleteEvent(session, 2, expectedSequenceId); + session.clearMessages(); + }); + + it("suppressed diagnostic events", () => { + const file: File = { + path: "/a.ts", + content: "1 = 2;", + }; + + const host = createServerHost([file]); + const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true }); + const service = session.getProjectService(); + + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { file: file.path, fileContent: file.content }, + }); + + checkNumberOfProjects(service, { inferredProjects: 1 }); + + host.checkTimeoutQueueLength(0); + checkNoDiagnosticEvents(session); + + session.clearMessages(); + + let expectedSequenceId = session.getNextSeq(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path], + } + }); + + host.checkTimeoutQueueLength(0); + checkNoDiagnosticEvents(session); + + checkCompleteEvent(session, 1, expectedSequenceId); + + session.clearMessages(); + + expectedSequenceId = session.getNextSeq(); + + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + file: file.path, + } + }); + + host.checkTimeoutQueueLength(0); + checkNoDiagnosticEvents(session); + + checkCompleteEvent(session, 1, expectedSequenceId); + + session.clearMessages(); + }); + }); + + describe("resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { + it("should restore the states for inferred projects", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const host = createServerHost([moduleFile, file1]); + const session = createSession(host); + + openFilesForSession([file1], session); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + + const moduleFileOldPath = moduleFile.path; + const moduleFileNewPath = "/a/b/moduleFile1.ts"; + moduleFile.path = moduleFileNewPath; + host.reloadFS([moduleFile, file1]); + host.runQueuedTimeoutCallbacks(); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); + assert.equal(diags.length, 1); + + moduleFile.path = moduleFileOldPath; + host.reloadFS([moduleFile, file1]); + host.runQueuedTimeoutCallbacks(); + + // Make a change to trigger the program rebuild + const changeRequest = makeSessionRequest( + server.CommandNames.Change, + { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } + ); + session.executeCommand(changeRequest); + host.runQueuedTimeoutCallbacks(); + + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("should restore the states for configured projects", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([moduleFile, file1, configFile]); + const session = createSession(host); + + openFilesForSession([file1], session); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + + const moduleFileOldPath = moduleFile.path; + const moduleFileNewPath = "/a/b/moduleFile1.ts"; + moduleFile.path = moduleFileNewPath; + host.reloadFS([moduleFile, file1, configFile]); + host.runQueuedTimeoutCallbacks(); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); + + moduleFile.path = moduleFileOldPath; + host.reloadFS([moduleFile, file1, configFile]); + host.runQueuedTimeoutCallbacks(); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("should property handle missing config files", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const projectName = "project1"; + const host = createServerHost([f1]); + const projectService = createProjectService(host); + projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); + + // should have one external project since config file is missing + projectService.checkNumberOfProjects({ externalProjects: 1 }); + + host.reloadFS([f1, config]); + projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + }); + + it("types should load from config file path if config exists", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) + }; + const node = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: "declare var process: any" + }; + const cwd = { + path: "/a/c" + }; + const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path }); + const projectService = createProjectService(host); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]); + }); + }); + describe("resolutionCache:: tsserverProjectSystem module resolution caching", () => { const projectLocation = "/user/username/projects/myproject"; const configFile: File = { @@ -978,405 +1425,4 @@ export const x = 10;` }); }); }); - - describe("resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { - it("should restore the states for inferred projects", () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: "import * as T from './moduleFile'; T.bar();" - }; - const host = createServerHost([moduleFile, file1]); - const session = createSession(host); - - openFilesForSession([file1], session); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - - const moduleFileOldPath = moduleFile.path; - const moduleFileNewPath = "/a/b/moduleFile1.ts"; - moduleFile.path = moduleFileNewPath; - host.reloadFS([moduleFile, file1]); - host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } - ]); - assert.equal(diags.length, 1); - - moduleFile.path = moduleFileOldPath; - host.reloadFS([moduleFile, file1]); - host.runQueuedTimeoutCallbacks(); - - // Make a change to trigger the program rebuild - const changeRequest = makeSessionRequest( - server.CommandNames.Change, - { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } - ); - session.executeCommand(changeRequest); - host.runQueuedTimeoutCallbacks(); - - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); - - it("should restore the states for configured projects", () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: "import * as T from './moduleFile'; T.bar();" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([moduleFile, file1, configFile]); - const session = createSession(host); - - openFilesForSession([file1], session); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - - const moduleFileOldPath = moduleFile.path; - const moduleFileNewPath = "/a/b/moduleFile1.ts"; - moduleFile.path = moduleFileNewPath; - host.reloadFS([moduleFile, file1, configFile]); - host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } - ]); - - moduleFile.path = moduleFileOldPath; - host.reloadFS([moduleFile, file1, configFile]); - host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); - - it("should property handle missing config files", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const projectName = "project1"; - const host = createServerHost([f1]); - const projectService = createProjectService(host); - projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); - - // should have one external project since config file is missing - projectService.checkNumberOfProjects({ externalProjects: 1 }); - - host.reloadFS([f1, config]); - projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - }); - - it("types should load from config file path if config exists", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) - }; - const node = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: "declare var process: any" - }; - const cwd = { - path: "/a/c" - }; - const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path }); - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]); - }); - }); - - describe("resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { - it("should remove the `module not found` error", () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: "import * as T from './moduleFile'; T.bar();" - }; - const host = createServerHost([file1]); - const session = createSession(host); - openFilesForSession([file1], session); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } - ]); - - host.reloadFS([file1, moduleFile]); - host.runQueuedTimeoutCallbacks(); - - // Make a change to trigger the program rebuild - const changeRequest = makeSessionRequest( - server.CommandNames.Change, - { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } - ); - session.executeCommand(changeRequest); - - // Recheck - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); - - it("npm install @types works", () => { - const folderPath = "/a/b/projects/temp"; - const file1: File = { - path: `${folderPath}/a.ts`, - content: 'import f = require("pad"); f;' - }; - const files = [file1, libFile]; - const host = createServerHost(files); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: file1.path, - fileContent: file1.content, - scriptKindName: "TS", - projectRootPath: folderPath - } - }); - checkNumberOfProjects(service, { inferredProjects: 1 }); - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file1.path] - } - }); - - host.checkTimeoutQueueLengthAndRun(1); - checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - const startOffset = file1.content.indexOf('"') + 1; - checkErrorMessage(session, "semanticDiag", { - file: file1.path, - diagnostics: [ - createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0, ["pad"]) - ], - }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - checkErrorMessage(session, "suggestionDiag", { file: file1.path, diagnostics: [] }); - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - - const padIndex: File = { - path: `${folderPath}/node_modules/@types/pad/index.d.ts`, - content: "export = pad;declare function pad(length: number, text: string, char ?: string): string;" - }; - files.push(padIndex); - host.reloadFS(files, { ignoreWatchInvokedWithTriggerAsFileCreate: true }); - host.runQueuedTimeoutCallbacks(); - checkProjectUpdatedInBackgroundEvent(session, [file1.path]); - session.clearMessages(); - - host.runQueuedTimeoutCallbacks(); - checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(); - checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] }); - }); - - it("suggestion diagnostics", () => { - const file: File = { - path: "/a.js", - content: "function f(p) {}", - }; - - const host = createServerHost([file]); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: file.path, fileContent: file.content }, - }); - - checkNumberOfProjects(service, { inferredProjects: 1 }); - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - host.checkTimeoutQueueLengthAndRun(2); - - checkProjectUpdatedInBackgroundEvent(session, [file.path]); - session.clearMessages(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path], - } - }); - - host.checkTimeoutQueueLengthAndRun(1); - - checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "suggestionDiag", { - file: file.path, - diagnostics: [ - createDiagnostic({ line: 1, offset: 12 }, { line: 1, offset: 13 }, Diagnostics._0_is_declared_but_its_value_is_never_read, ["p"], "suggestion", /*reportsUnnecessary*/ true), - ], - }); - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - }); - - it("disable suggestion diagnostics", () => { - const file: File = { - path: "/a.js", - content: 'require("b")', - }; - - const host = createServerHost([file]); - const session = createSession(host, { canUseEvents: true }); - const service = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: file.path, fileContent: file.content }, - }); - - session.executeCommandSeq({ - command: server.CommandNames.Configure, - arguments: { - preferences: { disableSuggestions: true } - }, - }); - - checkNumberOfProjects(service, { inferredProjects: 1 }); - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - host.checkTimeoutQueueLengthAndRun(2); - - checkProjectUpdatedInBackgroundEvent(session, [file.path]); - session.clearMessages(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path], - } - }); - - host.checkTimeoutQueueLengthAndRun(1); - - checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] }); - // No suggestion event, we're done. - checkCompleteEvent(session, 2, expectedSequenceId); - session.clearMessages(); - }); - - it("suppressed diagnostic events", () => { - const file: File = { - path: "/a.ts", - content: "1 = 2;", - }; - - const host = createServerHost([file]); - const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true }); - const service = session.getProjectService(); - - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: file.path, fileContent: file.content }, - }); - - checkNumberOfProjects(service, { inferredProjects: 1 }); - - host.checkTimeoutQueueLength(0); - checkNoDiagnosticEvents(session); - - session.clearMessages(); - - let expectedSequenceId = session.getNextSeq(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path], - } - }); - - host.checkTimeoutQueueLength(0); - checkNoDiagnosticEvents(session); - - checkCompleteEvent(session, 1, expectedSequenceId); - - session.clearMessages(); - - expectedSequenceId = session.getNextSeq(); - - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - file: file.path, - } - }); - - host.checkTimeoutQueueLength(0); - checkNoDiagnosticEvents(session); - - checkCompleteEvent(session, 1, expectedSequenceId); - - session.clearMessages(); - }); - }); } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index f30e8ad5d9c..996b81d4c2d 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3653,52 +3653,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem watching @types", () => { - it("works correctly when typings are added or removed", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const t1 = { - path: "/a/b/node_modules/@types/lib1/index.d.ts", - content: "export let a: number" - }; - const t2 = { - path: "/a/b/node_modules/@types/lib2/index.d.ts", - content: "export let b: number" - }; - const tsconfig = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - exclude: ["node_modules"] - }) - }; - const host = createServerHost([f1, t1, tsconfig]); - const projectService = createProjectService(host); - - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t1.path, tsconfig.path]); - - // delete t1 - host.reloadFS([f1, tsconfig]); - // run throttled operation - host.runQueuedTimeoutCallbacks(); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - - // create t2 - host.reloadFS([f1, tsconfig, t2]); - // run throttled operation - host.runQueuedTimeoutCallbacks(); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t2.path, tsconfig.path]); - }); - }); - describe("tsserverProjectSystem Open-file", () => { it("can be reloaded with empty content", () => { const f = { From 9e17a66151ac9b4c6bb9d6507dfbdecd3985d844 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 13:44:01 -0800 Subject: [PATCH 088/120] Sym links into single test --- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/tsserverHelpers.ts | 17 +- .../unittests/tsserverProjectSystem.ts | 289 ------------------ src/testRunner/unittests/tsserverSymLinks.ts | 282 +++++++++++++++++ 4 files changed, 292 insertions(+), 297 deletions(-) create mode 100644 src/testRunner/unittests/tsserverSymLinks.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index daad5a29b6a..8a5aab050c7 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -93,6 +93,7 @@ "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", "unittests/tsserverProjectUpdatedInBackgroundEvent.ts", + "unittests/tsserverSymLinks.ts", "unittests/typingsInstaller.ts", "unittests/versionCache.ts", "unittests/watchEnvironment.ts", diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserverHelpers.ts index f042a43738a..86dfddf3bb3 100644 --- a/src/testRunner/unittests/tsserverHelpers.ts +++ b/src/testRunner/unittests/tsserverHelpers.ts @@ -482,14 +482,15 @@ namespace ts.projectSystem { const toLocation = protocolToLocation(str); return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; } - //function protocolRenameSpanFromSubstring( - // str: string, - // substring: string, - // options?: SpanFromSubstringOptions, - // prefixSuffixText?: { readonly prefixText?: string, readonly suffixText?: string }, - //): protocol.RenameTextSpan { - // return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; - //} + + export function protocolRenameSpanFromSubstring( + str: string, + substring: string, + options?: SpanFromSubstringOptions, + prefixSuffixText?: { readonly prefixText?: string, readonly suffixText?: string }, + ): protocol.RenameTextSpan { + return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; + } export function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { const start = nthIndexOf(str, substring, options ? options.index : 0); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 996b81d4c2d..56493c01e35 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -7,14 +7,6 @@ namespace ts.projectSystem { checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); } - function protocolRenameSpanFromSubstring( - str: string, - substring: string, - options?: SpanFromSubstringOptions, - prefixSuffixText?: { readonly prefixText?: string, readonly suffixText?: string }, - ): protocol.RenameTextSpan { - return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; - } function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; } @@ -5144,287 +5136,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem with symLinks", () => { - it("rename in common file renames all project", () => { - const projects = "/users/username/projects"; - const folderA = `${projects}/a`; - const aFile: File = { - path: `${folderA}/a.ts`, - content: `import {C} from "./c/fc"; console.log(C)` - }; - const aTsconfig: File = { - path: `${folderA}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) - }; - const aC: SymLink = { - path: `${folderA}/c`, - symLink: "../c" - }; - const aFc = `${folderA}/c/fc.ts`; - - const folderB = `${projects}/b`; - const bFile: File = { - path: `${folderB}/b.ts`, - content: `import {C} from "./c/fc"; console.log(C)` - }; - const bTsconfig: File = { - path: `${folderB}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) - }; - const bC: SymLink = { - path: `${folderB}/c`, - symLink: "../c" - }; - const bFc = `${folderB}/c/fc.ts`; - - const folderC = `${projects}/c`; - const cFile: File = { - 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(); - openFilesForSession( - [ - { file: aFile, projectRootPath: folderA }, - { file: bFile, projectRootPath: folderB }, - { file: aFc, projectRootPath: folderA }, - { file: bFc, projectRootPath: folderB }, - ], - session); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - assert.isDefined(projectService.configuredProjects.get(aTsconfig.path)); - assert.isDefined(projectService.configuredProjects.get(bTsconfig.path)); - - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, { file: aFc, ...protocolLocationFromSubstring(cFile.content, "C") }); - - assert.equal(aFile.content, bFile.content); - const abLocs: protocol.RenameTextSpan[] = [ - protocolRenameSpanFromSubstring(aFile.content, "C"), - protocolRenameSpanFromSubstring(aFile.content, "C", { index: 1 }), - ]; - const span = protocolRenameSpanFromSubstring(cFile.content, "C"); - const cLocs: protocol.RenameTextSpan[] = [span]; - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "C", - fileToRename: undefined, - fullDisplayName: '"/users/username/projects/a/c/fc".C', - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - triggerSpan: protocolTextSpanFromSubstring(cFile.content, "C"), - }, - locs: [ - { file: aFc, locs: cLocs }, - { file: aFile.path, locs: abLocs }, - { file: bFc, locs: cLocs }, - { file: bFile.path, locs: abLocs }, - ], - }); - }); - - describe("module resolution when symlinked folder contents change and resolve modules", () => { - const projectRootPath = "/users/username/projects/myproject"; - const packages = `${projectRootPath}/javascript/packages`; - const recognizersDateTime = `${packages}/recognizers-date-time`; - const recognizersText = `${packages}/recognizers-text`; - const recognizersTextDist = `${recognizersText}/dist`; - const moduleName = "@microsoft/recognizers-text"; - const moduleNameInFile = `"${moduleName}"`; - const recognizersDateTimeSrcFile: File = { - path: `${recognizersDateTime}/src/datetime/baseDate.ts`, - content: `import {C} from ${moduleNameInFile}; -new C();` - }; - const recognizerDateTimeTsconfigPath = `${recognizersDateTime}/tsconfig.json`; - const recognizerDateTimeTsconfigWithoutPathMapping: File = { - path: recognizerDateTimeTsconfigPath, - content: JSON.stringify({ - include: ["src"] - }) - }; - const recognizerDateTimeTsconfigWithPathMapping: File = { - path: recognizerDateTimeTsconfigPath, - content: JSON.stringify({ - compilerOptions: { - rootDir: "src", - baseUrl: "./", - paths: { - "@microsoft/*": ["../*"] - } - }, - include: ["src"] - }) - }; - const nodeModulesRecorgnizersText: SymLink = { - path: `${recognizersDateTime}/node_modules/@microsoft/recognizers-text`, - symLink: recognizersText - }; - const recognizerTextSrcFile: File = { - path: `${recognizersText}/src/recognizers-text.ts`, - content: `export class C { method () { return 10; } }` - }; - const recongnizerTextDistTypingFile: File = { - path: `${recognizersTextDist}/types/recognizers-text.d.ts`, - content: `export class C { method(): number; }` - }; - const recongnizerTextPackageJson: File = { - path: `${recognizersText}/package.json`, - content: JSON.stringify({ - typings: "dist/types/recognizers-text.d.ts" - }) - }; - const filesInProjectWithUnresolvedModule = [recognizerDateTimeTsconfigPath, libFile.path, recognizersDateTimeSrcFile.path]; - const filesInProjectWithResolvedModule = [...filesInProjectWithUnresolvedModule, recongnizerTextDistTypingFile.path]; - - function verifyErrors(session: TestSession, semanticErrors: protocol.Diagnostic[]) { - session.clearMessages(); - const expectedSequenceId = session.getNextSeq(); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [recognizersDateTimeSrcFile.path], - } - }); - - const host = session.host; - host.checkTimeoutQueueLengthAndRun(1); - - checkErrorMessage(session, "syntaxDiag", { file: recognizersDateTimeSrcFile.path, diagnostics: [] }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "semanticDiag", { file: recognizersDateTimeSrcFile.path, diagnostics: semanticErrors }); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - - checkErrorMessage(session, "suggestionDiag", { - file: recognizersDateTimeSrcFile.path, - diagnostics: [], - }); - checkCompleteEvent(session, 2, expectedSequenceId); - } - - function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: ReadonlyMap, nonRecursiveDirectories: string[]) { - checkWatchedFilesDetailed(host, files.filter(f => f !== recognizersDateTimeSrcFile.path), 1); - checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, recursiveDirectories, /*recursive*/ true); - } - - function createSessionAndOpenFile(host: TestServerHost) { - const session = createSession(host, { canUseEvents: true }); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: recognizersDateTimeSrcFile.path, - projectRootPath - } - }); - return session; - } - - function verifyModuleResolution(withPathMapping: boolean) { - describe(withPathMapping ? "when tsconfig file contains path mapping" : "when tsconfig does not contain path mapping", () => { - const filesWithSources = [libFile, recognizersDateTimeSrcFile, withPathMapping ? recognizerDateTimeTsconfigWithPathMapping : recognizerDateTimeTsconfigWithoutPathMapping, recognizerTextSrcFile, recongnizerTextPackageJson]; - const filesWithNodeModulesSetup = [...filesWithSources, nodeModulesRecorgnizersText]; - const filesAfterCompilation = [...filesWithNodeModulesSetup, recongnizerTextDistTypingFile]; - - const watchedDirectoriesWithResolvedModule = arrayToMap(getTypeRootsFromLocation(recognizersDateTime), k => k, () => 1); - watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/src`, withPathMapping ? 1 : 2); // wild card + failed lookups - if (!withPathMapping) { - watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/node_modules`, 1); // failed lookups - } - const watchedDirectoriesWithUnresolvedModule = cloneMap(watchedDirectoriesWithResolvedModule); - watchedDirectoriesWithUnresolvedModule.set(`${recognizersDateTime}/src`, 2); // wild card + failed lookups - [`${recognizersDateTime}/node_modules`, ...(withPathMapping ? [recognizersText] : emptyArray), ...getNodeModuleDirectories(packages)].forEach(d => { - watchedDirectoriesWithUnresolvedModule.set(d, 1); - }); - const nonRecursiveWatchedDirectories = withPathMapping ? [packages] : emptyArray; - - function verifyProjectWithResolvedModule(session: TestSession) { - const projectService = session.getProjectService(); - const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath)!; - checkProjectActualFiles(project, filesInProjectWithResolvedModule); - verifyWatchedFilesAndDirectories(session.host, filesInProjectWithResolvedModule, watchedDirectoriesWithResolvedModule, nonRecursiveWatchedDirectories); - verifyErrors(session, []); - } - - function verifyProjectWithUnresolvedModule(session: TestSession) { - const projectService = session.getProjectService(); - const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath)!; - checkProjectActualFiles(project, filesInProjectWithUnresolvedModule); - verifyWatchedFilesAndDirectories(session.host, filesInProjectWithUnresolvedModule, watchedDirectoriesWithUnresolvedModule, nonRecursiveWatchedDirectories); - const startOffset = recognizersDateTimeSrcFile.content.indexOf('"') + 1; - verifyErrors(session, [ - createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + moduleNameInFile.length }, Diagnostics.Cannot_find_module_0, [moduleName]) - ]); - } - - it("when project compiles from sources", () => { - const host = createServerHost(filesWithSources); - const session = createSessionAndOpenFile(host); - verifyProjectWithUnresolvedModule(session); - - host.reloadFS(filesAfterCompilation); - host.runQueuedTimeoutCallbacks(); - - verifyProjectWithResolvedModule(session); - }); - - it("when project has node_modules setup but doesnt have modules in typings folder and then recompiles", () => { - const host = createServerHost(filesWithNodeModulesSetup); - const session = createSessionAndOpenFile(host); - verifyProjectWithUnresolvedModule(session); - - host.reloadFS(filesAfterCompilation); - host.runQueuedTimeoutCallbacks(); - - if (withPathMapping) { - verifyProjectWithResolvedModule(session); - } - else { - // Cannot handle the resolution update - verifyProjectWithUnresolvedModule(session); - } - }); - - it("when project recompiles after deleting generated folders", () => { - const host = createServerHost(filesAfterCompilation); - const session = createSessionAndOpenFile(host); - - verifyProjectWithResolvedModule(session); - - host.deleteFolder(recognizersTextDist, /*recursive*/ true); - host.runQueuedTimeoutCallbacks(); - - verifyProjectWithUnresolvedModule(session); - - host.ensureFileOrFolder(recongnizerTextDistTypingFile); - host.runQueuedTimeoutCallbacks(); - - if (withPathMapping) { - verifyProjectWithResolvedModule(session); - } - else { - // Cannot handle the resolution update - verifyProjectWithUnresolvedModule(session); - } - }); - }); - } - - verifyModuleResolution(/*withPathMapping*/ false); - verifyModuleResolution(/*withPathMapping*/ true); - }); - }); - describe("tsserverProjectSystem forceConsistentCasingInFileNames", () => { it("works when extends is specified with a case insensitive file system", () => { const rootPath = "/Users/username/dev/project"; diff --git a/src/testRunner/unittests/tsserverSymLinks.ts b/src/testRunner/unittests/tsserverSymLinks.ts new file mode 100644 index 00000000000..c9bad7ea678 --- /dev/null +++ b/src/testRunner/unittests/tsserverSymLinks.ts @@ -0,0 +1,282 @@ +namespace ts.projectSystem { + describe("tsserverProjectSystem with symLinks", () => { + it("rename in common file renames all project", () => { + const projects = "/users/username/projects"; + const folderA = `${projects}/a`; + const aFile: File = { + path: `${folderA}/a.ts`, + content: `import {C} from "./c/fc"; console.log(C)` + }; + const aTsconfig: File = { + path: `${folderA}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) + }; + const aC: SymLink = { + path: `${folderA}/c`, + symLink: "../c" + }; + const aFc = `${folderA}/c/fc.ts`; + + const folderB = `${projects}/b`; + const bFile: File = { + path: `${folderB}/b.ts`, + content: `import {C} from "./c/fc"; console.log(C)` + }; + const bTsconfig: File = { + path: `${folderB}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) + }; + const bC: SymLink = { + path: `${folderB}/c`, + symLink: "../c" + }; + const bFc = `${folderB}/c/fc.ts`; + + const folderC = `${projects}/c`; + const cFile: File = { + 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(); + openFilesForSession( + [ + { file: aFile, projectRootPath: folderA }, + { file: bFile, projectRootPath: folderB }, + { file: aFc, projectRootPath: folderA }, + { file: bFc, projectRootPath: folderB }, + ], + session); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.isDefined(projectService.configuredProjects.get(aTsconfig.path)); + assert.isDefined(projectService.configuredProjects.get(bTsconfig.path)); + + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, { file: aFc, ...protocolLocationFromSubstring(cFile.content, "C") }); + + assert.equal(aFile.content, bFile.content); + const abLocs: protocol.RenameTextSpan[] = [ + protocolRenameSpanFromSubstring(aFile.content, "C"), + protocolRenameSpanFromSubstring(aFile.content, "C", { index: 1 }), + ]; + const span = protocolRenameSpanFromSubstring(cFile.content, "C"); + const cLocs: protocol.RenameTextSpan[] = [span]; + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "C", + fileToRename: undefined, + fullDisplayName: '"/users/username/projects/a/c/fc".C', + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + triggerSpan: protocolTextSpanFromSubstring(cFile.content, "C"), + }, + locs: [ + { file: aFc, locs: cLocs }, + { file: aFile.path, locs: abLocs }, + { file: bFc, locs: cLocs }, + { file: bFile.path, locs: abLocs }, + ], + }); + }); + + describe("module resolution when symlinked folder contents change and resolve modules", () => { + const projectRootPath = "/users/username/projects/myproject"; + const packages = `${projectRootPath}/javascript/packages`; + const recognizersDateTime = `${packages}/recognizers-date-time`; + const recognizersText = `${packages}/recognizers-text`; + const recognizersTextDist = `${recognizersText}/dist`; + const moduleName = "@microsoft/recognizers-text"; + const moduleNameInFile = `"${moduleName}"`; + const recognizersDateTimeSrcFile: File = { + path: `${recognizersDateTime}/src/datetime/baseDate.ts`, + content: `import {C} from ${moduleNameInFile}; +new C();` + }; + const recognizerDateTimeTsconfigPath = `${recognizersDateTime}/tsconfig.json`; + const recognizerDateTimeTsconfigWithoutPathMapping: File = { + path: recognizerDateTimeTsconfigPath, + content: JSON.stringify({ + include: ["src"] + }) + }; + const recognizerDateTimeTsconfigWithPathMapping: File = { + path: recognizerDateTimeTsconfigPath, + content: JSON.stringify({ + compilerOptions: { + rootDir: "src", + baseUrl: "./", + paths: { + "@microsoft/*": ["../*"] + } + }, + include: ["src"] + }) + }; + const nodeModulesRecorgnizersText: SymLink = { + path: `${recognizersDateTime}/node_modules/@microsoft/recognizers-text`, + symLink: recognizersText + }; + const recognizerTextSrcFile: File = { + path: `${recognizersText}/src/recognizers-text.ts`, + content: `export class C { method () { return 10; } }` + }; + const recongnizerTextDistTypingFile: File = { + path: `${recognizersTextDist}/types/recognizers-text.d.ts`, + content: `export class C { method(): number; }` + }; + const recongnizerTextPackageJson: File = { + path: `${recognizersText}/package.json`, + content: JSON.stringify({ + typings: "dist/types/recognizers-text.d.ts" + }) + }; + const filesInProjectWithUnresolvedModule = [recognizerDateTimeTsconfigPath, libFile.path, recognizersDateTimeSrcFile.path]; + const filesInProjectWithResolvedModule = [...filesInProjectWithUnresolvedModule, recongnizerTextDistTypingFile.path]; + + function verifyErrors(session: TestSession, semanticErrors: protocol.Diagnostic[]) { + session.clearMessages(); + const expectedSequenceId = session.getNextSeq(); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [recognizersDateTimeSrcFile.path], + } + }); + + const host = session.host; + host.checkTimeoutQueueLengthAndRun(1); + + checkErrorMessage(session, "syntaxDiag", { file: recognizersDateTimeSrcFile.path, diagnostics: [] }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "semanticDiag", { file: recognizersDateTimeSrcFile.path, diagnostics: semanticErrors }); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + + checkErrorMessage(session, "suggestionDiag", { + file: recognizersDateTimeSrcFile.path, + diagnostics: [], + }); + checkCompleteEvent(session, 2, expectedSequenceId); + } + + function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: ReadonlyMap, nonRecursiveDirectories: string[]) { + checkWatchedFilesDetailed(host, files.filter(f => f !== recognizersDateTimeSrcFile.path), 1); + checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, recursiveDirectories, /*recursive*/ true); + } + + function createSessionAndOpenFile(host: TestServerHost) { + const session = createSession(host, { canUseEvents: true }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: recognizersDateTimeSrcFile.path, + projectRootPath + } + }); + return session; + } + + function verifyModuleResolution(withPathMapping: boolean) { + describe(withPathMapping ? "when tsconfig file contains path mapping" : "when tsconfig does not contain path mapping", () => { + const filesWithSources = [libFile, recognizersDateTimeSrcFile, withPathMapping ? recognizerDateTimeTsconfigWithPathMapping : recognizerDateTimeTsconfigWithoutPathMapping, recognizerTextSrcFile, recongnizerTextPackageJson]; + const filesWithNodeModulesSetup = [...filesWithSources, nodeModulesRecorgnizersText]; + const filesAfterCompilation = [...filesWithNodeModulesSetup, recongnizerTextDistTypingFile]; + + const watchedDirectoriesWithResolvedModule = arrayToMap(getTypeRootsFromLocation(recognizersDateTime), k => k, () => 1); + watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/src`, withPathMapping ? 1 : 2); // wild card + failed lookups + if (!withPathMapping) { + watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/node_modules`, 1); // failed lookups + } + const watchedDirectoriesWithUnresolvedModule = cloneMap(watchedDirectoriesWithResolvedModule); + watchedDirectoriesWithUnresolvedModule.set(`${recognizersDateTime}/src`, 2); // wild card + failed lookups + [`${recognizersDateTime}/node_modules`, ...(withPathMapping ? [recognizersText] : emptyArray), ...getNodeModuleDirectories(packages)].forEach(d => { + watchedDirectoriesWithUnresolvedModule.set(d, 1); + }); + const nonRecursiveWatchedDirectories = withPathMapping ? [packages] : emptyArray; + + function verifyProjectWithResolvedModule(session: TestSession) { + const projectService = session.getProjectService(); + const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath)!; + checkProjectActualFiles(project, filesInProjectWithResolvedModule); + verifyWatchedFilesAndDirectories(session.host, filesInProjectWithResolvedModule, watchedDirectoriesWithResolvedModule, nonRecursiveWatchedDirectories); + verifyErrors(session, []); + } + + function verifyProjectWithUnresolvedModule(session: TestSession) { + const projectService = session.getProjectService(); + const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath)!; + checkProjectActualFiles(project, filesInProjectWithUnresolvedModule); + verifyWatchedFilesAndDirectories(session.host, filesInProjectWithUnresolvedModule, watchedDirectoriesWithUnresolvedModule, nonRecursiveWatchedDirectories); + const startOffset = recognizersDateTimeSrcFile.content.indexOf('"') + 1; + verifyErrors(session, [ + createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + moduleNameInFile.length }, Diagnostics.Cannot_find_module_0, [moduleName]) + ]); + } + + it("when project compiles from sources", () => { + const host = createServerHost(filesWithSources); + const session = createSessionAndOpenFile(host); + verifyProjectWithUnresolvedModule(session); + + host.reloadFS(filesAfterCompilation); + host.runQueuedTimeoutCallbacks(); + + verifyProjectWithResolvedModule(session); + }); + + it("when project has node_modules setup but doesnt have modules in typings folder and then recompiles", () => { + const host = createServerHost(filesWithNodeModulesSetup); + const session = createSessionAndOpenFile(host); + verifyProjectWithUnresolvedModule(session); + + host.reloadFS(filesAfterCompilation); + host.runQueuedTimeoutCallbacks(); + + if (withPathMapping) { + verifyProjectWithResolvedModule(session); + } + else { + // Cannot handle the resolution update + verifyProjectWithUnresolvedModule(session); + } + }); + + it("when project recompiles after deleting generated folders", () => { + const host = createServerHost(filesAfterCompilation); + const session = createSessionAndOpenFile(host); + + verifyProjectWithResolvedModule(session); + + host.deleteFolder(recognizersTextDist, /*recursive*/ true); + host.runQueuedTimeoutCallbacks(); + + verifyProjectWithUnresolvedModule(session); + + host.ensureFileOrFolder(recongnizerTextDistTypingFile); + host.runQueuedTimeoutCallbacks(); + + if (withPathMapping) { + verifyProjectWithResolvedModule(session); + } + else { + // Cannot handle the resolution update + verifyProjectWithUnresolvedModule(session); + } + }); + }); + } + + verifyModuleResolution(/*withPathMapping*/ false); + verifyModuleResolution(/*withPathMapping*/ true); + }); + }); +} From 5c8ef3934d81d77cc9f780419aa8b39128e313c9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 13:53:51 -0800 Subject: [PATCH 089/120] Move the tests into their own folder for easy scenario search --- src/testRunner/tsconfig.json | 83 ++- .../{ => config}/commandLineParsing.ts | 0 .../{ => config}/configurationExtension.ts | 0 .../convertCompilerOptionsFromJson.ts | 0 .../convertTypeAcquisitionFromJson.ts | 0 .../{ => config}/initializeTSConfig.ts | 0 .../unittests/{ => config}/matchFiles.ts | 0 .../{ => config}/projectReferences.ts | 0 .../unittests/{ => config}/showConfig.ts | 0 .../unittests/{ => config}/tsconfigParsing.ts | 0 .../cancellableLanguageServiceOperations.ts | 0 .../{ => services}/convertToAsyncFunction.ts | 0 .../extract/constants.ts} | 0 .../extract/functions.ts} | 0 .../extract/helpers.ts} | 0 .../extract/ranges.ts} | 0 .../{ => services/extract}/symbolWalker.ts | 2 +- .../{ => services}/hostNewLineSupport.ts | 0 .../{ => services}/languageService.ts | 0 .../{ => services}/organizeImports.ts | 0 .../unittests/{ => services}/textChanges.ts | 0 .../{tscWatchEmit.ts => tscWatch/emit.ts} | 0 .../helpers.ts} | 0 .../unittests/tscWatch/resolutionCache.ts | 448 ++++++++++++ .../unittests/{ => tscWatch}/watchApi.ts | 0 .../unittests/tscWatch/watchEnvironment.ts | 178 +++++ .../cachingFileSystemInformation.ts} | 2 +- .../unittests/{ => tsserver}/compileOnSave.ts | 4 +- .../events/largeFileReferenced.ts} | 2 +- .../events/projectLoading.ts} | 2 +- .../events/projectUpdatedInBackground.ts} | 2 +- .../unittests/tsserver/externalProjects.ts | 300 ++++++++ .../helpers.ts} | 0 .../unittests/{ => tsserver}/projectErrors.ts | 0 src/testRunner/unittests/tsserver/reload.ts | 151 ++++ .../{ => tsserver}/resolutionCache.ts | 449 ------------ .../unittests/{ => tsserver}/session.ts | 0 .../unittests/tsserver/skipLibCheck.ts | 229 ++++++ .../symLinks.ts} | 2 +- .../unittests/{ => tsserver}/telemetry.ts | 0 .../unittests/{ => tsserver}/textStorage.ts | 0 .../{ => tsserver}/typingsInstaller.ts | 0 .../unittests/{ => tsserver}/versionCache.ts | 0 .../unittests/tsserver/watchEnvironment.ts | 135 ++++ .../unittests/tsserverProjectSystem.ts | 677 ------------------ src/testRunner/unittests/watchEnvironment.ts | 316 -------- 46 files changed, 1493 insertions(+), 1489 deletions(-) rename src/testRunner/unittests/{ => config}/commandLineParsing.ts (100%) rename src/testRunner/unittests/{ => config}/configurationExtension.ts (100%) rename src/testRunner/unittests/{ => config}/convertCompilerOptionsFromJson.ts (100%) rename src/testRunner/unittests/{ => config}/convertTypeAcquisitionFromJson.ts (100%) rename src/testRunner/unittests/{ => config}/initializeTSConfig.ts (100%) rename src/testRunner/unittests/{ => config}/matchFiles.ts (100%) rename src/testRunner/unittests/{ => config}/projectReferences.ts (100%) rename src/testRunner/unittests/{ => config}/showConfig.ts (100%) rename src/testRunner/unittests/{ => config}/tsconfigParsing.ts (100%) rename src/testRunner/unittests/{ => services}/cancellableLanguageServiceOperations.ts (100%) rename src/testRunner/unittests/{ => services}/convertToAsyncFunction.ts (100%) rename src/testRunner/unittests/{extractConstants.ts => services/extract/constants.ts} (100%) rename src/testRunner/unittests/{extractFunctions.ts => services/extract/functions.ts} (100%) rename src/testRunner/unittests/{extractTestHelpers.ts => services/extract/helpers.ts} (100%) rename src/testRunner/unittests/{extractRanges.ts => services/extract/ranges.ts} (100%) rename src/testRunner/unittests/{ => services/extract}/symbolWalker.ts (97%) rename src/testRunner/unittests/{ => services}/hostNewLineSupport.ts (100%) rename src/testRunner/unittests/{ => services}/languageService.ts (100%) rename src/testRunner/unittests/{ => services}/organizeImports.ts (100%) rename src/testRunner/unittests/{ => services}/textChanges.ts (100%) rename src/testRunner/unittests/{tscWatchEmit.ts => tscWatch/emit.ts} (100%) rename src/testRunner/unittests/{tscWatchHelpers.ts => tscWatch/helpers.ts} (100%) create mode 100644 src/testRunner/unittests/tscWatch/resolutionCache.ts rename src/testRunner/unittests/{ => tscWatch}/watchApi.ts (100%) create mode 100644 src/testRunner/unittests/tscWatch/watchEnvironment.ts rename src/testRunner/unittests/{tsserverCachingFileSystemInformation.ts => tsserver/cachingFileSystemInformation.ts} (98%) rename src/testRunner/unittests/{ => tsserver}/compileOnSave.ts (97%) rename src/testRunner/unittests/{tsserverLargeFileReferencedEvent.ts => tsserver/events/largeFileReferenced.ts} (95%) rename src/testRunner/unittests/{tsserverProjectLoadingEvents.ts => tsserver/events/projectLoading.ts} (96%) rename src/testRunner/unittests/{tsserverProjectUpdatedInBackgroundEvent.ts => tsserver/events/projectUpdatedInBackground.ts} (97%) create mode 100644 src/testRunner/unittests/tsserver/externalProjects.ts rename src/testRunner/unittests/{tsserverHelpers.ts => tsserver/helpers.ts} (100%) rename src/testRunner/unittests/{ => tsserver}/projectErrors.ts (100%) create mode 100644 src/testRunner/unittests/tsserver/reload.ts rename src/testRunner/unittests/{ => tsserver}/resolutionCache.ts (71%) rename src/testRunner/unittests/{ => tsserver}/session.ts (100%) create mode 100644 src/testRunner/unittests/tsserver/skipLibCheck.ts rename src/testRunner/unittests/{tsserverSymLinks.ts => tsserver/symLinks.ts} (97%) rename src/testRunner/unittests/{ => tsserver}/telemetry.ts (100%) rename src/testRunner/unittests/{ => tsserver}/textStorage.ts (100%) rename src/testRunner/unittests/{ => tsserver}/typingsInstaller.ts (100%) rename src/testRunner/unittests/{ => tsserver}/versionCache.ts (100%) create mode 100644 src/testRunner/unittests/tsserver/watchEnvironment.ts delete mode 100644 src/testRunner/unittests/watchEnvironment.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 8a5aab050c7..a7d82b84464 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -36,74 +36,79 @@ "runner.ts", - "unittests/extractTestHelpers.ts", - "unittests/tscWatchHelpers.ts", - "unittests/tsserverHelpers.ts", + "unittests/services/extract/helpers.ts", + "unittests/tscWatch/helpers.ts", + "unittests/tsserver/helpers.ts", "unittests/asserts.ts", "unittests/base64.ts", "unittests/builder.ts", - "unittests/cancellableLanguageServiceOperations.ts", - "unittests/commandLineParsing.ts", - "unittests/compileOnSave.ts", "unittests/compilerCore.ts", - "unittests/configurationExtension.ts", - "unittests/convertCompilerOptionsFromJson.ts", - "unittests/convertToAsyncFunction.ts", "unittests/convertToBase64.ts", - "unittests/convertTypeAcquisitionFromJson.ts", "unittests/customTransforms.ts", - "unittests/extractConstants.ts", - "unittests/extractFunctions.ts", - "unittests/extractRanges.ts", "unittests/factory.ts", - "unittests/hostNewLineSupport.ts", "unittests/incrementalParser.ts", - "unittests/initializeTSConfig.ts", "unittests/jsDocParsing.ts", - "unittests/languageService.ts", - "unittests/matchFiles.ts", "unittests/moduleResolution.ts", - "unittests/organizeImports.ts", "unittests/parsePseudoBigInt.ts", "unittests/paths.ts", "unittests/printer.ts", "unittests/programApi.ts", - "unittests/projectErrors.ts", - "unittests/projectReferences.ts", "unittests/publicApi.ts", - "unittests/resolutionCache.ts", "unittests/reuseProgramStructure.ts", - "unittests/session.ts", "unittests/semver.ts", - "unittests/showConfig.ts", - "unittests/symbolWalker.ts", - "unittests/telemetry.ts", - "unittests/textChanges.ts", - "unittests/textStorage.ts", "unittests/transform.ts", "unittests/transpile.ts", "unittests/tsbuild.ts", "unittests/tsbuildWatchMode.ts", - "unittests/tsconfigParsing.ts", - "unittests/tscWatchEmit.ts", "unittests/tscWatchMode.ts", - "unittests/tsserverCachingFileSystemInformation.ts", - "unittests/tsserverLargeFileReferencedEvent.ts", - "unittests/tsserverProjectLoadingEvents.ts", "unittests/tsserverProjectSystem.ts", - "unittests/tsserverProjectUpdatedInBackgroundEvent.ts", - "unittests/tsserverSymLinks.ts", - "unittests/typingsInstaller.ts", - "unittests/versionCache.ts", - "unittests/watchEnvironment.ts", - "unittests/watchApi.ts", + "unittests/config/commandLineParsing.ts", + "unittests/config/configurationExtension.ts", + "unittests/config/convertCompilerOptionsFromJson.ts", + "unittests/config/convertTypeAcquisitionFromJson.ts", + "unittests/config/initializeTSConfig.ts", + "unittests/config/matchFiles.ts", + "unittests/config/projectReferences.ts", + "unittests/config/showConfig.ts", + "unittests/config/tsconfigParsing.ts", "unittests/evaluation/asyncArrow.ts", "unittests/evaluation/asyncGenerator.ts", "unittests/evaluation/forAwaitOf.ts", + "unittests/services/cancellableLanguageServiceOperations.ts", "unittests/services/colorization.ts", + "unittests/services/convertToAsyncFunction.ts", "unittests/services/documentRegistry.ts", + "unittests/services/extract/constants.ts", + "unittests/services/extract/functions.ts", + "unittests/services/extract/symbolWalker.ts", + "unittests/services/extract/ranges.ts", + "unittests/services/hostNewLineSupport.ts", + "unittests/services/languageService.ts", + "unittests/services/organizeImports.ts", "unittests/services/patternMatcher.ts", - "unittests/services/preProcessFile.ts" + "unittests/services/preProcessFile.ts", + "unittests/services/textChanges.ts", + "unittests/tscWatch/emit.ts", + "unittests/tscWatch/resolutionCache.ts", + "unittests/tscWatch/watchEnvironment.ts", + "unittests/tscWatch/watchApi.ts", + "unittests/tsserver/cachingFileSystemInformation.ts", + "unittests/tsserver/compileOnSave.ts", + "unittests/tsserver/events/largeFileReferenced.ts", + "unittests/tsserver/events/projectLoading.ts", + "unittests/tsserver/events/projectUpdatedInBackground.ts", + "unittests/tsserver/externalProjects.ts", + "unittests/tsserver/projectErrors.ts", + "unittests/tsserver/reload.ts", + "unittests/tsserver/resolutionCache.ts", + "unittests/tsserver/session.ts", + "unittests/tsserver/skipLibCheck.ts", + "unittests/tsserver/symLinks.ts", + "unittests/tsserver/textStorage.ts", + "unittests/tsserver/telemetry.ts", + "unittests/tsserver/typingsInstaller.ts", + "unittests/tsserver/versionCache.ts", + "unittests/tsserver/watchEnvironment.ts" ] } diff --git a/src/testRunner/unittests/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts similarity index 100% rename from src/testRunner/unittests/commandLineParsing.ts rename to src/testRunner/unittests/config/commandLineParsing.ts diff --git a/src/testRunner/unittests/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts similarity index 100% rename from src/testRunner/unittests/configurationExtension.ts rename to src/testRunner/unittests/config/configurationExtension.ts diff --git a/src/testRunner/unittests/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts similarity index 100% rename from src/testRunner/unittests/convertCompilerOptionsFromJson.ts rename to src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts diff --git a/src/testRunner/unittests/convertTypeAcquisitionFromJson.ts b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts similarity index 100% rename from src/testRunner/unittests/convertTypeAcquisitionFromJson.ts rename to src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts diff --git a/src/testRunner/unittests/initializeTSConfig.ts b/src/testRunner/unittests/config/initializeTSConfig.ts similarity index 100% rename from src/testRunner/unittests/initializeTSConfig.ts rename to src/testRunner/unittests/config/initializeTSConfig.ts diff --git a/src/testRunner/unittests/matchFiles.ts b/src/testRunner/unittests/config/matchFiles.ts similarity index 100% rename from src/testRunner/unittests/matchFiles.ts rename to src/testRunner/unittests/config/matchFiles.ts diff --git a/src/testRunner/unittests/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts similarity index 100% rename from src/testRunner/unittests/projectReferences.ts rename to src/testRunner/unittests/config/projectReferences.ts diff --git a/src/testRunner/unittests/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts similarity index 100% rename from src/testRunner/unittests/showConfig.ts rename to src/testRunner/unittests/config/showConfig.ts diff --git a/src/testRunner/unittests/tsconfigParsing.ts b/src/testRunner/unittests/config/tsconfigParsing.ts similarity index 100% rename from src/testRunner/unittests/tsconfigParsing.ts rename to src/testRunner/unittests/config/tsconfigParsing.ts diff --git a/src/testRunner/unittests/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts similarity index 100% rename from src/testRunner/unittests/cancellableLanguageServiceOperations.ts rename to src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts similarity index 100% rename from src/testRunner/unittests/convertToAsyncFunction.ts rename to src/testRunner/unittests/services/convertToAsyncFunction.ts diff --git a/src/testRunner/unittests/extractConstants.ts b/src/testRunner/unittests/services/extract/constants.ts similarity index 100% rename from src/testRunner/unittests/extractConstants.ts rename to src/testRunner/unittests/services/extract/constants.ts diff --git a/src/testRunner/unittests/extractFunctions.ts b/src/testRunner/unittests/services/extract/functions.ts similarity index 100% rename from src/testRunner/unittests/extractFunctions.ts rename to src/testRunner/unittests/services/extract/functions.ts diff --git a/src/testRunner/unittests/extractTestHelpers.ts b/src/testRunner/unittests/services/extract/helpers.ts similarity index 100% rename from src/testRunner/unittests/extractTestHelpers.ts rename to src/testRunner/unittests/services/extract/helpers.ts diff --git a/src/testRunner/unittests/extractRanges.ts b/src/testRunner/unittests/services/extract/ranges.ts similarity index 100% rename from src/testRunner/unittests/extractRanges.ts rename to src/testRunner/unittests/services/extract/ranges.ts diff --git a/src/testRunner/unittests/symbolWalker.ts b/src/testRunner/unittests/services/extract/symbolWalker.ts similarity index 97% rename from src/testRunner/unittests/symbolWalker.ts rename to src/testRunner/unittests/services/extract/symbolWalker.ts index 4743b87133b..a027f0f2ce7 100644 --- a/src/testRunner/unittests/symbolWalker.ts +++ b/src/testRunner/unittests/services/extract/symbolWalker.ts @@ -42,4 +42,4 @@ export default function foo(a: number, b: Bar): void {}`, (file, checker) => { assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/hostNewLineSupport.ts b/src/testRunner/unittests/services/hostNewLineSupport.ts similarity index 100% rename from src/testRunner/unittests/hostNewLineSupport.ts rename to src/testRunner/unittests/services/hostNewLineSupport.ts diff --git a/src/testRunner/unittests/languageService.ts b/src/testRunner/unittests/services/languageService.ts similarity index 100% rename from src/testRunner/unittests/languageService.ts rename to src/testRunner/unittests/services/languageService.ts diff --git a/src/testRunner/unittests/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts similarity index 100% rename from src/testRunner/unittests/organizeImports.ts rename to src/testRunner/unittests/services/organizeImports.ts diff --git a/src/testRunner/unittests/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts similarity index 100% rename from src/testRunner/unittests/textChanges.ts rename to src/testRunner/unittests/services/textChanges.ts diff --git a/src/testRunner/unittests/tscWatchEmit.ts b/src/testRunner/unittests/tscWatch/emit.ts similarity index 100% rename from src/testRunner/unittests/tscWatchEmit.ts rename to src/testRunner/unittests/tscWatch/emit.ts diff --git a/src/testRunner/unittests/tscWatchHelpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts similarity index 100% rename from src/testRunner/unittests/tscWatchHelpers.ts rename to src/testRunner/unittests/tscWatch/helpers.ts diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts new file mode 100644 index 00000000000..759c80e93a6 --- /dev/null +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -0,0 +1,448 @@ +namespace ts.tscWatch { + describe("resolutionCache:: tsc-watch module resolution caching", () => { + it("works", () => { + const root = { + path: "/a/d/f0.ts", + content: `import {x} from "f1"` + }; + const imported = { + path: "/a/f1.ts", + content: `foo()` + }; + + const files = [root, imported, libFile]; + const host = createWatchedSystem(files); + 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"); + + // ensure that imported file was found + checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); + + const originalFileExists = host.fileExists; + { + const newContent = `import {x} from "f1" + var x: string = 1;`; + root.content = newContent; + host.reloadFS(files); + + // patch fileExists to make sure that disk is not touched + host.fileExists = notImplemented; + + // trigger synchronization to make sure that import will be fetched from the cache + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + f1IsNotModule, + getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"), + cannotFindFoo + ]); + } + { + let fileExistsIsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f2.") !== -1); + return originalFileExists.call(host, fileName); + }; + + root.content = `import {x} from "f2"`; + host.reloadFS(files); + + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") + ]); + + assert.isTrue(fileExistsIsCalled); + } + { + let fileExistsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsCalled = true; + assert.isTrue(fileName.indexOf("/f1.") !== -1); + return originalFileExists.call(host, fileName); + }; + + const newContent = `import {x} from "f1"`; + root.content = newContent; + + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); + assert.isTrue(fileExistsCalled); + } + }); + + it("loads missing files from disk", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; + + const files = [root, libFile]; + const host = createWatchedSystem(files); + const originalFileExists = host.fileExists; + + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") + ]); + + fileExistsCalledForBar = false; + root.content = `import {y} from "bar"`; + host.reloadFS(files.concat(imported)); + + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;export const x = 10;` + }; + + const files = [root, libFile]; + const filesWithImported = files.concat(imported); + const host = createWatchedSystem(filesWithImported); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + return originalFileExists.call(host, fileName); + }; + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); + + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputErrorsInitial(host, emptyArray); + + fileExistsCalledForBar = false; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputErrorsIncremental(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") + ]); + + fileExistsCalledForBar = false; + host.reloadFS(filesWithImported); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }); + + it("works when module resolution changes to ambient module", () => { + const root = { + path: "/a/b/foo.ts", + content: `import * as fs from "fs";` + }; + + const packageJson = { + path: "/a/b/node_modules/@types/node/package.json", + content: ` +{ + "main": "" +} +` + }; + + const nodeType = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: ` +declare module "fs" { + export interface Stats { + isFile(): boolean; + } +}` + }; + + const files = [root, libFile]; + const filesWithNodeType = files.concat(packageJson, nodeType); + const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); + + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); + + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") + ]); + + host.reloadFS(filesWithNodeType); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + }); + + it("works when included file with ambient module changes", () => { + const root = { + path: "/a/b/foo.ts", + content: ` +import * as fs from "fs"; +import * as u from "url"; +` + }; + + const file = { + path: "/a/b/bar.d.ts", + content: ` +declare module "url" { + export interface Url { + href?: string; + } +} +` + }; + + const fileContentWithFS = ` +declare module "fs" { + export interface Stats { + isFile(): boolean; + } +} +`; + + const files = [root, file, libFile]; + const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); + + const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); + + checkOutputErrorsInitial(host, [ + getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") + ]); + + file.content += fileContentWithFS; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkOutputErrorsIncremental(host, emptyArray); + }); + + it("works when reusing program with files from external library", () => { + interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; } + const configDir = "/a/b/projects/myProject/src/"; + const file1: File = { + path: configDir + "file1.ts", + content: 'import module1 = require("module1");\nmodule1("hello");' + }; + const file2: File = { + path: configDir + "file2.ts", + content: 'import module11 = require("module1");\nmodule11("hello");' + }; + const module1: File = { + path: "/a/b/projects/myProject/node_modules/module1/index.js", + content: "module.exports = options => { return options.toString(); }" + }; + const configFile: File = { + path: configDir + "tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + allowJs: true, + rootDir: ".", + outDir: "../dist", + moduleResolution: "node", + maxNodeModuleJsDepth: 1 + } + }) + }; + 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 = createWatchOfConfigFile(configFile.path, host); + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrorsInitial(host, emptyArray); + const expectedFiles: ExpectedFile[] = [ + createExpectedEmittedFile(file1), + createExpectedEmittedFile(file2), + createExpectedToNotEmitFile("index.js"), + createExpectedToNotEmitFile("src/index.js"), + createExpectedToNotEmitFile("src/file1.js"), + createExpectedToNotEmitFile("src/file2.js"), + createExpectedToNotEmitFile("lib.js"), + createExpectedToNotEmitFile("lib.d.ts") + ]; + verifyExpectedFiles(expectedFiles); + + file1.content += "\n;"; + expectedFiles[0].content += ";\n"; // Only emit file1 with this change + expectedFiles[1].isExpectedToEmit = false; + host.reloadFS(programFiles.concat(configFile)); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrorsIncremental(host, emptyArray); + verifyExpectedFiles(expectedFiles); + + + function verifyExpectedFiles(expectedFiles: ExpectedFile[]) { + forEach(expectedFiles, f => { + assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit")); + if (f.isExpectedToEmit) { + assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path); + } + }); + } + + function createExpectedToNotEmitFile(fileName: string): ExpectedFile { + return { + path: outDirFolder + fileName, + isExpectedToEmit: false + }; + } + + function createExpectedEmittedFile(file: File): ExpectedFile { + return { + path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js, + isExpectedToEmit: true, + content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n" + }; + } + }); + + it("works when renaming node_modules folder that already contains @types folder", () => { + const currentDirectory = "/user/username/projects/myproject"; + const file: File = { + path: `${currentDirectory}/a.ts`, + content: `import * as q from "qqq";` + }; + const module: File = { + path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, + content: "export {}" + }; + const files = [file, module, libFile]; + const host = createWatchedSystem(files, { currentDirectory }); + const watch = createWatchOfFilesAndCompilerOptions([file.path], host); + + checkProgramActualFiles(watch(), [file.path, libFile.path]); + checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true); + + host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); + host.runQueuedTimeoutCallbacks(); + checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); + checkOutputErrorsIncremental(host, emptyArray); + }); + + describe("ignores files/folder changes in node_modules that start with '.'", () => { + const projectPath = "/user/username/projects/project"; + const npmCacheFile: File = { + path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + content: JSON.stringify({ something: 10 }) + }; + const file1: File = { + path: `${projectPath}/test.ts`, + content: `import { x } from "somemodule";` + }; + const file2: File = { + path: `${projectPath}/node_modules/somemodule/index.d.ts`, + content: `export const x = 10;` + }; + const files = [libFile, file1, file2]; + const expectedFiles = files.map(f => f.path); + it("when watching node_modules in inferred project for failed lookup", () => { + const host = createWatchedSystem(files); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1); + checkProgramActualFiles(watch(), expectedFiles); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + it("when watching node_modules as part of wild card directories in config project", () => { + const config: File = { + path: `${projectPath}/tsconfig.json`, + content: "{}" + }; + const host = createWatchedSystem(files.concat(config)); + const watch = createWatchOfConfigFile(config.path, host); + checkProgramActualFiles(watch(), expectedFiles); + host.checkTimeoutQueueLength(0); + + host.ensureFileOrFolder(npmCacheFile); + host.checkTimeoutQueueLength(0); + }); + }); + }); + + describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => { + const projectRoot = "/user/username/projects/project"; + const mainPackageRoot = `${projectRoot}/main`; + const linkedPackageRoot = `${projectRoot}/linked-package`; + const mainFile: File = { + path: `${mainPackageRoot}/index.ts`, + content: "import { Foo } from '@scoped/linked-package'" + }; + const config: File = { + path: `${mainPackageRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, + files: ["index.ts"] + }) + }; + const linkedPackageInMain: SymLink = { + path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, + symLink: `${linkedPackageRoot}` + }; + const linkedPackageJson: File = { + path: `${linkedPackageRoot}/package.json`, + content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) + }; + const linkedPackageIndex: File = { + path: `${linkedPackageRoot}/dist/index.d.ts`, + content: "export * from './other';" + }; + const linkedPackageOther: File = { + path: `${linkedPackageRoot}/dist/other.d.ts`, + content: 'export declare const Foo = "BAR";' + }; + + it("verify watched directories", () => { + const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; + const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true); + }); + }); +} diff --git a/src/testRunner/unittests/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts similarity index 100% rename from src/testRunner/unittests/watchApi.ts rename to src/testRunner/unittests/tscWatch/watchApi.ts diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts new file mode 100644 index 00000000000..646fb59ef09 --- /dev/null +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -0,0 +1,178 @@ +namespace ts.tscWatch { + import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; + describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => { + it("watchFile using dynamic priority polling", () => { + const projectFolder = "/a/username/project"; + const file1: File = { + path: `${projectFolder}/typescript.ts`, + content: "var z = 10;" + }; + const files = [file1, libFile]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling"); + const host = createWatchedSystem(files, { environmentVariables }); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); + + const initialProgram = watch(); + verifyProgram(); + + const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; + for (let index = 0; index < mediumPollingIntervalThreshold; index++) { + // Transition libFile and file1 to low priority queue + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), initialProgram); + } + + // Make a change to file + file1.content = "var zz30 = 100;"; + host.reloadFS(files); + + // This should detect change in the file + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), initialProgram); + + // Callbacks: medium priority + high priority queue and scheduled program update + host.checkTimeoutQueueLengthAndRun(3); + // During this timeout the file would be detected as unchanged + let fileUnchangeDetected = 1; + const newProgram = watch(); + assert.notStrictEqual(newProgram, initialProgram); + + verifyProgram(); + const outputFile1 = changeExtension(file1.path, ".js"); + assert.isTrue(host.fileExists(outputFile1)); + assert.equal(host.readFile(outputFile1), file1.content + host.newLine); + + const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; + for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { + // For high + Medium/low polling interval + host.checkTimeoutQueueLengthAndRun(2); + assert.deepEqual(watch(), newProgram); + } + + // Everything goes in high polling interval queue + host.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(watch(), newProgram); + + function verifyProgram() { + checkProgramActualFiles(watch(), files.map(f => f.path)); + checkWatchedFiles(host, []); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, [], /*recursive*/ true); + } + }); + + describe("tsc-watch when watchDirectories implementation", () => { + function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const file: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + const programFiles = [file, libFile]; + const files = [file, configFile, libFile]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables }); + const watch = createWatchOfConfigFile(configFile.path, host); + const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; + // Watching files config file, file, lib file + const expectedWatchedFiles = files.map(f => f.path); + const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; + if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { + expectedWatchedFiles.push(...projectFolders); + } + + verifyProgram(checkOutputErrorsInitial); + + // Rename the file: + file.path = file.path.replace("file1.ts", "file2.ts"); + expectedWatchedFiles[0] = file.path; + host.reloadFS(files); + if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { + // With dynamic polling the fs change would be detected only by running timeouts + host.runQueuedTimeoutCallbacks(); + } + // Delayed update program + host.runQueuedTimeoutCallbacks(); + verifyProgram(checkOutputErrorsIncremental); + + function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { + checkProgramActualFiles(watch(), programFiles.map(f => f.path)); + checkOutputErrors(host, emptyArray); + + const outputFile = changeExtension(file.path, ".js"); + assert(host.fileExists(outputFile)); + assert.equal(host.readFile(outputFile), file.content); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + // Watching config file, file, lib file and directories + checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false); + } + } + + it("uses watchFile when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile); + }); + + it("uses non recursive watchDirectory when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses non recursive dynamic polling when renaming file in subfolder", () => { + verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); + }); + + it("when there are symlinks to folders in recursive folders", () => { + const cwd = "/home/user/projects/myproject"; + const file1: File = { + path: `${cwd}/src/file.ts`, + content: `import * as a from "a"` + }; + const tsconfig: File = { + path: `${cwd}/tsconfig.json`, + content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` + }; + const realA: File = { + path: `${cwd}/node_modules/reala/index.d.ts`, + content: `export {}` + }; + const realB: File = { + path: `${cwd}/node_modules/realb/index.d.ts`, + content: `export {}` + }; + const symLinkA: SymLink = { + path: `${cwd}/node_modules/a`, + symLink: `${cwd}/node_modules/reala` + }; + const symLinkB: SymLink = { + path: `${cwd}/node_modules/b`, + symLink: `${cwd}/node_modules/realb` + }; + const symLinkBInA: SymLink = { + path: `${cwd}/node_modules/reala/node_modules/b`, + symLink: `${cwd}/node_modules/b` + }; + const symLinkAInB: SymLink = { + path: `${cwd}/node_modules/realb/node_modules/a`, + symLink: `${cwd}/node_modules/a` + }; + const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); + createWatchOfConfigFile("tsconfig.json", host); + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, + `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts similarity index 98% rename from src/testRunner/unittests/tsserverCachingFileSystemInformation.ts rename to src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index 65e1ec564d3..b5132f57c0e 100644 --- a/src/testRunner/unittests/tsserverCachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -3,7 +3,7 @@ namespace ts.projectSystem { return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); } - describe("tsserverCachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { + describe("tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { enum CalledMapsWithSingleArg { fileExists = "fileExists", directoryExists = "directoryExists", diff --git a/src/testRunner/unittests/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts similarity index 97% rename from src/testRunner/unittests/compileOnSave.ts rename to src/testRunner/unittests/tsserver/compileOnSave.ts index 2352ac58c19..310a6e9e33c 100644 --- a/src/testRunner/unittests/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -6,7 +6,7 @@ namespace ts.projectSystem { return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); } - describe("compileOnSave:: affected list", () => { + describe("tsserver:: compileOnSave:: affected list", () => { function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); @@ -552,7 +552,7 @@ namespace ts.projectSystem { }); }); - describe("compileOnSave:: EmitFile test", () => { + describe("tsserver:: compileOnSave:: EmitFile test", () => { it("should respect line endings", () => { test("\n"); test("\r\n"); diff --git a/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts similarity index 95% rename from src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts rename to src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index f33f32dfe58..ca7f839e1b0 100644 --- a/src/testRunner/unittests/tsserverLargeFileReferencedEvent.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverLargeFileReferencedEvent:: LargeFileReferencedEvent with large file", () => { + describe("tsserver:: LargeFileReferencedEvent with large file", () => { const projectRoot = "/user/username/projects/project"; function getLargeFile(useLargeTsFile: boolean) { diff --git a/src/testRunner/unittests/tsserverProjectLoadingEvents.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts similarity index 96% rename from src/testRunner/unittests/tsserverProjectLoadingEvents.ts rename to src/testRunner/unittests/tsserver/events/projectLoading.ts index 0ff256bd692..2de3ab40db6 100644 --- a/src/testRunner/unittests/tsserverProjectLoadingEvents.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverProjectLoadingEvents:: ProjectLoadingStart and ProjectLoadingFinish events", () => { + describe("tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { const projectRoot = "/user/username/projects"; const aTs: File = { path: `${projectRoot}/a/a.ts`, diff --git a/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts similarity index 97% rename from src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts rename to src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 3e8382fff58..c818f4d2a8d 100644 --- a/src/testRunner/unittests/tsserverProjectUpdatedInBackgroundEvent.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverProjectUpdatedInBackgroundEvent:: ProjectsUpdatedInBackground", () => { + describe("tsserver:: ProjectsUpdatedInBackground", () => { function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); const seen = createMap(); diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts new file mode 100644 index 00000000000..6c246a2e3bd --- /dev/null +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -0,0 +1,300 @@ +namespace ts.projectSystem { + describe("tsserver:: ExternalProjects", () => { + describe("correctly handling add/remove tsconfig - 1", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const f2 = { + path: "/a/b/lib.ts", + content: "" + }; + const tsconfig = { + path: "/a/b/tsconfig.json", + content: "" + }; + const host = createServerHost([f1, f2]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + + // rename lib.ts to tsconfig.json + host.reloadFS([f1, tsconfig]); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, tsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); + + // rename tsconfig.json back to lib.ts + host.reloadFS([f1, f2]); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); + + describe("correctly handling add/remove tsconfig - 2", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const cLib = { + path: "/a/b/c/lib.ts", + content: "" + }; + const cTsconfig = { + path: "/a/b/c/tsconfig.json", + content: "{}" + }; + const dLib = { + path: "/a/b/d/lib.ts", + content: "" + }; + const dTsconfig = { + path: "/a/b/d/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); + + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // remove one config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, dTsconfig.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); + + // remove second config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); + + // open two config files + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed + checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // close all projects - no projects should be opened + projectService.closeExternalProject(projectName); + projectService.checkNumberOfProjects({}); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); + + it("correctly handles changes in lib section of config file", () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5" + ] + } + }) + }; + const config2 = { + path: config1.path, + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5", + "es2015.promise" + ] + } + }) + }; + const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + const projectService = createProjectService(host); + projectService.openClientFile(app.path); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); + + host.reloadFS([libES5, libES2015Promise, app, config2]); + host.checkTimeoutQueueLengthAndRun(2); + + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); + }); + + it("should handle non-existing directories in config file", () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + const host = createServerHost([f, config]); + const projectService = createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f + + projectService.closeClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isTrue(project.hasOpenRef()); // f + assert.isFalse(project.isClosed()); + }); + + it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; + const projectFileName = "/a/b/project.csproj"; + const host = createServerHost([f1, config]); + const service = createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, config.path]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, emptyArray); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project, [config.path, f1.path]); + + service.closeExternalProject(projectFileName); + service.checkNumberOfProjects({}); + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, config.path]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project2 = service.configuredProjects.get(config.path)!; + assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project2, [config.path, f1.path]); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverHelpers.ts b/src/testRunner/unittests/tsserver/helpers.ts similarity index 100% rename from src/testRunner/unittests/tsserverHelpers.ts rename to src/testRunner/unittests/tsserver/helpers.ts diff --git a/src/testRunner/unittests/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts similarity index 100% rename from src/testRunner/unittests/projectErrors.ts rename to src/testRunner/unittests/tsserver/projectErrors.ts diff --git a/src/testRunner/unittests/tsserver/reload.ts b/src/testRunner/unittests/tsserver/reload.ts new file mode 100644 index 00000000000..80f3e56e437 --- /dev/null +++ b/src/testRunner/unittests/tsserver/reload.ts @@ -0,0 +1,151 @@ +namespace ts.projectSystem { + describe("tsserver:: reload", () => { + it("should work with temp file", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const tmp = { + path: "/a/b/app.tmp", + content: "const y = 42" + }; + const host = createServerHost([f1, tmp]); + const session = createSession(host); + + // send open request + session.executeCommand({ + type: "request", + command: "open", + seq: 1, + arguments: { file: f1.path } + }); + + // reload from tmp file + session.executeCommand({ + type: "request", + command: "reload", + seq: 2, + arguments: { file: f1.path, tmpfile: tmp.path } + }); + + // verify content + const projectServiice = session.getProjectService(); + const snap1 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); + assert.equal(getSnapshotText(snap1), tmp.content, "content should be equal to the content of temp file"); + + // reload from original file file + session.executeCommand({ + type: "request", + command: "reload", + seq: 2, + arguments: { file: f1.path } + }); + + // verify content + const snap2 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); + assert.equal(getSnapshotText(snap2), f1.content, "content should be equal to the content of original file"); + + }); + + it("should work when script info doesnt have any project open", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const tmp = { + path: "/a/b/app.tmp", + content: "const y = 42" + }; + const host = createServerHost([f1, tmp, libFile]); + const session = createSession(host); + const openContent = "let z = 1"; + // send open request + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Open, + arguments: { file: f1.path, fileContent: openContent } + }); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const info = projectService.getScriptInfo(f1.path)!; + assert.isDefined(info); + checkScriptInfoContents(openContent, "contents set during open request"); + + // send close request + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Close, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + checkInferredProjectIsOrphan(); + + // Can reload contents of the file when its not open and has no project + // reload from temp file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path, tmpfile: tmp.path } + }); + checkScriptInfoAndProjects(tmp.content, "contents of temp file"); + checkInferredProjectIsOrphan(); + + // reload from own file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + checkInferredProjectIsOrphan(); + + // Open file again without setting its content + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Open, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of file when opened without specifying contents"); + const snap = info.getSnapshot(); + + // send close request + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Close, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + assert.strictEqual(info.getSnapshot(), snap); + checkInferredProjectIsOrphan(); + + // reload from temp file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path, tmpfile: tmp.path } + }); + checkScriptInfoAndProjects(tmp.content, "contents of temp file"); + assert.notStrictEqual(info.getSnapshot(), snap); + checkInferredProjectIsOrphan(); + + // reload from own file + session.executeCommandSeq({ + command: server.protocol.CommandTypes.Reload, + arguments: { file: f1.path } + }); + checkScriptInfoAndProjects(f1.content, "contents of closed file"); + assert.notStrictEqual(info.getSnapshot(), snap); + checkInferredProjectIsOrphan(); + + function checkInferredProjectIsOrphan() { + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.equal(info.containingProjects.length, 0); + } + + function checkScriptInfoAndProjects(contentsOfInfo: string, captionForContents: string) { + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.strictEqual(projectService.getScriptInfo(f1.path), info); + checkScriptInfoContents(contentsOfInfo, captionForContents); + } + + function checkScriptInfoContents(contentsOfInfo: string, captionForContents: string) { + const snap = info.getSnapshot(); + assert.equal(getSnapshotText(snap), contentsOfInfo, "content should be equal to " + captionForContents); + } + }); + }); +} diff --git a/src/testRunner/unittests/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts similarity index 71% rename from src/testRunner/unittests/resolutionCache.ts rename to src/testRunner/unittests/tsserver/resolutionCache.ts index d97770e21b5..a07cb7721d4 100644 --- a/src/testRunner/unittests/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -1,452 +1,3 @@ -namespace ts.tscWatch { - describe("resolutionCache:: tsc-watch module resolution caching", () => { - it("works", () => { - const root = { - path: "/a/d/f0.ts", - content: `import {x} from "f1"` - }; - const imported = { - path: "/a/f1.ts", - content: `foo()` - }; - - const files = [root, imported, libFile]; - const host = createWatchedSystem(files); - 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"); - - // ensure that imported file was found - checkOutputErrorsInitial(host, [f1IsNotModule, cannotFindFoo]); - - const originalFileExists = host.fileExists; - { - const newContent = `import {x} from "f1" - var x: string = 1;`; - root.content = newContent; - host.reloadFS(files); - - // patch fileExists to make sure that disk is not touched - host.fileExists = notImplemented; - - // trigger synchronization to make sure that import will be fetched from the cache - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - f1IsNotModule, - getDiagnosticOfFileFromProgram(watch(), root.path, newContent.indexOf("var x") + "var ".length, "x".length, Diagnostics.Type_0_is_not_assignable_to_type_1, 1, "string"), - cannotFindFoo - ]); - } - { - let fileExistsIsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f2.") !== -1); - return originalFileExists.call(host, fileName); - }; - - root.content = `import {x} from "f2"`; - host.reloadFS(files); - - // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk - host.runQueuedTimeoutCallbacks(); - - // ensure file has correct number of errors after edit - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "f2") - ]); - - assert.isTrue(fileExistsIsCalled); - } - { - let fileExistsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsCalled = true; - assert.isTrue(fileName.indexOf("/f1.") !== -1); - return originalFileExists.call(host, fileName); - }; - - const newContent = `import {x} from "f1"`; - root.content = newContent; - - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - - checkOutputErrorsIncremental(host, [f1IsNotModule, cannotFindFoo]); - assert.isTrue(fileExistsCalled); - } - }); - - it("loads missing files from disk", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;` - }; - - const files = [root, libFile]; - const host = createWatchedSystem(files); - const originalFileExists = host.fileExists; - - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - - return originalFileExists.call(host, fileName); - }; - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); - - fileExistsCalledForBar = false; - root.content = `import {y} from "bar"`; - host.reloadFS(files.concat(imported)); - - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;export const x = 10;` - }; - - const files = [root, libFile]; - const filesWithImported = files.concat(imported); - const host = createWatchedSystem(filesWithImported); - const originalFileExists = host.fileExists; - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - return originalFileExists.call(host, fileName); - }; - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); - - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - checkOutputErrorsInitial(host, emptyArray); - - fileExistsCalledForBar = false; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - checkOutputErrorsIncremental(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "bar") - ]); - - fileExistsCalledForBar = false; - host.reloadFS(filesWithImported); - host.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(host, emptyArray); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }); - - it("works when module resolution changes to ambient module", () => { - const root = { - path: "/a/b/foo.ts", - content: `import * as fs from "fs";` - }; - - const packageJson = { - path: "/a/b/node_modules/@types/node/package.json", - content: ` -{ - "main": "" -} -` - }; - - const nodeType = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: ` -declare module "fs" { - export interface Stats { - isFile(): boolean; - } -}` - }; - - const files = [root, libFile]; - const filesWithNodeType = files.concat(packageJson, nodeType); - const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - - const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); - - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ]); - - host.reloadFS(filesWithNodeType); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - }); - - it("works when included file with ambient module changes", () => { - const root = { - path: "/a/b/foo.ts", - content: ` -import * as fs from "fs"; -import * as u from "url"; -` - }; - - const file = { - path: "/a/b/bar.d.ts", - content: ` -declare module "url" { - export interface Url { - href?: string; - } -} -` - }; - - const fileContentWithFS = ` -declare module "fs" { - export interface Stats { - isFile(): boolean; - } -} -`; - - const files = [root, file, libFile]; - const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - - const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); - - checkOutputErrorsInitial(host, [ - getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") - ]); - - file.content += fileContentWithFS; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkOutputErrorsIncremental(host, emptyArray); - }); - - it("works when reusing program with files from external library", () => { - interface ExpectedFile { path: string; isExpectedToEmit?: boolean; content?: string; } - const configDir = "/a/b/projects/myProject/src/"; - const file1: File = { - path: configDir + "file1.ts", - content: 'import module1 = require("module1");\nmodule1("hello");' - }; - const file2: File = { - path: configDir + "file2.ts", - content: 'import module11 = require("module1");\nmodule11("hello");' - }; - const module1: File = { - path: "/a/b/projects/myProject/node_modules/module1/index.js", - content: "module.exports = options => { return options.toString(); }" - }; - const configFile: File = { - path: configDir + "tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - allowJs: true, - rootDir: ".", - outDir: "../dist", - moduleResolution: "node", - maxNodeModuleJsDepth: 1 - } - }) - }; - 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 = createWatchOfConfigFile(configFile.path, host); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrorsInitial(host, emptyArray); - const expectedFiles: ExpectedFile[] = [ - createExpectedEmittedFile(file1), - createExpectedEmittedFile(file2), - createExpectedToNotEmitFile("index.js"), - createExpectedToNotEmitFile("src/index.js"), - createExpectedToNotEmitFile("src/file1.js"), - createExpectedToNotEmitFile("src/file2.js"), - createExpectedToNotEmitFile("lib.js"), - createExpectedToNotEmitFile("lib.d.ts") - ]; - verifyExpectedFiles(expectedFiles); - - file1.content += "\n;"; - expectedFiles[0].content += ";\n"; // Only emit file1 with this change - expectedFiles[1].isExpectedToEmit = false; - host.reloadFS(programFiles.concat(configFile)); - host.runQueuedTimeoutCallbacks(); - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrorsIncremental(host, emptyArray); - verifyExpectedFiles(expectedFiles); - - - function verifyExpectedFiles(expectedFiles: ExpectedFile[]) { - forEach(expectedFiles, f => { - assert.equal(!!host.fileExists(f.path), f.isExpectedToEmit, "File " + f.path + " is expected to " + (f.isExpectedToEmit ? "emit" : "not emit")); - if (f.isExpectedToEmit) { - assert.equal(host.readFile(f.path), f.content, "Expected contents of " + f.path); - } - }); - } - - function createExpectedToNotEmitFile(fileName: string): ExpectedFile { - return { - path: outDirFolder + fileName, - isExpectedToEmit: false - }; - } - - function createExpectedEmittedFile(file: File): ExpectedFile { - return { - path: removeFileExtension(file.path.replace(configDir, outDirFolder)) + Extension.Js, - isExpectedToEmit: true, - content: '"use strict";\nexports.__esModule = true;\n' + file.content.replace("import", "var") + "\n" - }; - } - }); - - it("works when renaming node_modules folder that already contains @types folder", () => { - const currentDirectory = "/user/username/projects/myproject"; - const file: File = { - path: `${currentDirectory}/a.ts`, - content: `import * as q from "qqq";` - }; - const module: File = { - path: `${currentDirectory}/node_modules2/@types/qqq/index.d.ts`, - content: "export {}" - }; - const files = [file, module, libFile]; - const host = createWatchedSystem(files, { currentDirectory }); - const watch = createWatchOfFilesAndCompilerOptions([file.path], host); - - checkProgramActualFiles(watch(), [file.path, libFile.path]); - checkOutputErrorsInitial(host, [getDiagnosticModuleNotFoundOfFile(watch(), file, "qqq")]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, [`${currentDirectory}/node_modules`, `${currentDirectory}/node_modules/@types`], /*recursive*/ true); - - host.renameFolder(`${currentDirectory}/node_modules2`, `${currentDirectory}/node_modules`); - host.runQueuedTimeoutCallbacks(); - checkProgramActualFiles(watch(), [file.path, libFile.path, `${currentDirectory}/node_modules/@types/qqq/index.d.ts`]); - checkOutputErrorsIncremental(host, emptyArray); - }); - - describe("ignores files/folder changes in node_modules that start with '.'", () => { - const projectPath = "/user/username/projects/project"; - const npmCacheFile: File = { - path: `${projectPath}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, - content: JSON.stringify({ something: 10 }) - }; - const file1: File = { - path: `${projectPath}/test.ts`, - content: `import { x } from "somemodule";` - }; - const file2: File = { - path: `${projectPath}/node_modules/somemodule/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2]; - const expectedFiles = files.map(f => f.path); - it("when watching node_modules in inferred project for failed lookup", () => { - const host = createWatchedSystem(files); - const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1); - checkProgramActualFiles(watch(), expectedFiles); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - it("when watching node_modules as part of wild card directories in config project", () => { - const config: File = { - path: `${projectPath}/tsconfig.json`, - content: "{}" - }; - const host = createWatchedSystem(files.concat(config)); - const watch = createWatchOfConfigFile(config.path, host); - checkProgramActualFiles(watch(), expectedFiles); - host.checkTimeoutQueueLength(0); - - host.ensureFileOrFolder(npmCacheFile); - host.checkTimeoutQueueLength(0); - }); - }); - }); - - describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => { - const projectRoot = "/user/username/projects/project"; - const mainPackageRoot = `${projectRoot}/main`; - const linkedPackageRoot = `${projectRoot}/linked-package`; - const mainFile: File = { - path: `${mainPackageRoot}/index.ts`, - content: "import { Foo } from '@scoped/linked-package'" - }; - const config: File = { - path: `${mainPackageRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, - files: ["index.ts"] - }) - }; - const linkedPackageInMain: SymLink = { - path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, - symLink: `${linkedPackageRoot}` - }; - const linkedPackageJson: File = { - path: `${linkedPackageRoot}/package.json`, - content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) - }; - const linkedPackageIndex: File = { - path: `${linkedPackageRoot}/dist/index.d.ts`, - content: "export * from './other';" - }; - const linkedPackageOther: File = { - path: `${linkedPackageRoot}/dist/other.d.ts`, - content: 'export declare const Foo = "BAR";' - }; - - it("verify watched directories", () => { - const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; - const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot }); - createWatchOfConfigFile("tsconfig.json", host); - checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, [`${mainPackageRoot}/@scoped`, `${mainPackageRoot}/node_modules`, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true); - }); - }); -} - namespace ts.projectSystem { function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) { const resolutionTrace: string[] = []; diff --git a/src/testRunner/unittests/session.ts b/src/testRunner/unittests/tsserver/session.ts similarity index 100% rename from src/testRunner/unittests/session.ts rename to src/testRunner/unittests/tsserver/session.ts diff --git a/src/testRunner/unittests/tsserver/skipLibCheck.ts b/src/testRunner/unittests/tsserver/skipLibCheck.ts new file mode 100644 index 00000000000..463ea439396 --- /dev/null +++ b/src/testRunner/unittests/tsserver/skipLibCheck.ts @@ -0,0 +1,229 @@ +namespace ts.projectSystem { + describe("tsserver:: with skipLibCheck", () => { + it("should be turned on for js-only inferred projects", () => { + const file1 = { + path: "/a/b/file1.js", + content: ` + /// + var x = 1;` + }; + const file2 = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([file1, file2]); + const session = createSession(host); + openFilesForSession([file1, file2], session); + + const file2GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: file2.path } + ); + let errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length === 0); + + const closeFileRequest = makeSessionRequest(CommandNames.Close, { file: file1.path }); + session.executeCommand(closeFileRequest); + errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length !== 0); + + openFilesForSession([file1], session); + errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should be turned on for js-only external projects", () => { + const jsFile = { + path: "/a/b/file1.js", + content: "let x =1;" + }; + const dTsFile = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([jsFile, dTsFile]); + const session = createSession(host); + + const openExternalProjectRequest = makeSessionRequest( + CommandNames.OpenExternalProject, + { + projectFileName: "project1", + rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), + options: {} + } + ); + session.executeCommand(openExternalProjectRequest); + + const dTsFileGetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile.path } + ); + const errorResult = session.executeCommand(dTsFileGetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should be turned on for js-only external projects with skipLibCheck=false", () => { + const jsFile = { + path: "/a/b/file1.js", + content: "let x =1;" + }; + const dTsFile = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([jsFile, dTsFile]); + const session = createSession(host); + + const openExternalProjectRequest = makeSessionRequest( + CommandNames.OpenExternalProject, + { + projectFileName: "project1", + rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), + options: { skipLibCheck: false } + } + ); + session.executeCommand(openExternalProjectRequest); + + const dTsFileGetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile.path } + ); + const errorResult = session.executeCommand(dTsFileGetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should not report bind errors for declaration files with skipLibCheck=true", () => { + const jsconfigFile = { + path: "/a/jsconfig.json", + content: "{}" + }; + const jsFile = { + path: "/a/jsFile.js", + content: "let x = 1;" + }; + const dTsFile1 = { + path: "/a/dTsFile1.d.ts", + content: ` + declare var x: number;` + }; + const dTsFile2 = { + path: "/a/dTsFile2.d.ts", + content: ` + declare var x: string;` + }; + const host = createServerHost([jsconfigFile, jsFile, dTsFile1, dTsFile2]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const dTsFile1GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile1.path } + ); + const error1Result = session.executeCommand(dTsFile1GetErrRequest).response; + assert.isTrue(error1Result.length === 0); + + const dTsFile2GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile2.path } + ); + const error2Result = session.executeCommand(dTsFile2GetErrRequest).response; + assert.isTrue(error2Result.length === 0); + }); + + it("should report semantic errors for loose JS files with '// @ts-check' and skipLibCheck=true", () => { + const jsFile = { + path: "/a/jsFile.js", + content: ` + // @ts-check + let x = 1; + x === "string";` + }; + + const host = createServerHost([jsFile]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const getErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: jsFile.path } + ); + const errorResult = session.executeCommand(getErrRequest).response; + assert.isTrue(errorResult.length === 1); + assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); + }); + + it("should report semantic errors for configured js project with '// @ts-check' and skipLibCheck=true", () => { + const jsconfigFile = { + path: "/a/jsconfig.json", + content: "{}" + }; + + const jsFile = { + path: "/a/jsFile.js", + content: ` + // @ts-check + let x = 1; + x === "string";` + }; + + const host = createServerHost([jsconfigFile, jsFile]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const getErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: jsFile.path } + ); + const errorResult = session.executeCommand(getErrRequest).response; + assert.isTrue(errorResult.length === 1); + assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); + }); + + it("should report semantic errors for configured js project with checkJs=true and skipLibCheck=true", () => { + const jsconfigFile = { + path: "/a/jsconfig.json", + content: JSON.stringify({ + compilerOptions: { + checkJs: true, + skipLibCheck: true + }, + }) + }; + const jsFile = { + path: "/a/jsFile.js", + content: `let x = 1; + x === "string";` + }; + + const host = createServerHost([jsconfigFile, jsFile]); + const session = createSession(host); + openFilesForSession([jsFile], session); + + const getErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: jsFile.path } + ); + const errorResult = session.executeCommand(getErrRequest).response; + assert.isTrue(errorResult.length === 1); + assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverSymLinks.ts b/src/testRunner/unittests/tsserver/symLinks.ts similarity index 97% rename from src/testRunner/unittests/tsserverSymLinks.ts rename to src/testRunner/unittests/tsserver/symLinks.ts index c9bad7ea678..d6fa9dca894 100644 --- a/src/testRunner/unittests/tsserverSymLinks.ts +++ b/src/testRunner/unittests/tsserver/symLinks.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserverProjectSystem with symLinks", () => { + describe("tsserver:: symLinks", () => { it("rename in common file renames all project", () => { const projects = "/users/username/projects"; const folderA = `${projects}/a`; diff --git a/src/testRunner/unittests/telemetry.ts b/src/testRunner/unittests/tsserver/telemetry.ts similarity index 100% rename from src/testRunner/unittests/telemetry.ts rename to src/testRunner/unittests/tsserver/telemetry.ts diff --git a/src/testRunner/unittests/textStorage.ts b/src/testRunner/unittests/tsserver/textStorage.ts similarity index 100% rename from src/testRunner/unittests/textStorage.ts rename to src/testRunner/unittests/tsserver/textStorage.ts diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts similarity index 100% rename from src/testRunner/unittests/typingsInstaller.ts rename to src/testRunner/unittests/tsserver/typingsInstaller.ts diff --git a/src/testRunner/unittests/versionCache.ts b/src/testRunner/unittests/tsserver/versionCache.ts similarity index 100% rename from src/testRunner/unittests/versionCache.ts rename to src/testRunner/unittests/tsserver/versionCache.ts diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts new file mode 100644 index 00000000000..8bce3505626 --- /dev/null +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -0,0 +1,135 @@ +namespace ts.projectSystem { + import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; + describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { + function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const index: File = { + path: `${projectSrcFolder}/index.ts`, + content: `import {} from "./"` + }; + const file1: File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + + const files = [index, file1, configFile, libFile]; + const fileNames = files.map(file => file.path); + // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder + const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); + const expectedWatchedDirectories = createMap(); + const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? + expectedWatchedDirectories : + tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? + expectedWatchedFiles : + createMap(); + // For failed resolution lookup and tsconfig files => cached so only watched only once + mapOfDirectories.set(projectFolder, 1); + // Through above recursive watches + mapOfDirectories.set(projectSrcFolder, 1); + // node_modules/@types folder + mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); + const expectedCompletions = ["file1"]; + const completionPosition = index.content.lastIndexOf('"'); + const environmentVariables = createMap(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + const host = createServerHost(files, { environmentVariables }); + const projectService = createProjectService(host); + projectService.openClientFile(index.path); + + const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); + verifyProjectAndCompletions(); + + // Add file2 + const file2: File = { + path: `${projectSrcFolder}/file2.ts`, + content: "" + }; + files.push(file2); + fileNames.push(file2.path); + expectedWatchedFiles.set(file2.path, 1); + expectedCompletions.push("file2"); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.equal(projectService.configuredProjects.get(configFile.path), project); + verifyProjectAndCompletions(); + + function verifyProjectAndCompletions() { + const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; + checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); + + checkWatchedDirectories(host, emptyArray, /*recursive*/ true); + + checkWatchedFilesDetailed(host, expectedWatchedFiles); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); + checkProjectActualFiles(project, fileNames); + } + } + + it("uses watchFile when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); + }); + + it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); + }); + + it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { + verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); + }); + }); + + describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { + function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { + const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; + const configFile: File = { + path: root + "project/tsconfig.json", + content: "{}" + }; + const file1: File = { + path: root + "project/file1.ts", + content: "let x = 10;" + }; + const file2: File = { + path: root + "project/file2.ts", + content: "let y = 10;" + }; + const files = [configFile, file1, file2, libFile]; + const host = createServerHost(files, { useWindowsStylePaths: true }); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); + checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); + checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, [ + root + "project", + root + "project/node_modules/@types" + ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); + } + + function verifyRootedDirectoryWatch(rootedPath: string) { + it("When project is in rootFolder of style c:/", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); + }); + + it("When files at some folder other than root", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); + }); + } + + describe("for rootFolder of style c:/", () => { + verifyRootedDirectoryWatch("c:/"); + }); + + describe("for rootFolder of style c:/users/username", () => { + verifyRootedDirectoryWatch("c:/users/username/"); + }); + }); +} diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 56493c01e35..85be445171a 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -3280,305 +3280,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem external projects", () => { - describe("correctly handling add/remove tsconfig - 1", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const f2 = { - path: "/a/b/lib.ts", - content: "" - }; - const tsconfig = { - path: "/a/b/tsconfig.json", - content: "" - }; - const host = createServerHost([f1, f2]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - - // rename lib.ts to tsconfig.json - host.reloadFS([f1, tsconfig]); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, tsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - - // rename tsconfig.json back to lib.ts - host.reloadFS([f1, f2]); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - describe("correctly handling add/remove tsconfig - 2", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const cLib = { - path: "/a/b/c/lib.ts", - content: "" - }; - const cTsconfig = { - path: "/a/b/c/tsconfig.json", - content: "{}" - }; - const dLib = { - path: "/a/b/d/lib.ts", - content: "" - }; - const dTsconfig = { - path: "/a/b/d/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // remove one config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, dTsconfig.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - - // remove second config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // open two config files - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // close all projects - no projects should be opened - projectService.closeExternalProject(projectName); - projectService.checkNumberOfProjects({}); - } - - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - it("correctly handles changes in lib section of config file", () => { - const libES5 = { - path: "/compiler/lib.es5.d.ts", - content: "declare const eval: any" - }; - const libES2015Promise = { - path: "/compiler/lib.es2015.promise.d.ts", - content: "declare class Promise {}" - }; - const app = { - path: "/src/app.ts", - content: "var x: Promise;" - }; - const config1 = { - path: "/src/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5" - ] - } - }) - }; - const config2 = { - path: config1.path, - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5", - "es2015.promise" - ] - } - }) - }; - const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const projectService = createProjectService(host); - projectService.openClientFile(app.path); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); - - host.reloadFS([libES5, libES2015Promise, app, config2]); - host.checkTimeoutQueueLengthAndRun(2); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); - }); - - it("should handle non-existing directories in config file", () => { - const f = { - path: "/a/src/app.ts", - content: "let x = 1;" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - include: [ - "src/**/*", - "notexistingfolder/*" - ] - }) - }; - const host = createServerHost([f, config]); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - assert.isTrue(project.hasOpenRef()); // f - - projectService.closeClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); - - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isTrue(project.hasOpenRef()); // f - assert.isFalse(project.isClosed()); - }); - - it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; - const projectFileName = "/a/b/project.csproj"; - const host = createServerHost([f1, config]); - const service = createProjectService(host); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), - options: {} - }); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project, [config.path, f1.path]); - - service.closeExternalProject(projectFileName); - service.checkNumberOfProjects({}); - - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), - options: {} - }); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project2 = service.configuredProjects.get(config.path)!; - assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project2, [config.path, f1.path]); - }); - }); - describe("tsserverProjectSystem prefer typings to js", () => { it("during second resolution pass", () => { const typingsCacheLocation = "/a/typings"; @@ -3770,234 +3471,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem skipLibCheck", () => { - it("should be turned on for js-only inferred projects", () => { - const file1 = { - path: "/a/b/file1.js", - content: ` - /// - var x = 1;` - }; - const file2 = { - path: "/a/b/file2.d.ts", - content: ` - interface T { - name: string; - }; - interface T { - name: number; - };` - }; - const host = createServerHost([file1, file2]); - const session = createSession(host); - openFilesForSession([file1, file2], session); - - const file2GetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: file2.path } - ); - let errorResult = session.executeCommand(file2GetErrRequest).response; - assert.isTrue(errorResult.length === 0); - - const closeFileRequest = makeSessionRequest(CommandNames.Close, { file: file1.path }); - session.executeCommand(closeFileRequest); - errorResult = session.executeCommand(file2GetErrRequest).response; - assert.isTrue(errorResult.length !== 0); - - openFilesForSession([file1], session); - errorResult = session.executeCommand(file2GetErrRequest).response; - assert.isTrue(errorResult.length === 0); - }); - - it("should be turned on for js-only external projects", () => { - const jsFile = { - path: "/a/b/file1.js", - content: "let x =1;" - }; - const dTsFile = { - path: "/a/b/file2.d.ts", - content: ` - interface T { - name: string; - }; - interface T { - name: number; - };` - }; - const host = createServerHost([jsFile, dTsFile]); - const session = createSession(host); - - const openExternalProjectRequest = makeSessionRequest( - CommandNames.OpenExternalProject, - { - projectFileName: "project1", - rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), - options: {} - } - ); - session.executeCommand(openExternalProjectRequest); - - const dTsFileGetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile.path } - ); - const errorResult = session.executeCommand(dTsFileGetErrRequest).response; - assert.isTrue(errorResult.length === 0); - }); - - it("should be turned on for js-only external projects with skipLibCheck=false", () => { - const jsFile = { - path: "/a/b/file1.js", - content: "let x =1;" - }; - const dTsFile = { - path: "/a/b/file2.d.ts", - content: ` - interface T { - name: string; - }; - interface T { - name: number; - };` - }; - const host = createServerHost([jsFile, dTsFile]); - const session = createSession(host); - - const openExternalProjectRequest = makeSessionRequest( - CommandNames.OpenExternalProject, - { - projectFileName: "project1", - rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), - options: { skipLibCheck: false } - } - ); - session.executeCommand(openExternalProjectRequest); - - const dTsFileGetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile.path } - ); - const errorResult = session.executeCommand(dTsFileGetErrRequest).response; - assert.isTrue(errorResult.length === 0); - }); - - it("should not report bind errors for declaration files with skipLibCheck=true", () => { - const jsconfigFile = { - path: "/a/jsconfig.json", - content: "{}" - }; - const jsFile = { - path: "/a/jsFile.js", - content: "let x = 1;" - }; - const dTsFile1 = { - path: "/a/dTsFile1.d.ts", - content: ` - declare var x: number;` - }; - const dTsFile2 = { - path: "/a/dTsFile2.d.ts", - content: ` - declare var x: string;` - }; - const host = createServerHost([jsconfigFile, jsFile, dTsFile1, dTsFile2]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const dTsFile1GetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile1.path } - ); - const error1Result = session.executeCommand(dTsFile1GetErrRequest).response; - assert.isTrue(error1Result.length === 0); - - const dTsFile2GetErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: dTsFile2.path } - ); - const error2Result = session.executeCommand(dTsFile2GetErrRequest).response; - assert.isTrue(error2Result.length === 0); - }); - - it("should report semantic errors for loose JS files with '// @ts-check' and skipLibCheck=true", () => { - const jsFile = { - path: "/a/jsFile.js", - content: ` - // @ts-check - let x = 1; - x === "string";` - }; - - const host = createServerHost([jsFile]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const getErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: jsFile.path } - ); - const errorResult = session.executeCommand(getErrRequest).response; - assert.isTrue(errorResult.length === 1); - assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); - }); - - it("should report semantic errors for configured js project with '// @ts-check' and skipLibCheck=true", () => { - const jsconfigFile = { - path: "/a/jsconfig.json", - content: "{}" - }; - - const jsFile = { - path: "/a/jsFile.js", - content: ` - // @ts-check - let x = 1; - x === "string";` - }; - - const host = createServerHost([jsconfigFile, jsFile]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const getErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: jsFile.path } - ); - const errorResult = session.executeCommand(getErrRequest).response; - assert.isTrue(errorResult.length === 1); - assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); - }); - - it("should report semantic errors for configured js project with checkJs=true and skipLibCheck=true", () => { - const jsconfigFile = { - path: "/a/jsconfig.json", - content: JSON.stringify({ - compilerOptions: { - checkJs: true, - skipLibCheck: true - }, - }) - }; - const jsFile = { - path: "/a/jsFile.js", - content: `let x = 1; - x === "string";` - }; - - const host = createServerHost([jsconfigFile, jsFile]); - const session = createSession(host); - openFilesForSession([jsFile], session); - - const getErrRequest = makeSessionRequest( - CommandNames.SemanticDiagnosticsSync, - { file: jsFile.path } - ); - const errorResult = session.executeCommand(getErrRequest).response; - assert.isTrue(errorResult.length === 1); - assert.equal(errorResult[0].code, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap.code); - }); - }); - describe("tsserverProjectSystem non-existing directories listed in config file input array", () => { it("should be tolerated without crashing the server", () => { const configFile = { @@ -4079,156 +3552,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem reload", () => { - it("should work with temp file", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const tmp = { - path: "/a/b/app.tmp", - content: "const y = 42" - }; - const host = createServerHost([f1, tmp]); - const session = createSession(host); - - // send open request - session.executeCommand({ - type: "request", - command: "open", - seq: 1, - arguments: { file: f1.path } - }); - - // reload from tmp file - session.executeCommand({ - type: "request", - command: "reload", - seq: 2, - arguments: { file: f1.path, tmpfile: tmp.path } - }); - - // verify content - const projectServiice = session.getProjectService(); - const snap1 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); - assert.equal(getSnapshotText(snap1), tmp.content, "content should be equal to the content of temp file"); - - // reload from original file file - session.executeCommand({ - type: "request", - command: "reload", - seq: 2, - arguments: { file: f1.path } - }); - - // verify content - const snap2 = projectServiice.getScriptInfo(f1.path)!.getSnapshot(); - assert.equal(getSnapshotText(snap2), f1.content, "content should be equal to the content of original file"); - - }); - - it("should work when script info doesnt have any project open", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const tmp = { - path: "/a/b/app.tmp", - content: "const y = 42" - }; - const host = createServerHost([f1, tmp, libFile]); - const session = createSession(host); - const openContent = "let z = 1"; - // send open request - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Open, - arguments: { file: f1.path, fileContent: openContent } - }); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const info = projectService.getScriptInfo(f1.path)!; - assert.isDefined(info); - checkScriptInfoContents(openContent, "contents set during open request"); - - // send close request - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Close, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - checkInferredProjectIsOrphan(); - - // Can reload contents of the file when its not open and has no project - // reload from temp file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path, tmpfile: tmp.path } - }); - checkScriptInfoAndProjects(tmp.content, "contents of temp file"); - checkInferredProjectIsOrphan(); - - // reload from own file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - checkInferredProjectIsOrphan(); - - // Open file again without setting its content - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Open, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of file when opened without specifying contents"); - const snap = info.getSnapshot(); - - // send close request - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Close, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - assert.strictEqual(info.getSnapshot(), snap); - checkInferredProjectIsOrphan(); - - // reload from temp file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path, tmpfile: tmp.path } - }); - checkScriptInfoAndProjects(tmp.content, "contents of temp file"); - assert.notStrictEqual(info.getSnapshot(), snap); - checkInferredProjectIsOrphan(); - - // reload from own file - session.executeCommandSeq({ - command: server.protocol.CommandTypes.Reload, - arguments: { file: f1.path } - }); - checkScriptInfoAndProjects(f1.content, "contents of closed file"); - assert.notStrictEqual(info.getSnapshot(), snap); - checkInferredProjectIsOrphan(); - - function checkInferredProjectIsOrphan() { - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - assert.equal(info.containingProjects.length, 0); - } - - function checkScriptInfoAndProjects(contentsOfInfo: string, captionForContents: string) { - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.strictEqual(projectService.getScriptInfo(f1.path), info); - checkScriptInfoContents(contentsOfInfo, captionForContents); - } - - function checkScriptInfoContents(contentsOfInfo: string, captionForContents: string) { - const snap = info.getSnapshot(); - assert.equal(getSnapshotText(snap), contentsOfInfo, "content should be equal to " + captionForContents); - } - }); - }); - describe("tsserverProjectSystem Inferred projects", () => { it("should support files without extensions", () => { const f = { diff --git a/src/testRunner/unittests/watchEnvironment.ts b/src/testRunner/unittests/watchEnvironment.ts deleted file mode 100644 index 74a6637d6fc..00000000000 --- a/src/testRunner/unittests/watchEnvironment.ts +++ /dev/null @@ -1,316 +0,0 @@ -namespace ts { - import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - - export namespace tscWatch { - describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => { - it("watchFile using dynamic priority polling", () => { - const projectFolder = "/a/username/project"; - const file1: File = { - path: `${projectFolder}/typescript.ts`, - content: "var z = 10;" - }; - const files = [file1, libFile]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling"); - const host = createWatchedSystem(files, { environmentVariables }); - const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); - - const initialProgram = watch(); - verifyProgram(); - - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - for (let index = 0; index < mediumPollingIntervalThreshold; index++) { - // Transition libFile and file1 to low priority queue - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), initialProgram); - } - - // Make a change to file - file1.content = "var zz30 = 100;"; - host.reloadFS(files); - - // This should detect change in the file - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), initialProgram); - - // Callbacks: medium priority + high priority queue and scheduled program update - host.checkTimeoutQueueLengthAndRun(3); - // During this timeout the file would be detected as unchanged - let fileUnchangeDetected = 1; - const newProgram = watch(); - assert.notStrictEqual(newProgram, initialProgram); - - verifyProgram(); - const outputFile1 = changeExtension(file1.path, ".js"); - assert.isTrue(host.fileExists(outputFile1)); - assert.equal(host.readFile(outputFile1), file1.content + host.newLine); - - const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; - for (; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { - // For high + Medium/low polling interval - host.checkTimeoutQueueLengthAndRun(2); - assert.deepEqual(watch(), newProgram); - } - - // Everything goes in high polling interval queue - host.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(watch(), newProgram); - - function verifyProgram() { - checkProgramActualFiles(watch(), files.map(f => f.path)); - checkWatchedFiles(host, []); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, [], /*recursive*/ true); - } - }); - - describe("tsc-watch when watchDirectories implementation", () => { - function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const file: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" - }; - const programFiles = [file, libFile]; - const files = [file, configFile, libFile]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - const host = createWatchedSystem(files, { environmentVariables }); - const watch = createWatchOfConfigFile(configFile.path, host); - const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`]; - // Watching files config file, file, lib file - const expectedWatchedFiles = files.map(f => f.path); - const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray; - if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) { - expectedWatchedFiles.push(...projectFolders); - } - - verifyProgram(checkOutputErrorsInitial); - - // Rename the file: - file.path = file.path.replace("file1.ts", "file2.ts"); - expectedWatchedFiles[0] = file.path; - host.reloadFS(files); - if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { - // With dynamic polling the fs change would be detected only by running timeouts - host.runQueuedTimeoutCallbacks(); - } - // Delayed update program - host.runQueuedTimeoutCallbacks(); - verifyProgram(checkOutputErrorsIncremental); - - function verifyProgram(checkOutputErrors: (host: WatchedSystem, errors: ReadonlyArray) => void) { - checkProgramActualFiles(watch(), programFiles.map(f => f.path)); - checkOutputErrors(host, emptyArray); - - const outputFile = changeExtension(file.path, ".js"); - assert(host.fileExists(outputFile)); - assert.equal(host.readFile(outputFile), file.content); - - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - // Watching config file, file, lib file and directories - checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false); - } - } - - it("uses watchFile when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile); - }); - - it("uses non recursive watchDirectory when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); - }); - - it("uses non recursive dynamic polling when renaming file in subfolder", () => { - verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); - }); - - it("when there are symlinks to folders in recursive folders", () => { - const cwd = "/home/user/projects/myproject"; - const file1: File = { - path: `${cwd}/src/file.ts`, - content: `import * as a from "a"` - }; - const tsconfig: File = { - path: `${cwd}/tsconfig.json`, - content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` - }; - const realA: File = { - path: `${cwd}/node_modules/reala/index.d.ts`, - content: `export {}` - }; - const realB: File = { - path: `${cwd}/node_modules/realb/index.d.ts`, - content: `export {}` - }; - const symLinkA: SymLink = { - path: `${cwd}/node_modules/a`, - symLink: `${cwd}/node_modules/reala` - }; - const symLinkB: SymLink = { - path: `${cwd}/node_modules/b`, - symLink: `${cwd}/node_modules/realb` - }; - const symLinkBInA: SymLink = { - path: `${cwd}/node_modules/reala/node_modules/b`, - symLink: `${cwd}/node_modules/b` - }; - const symLinkAInB: SymLink = { - path: `${cwd}/node_modules/realb/node_modules/a`, - symLink: `${cwd}/node_modules/a` - }; - const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); - createWatchOfConfigFile("tsconfig.json", host); - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`, - `${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false); - }); - }); - }); - } - - export namespace projectSystem { - describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { - function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const index: File = { - path: `${projectSrcFolder}/index.ts`, - content: `import {} from "./"` - }; - const file1: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" - }; - - const files = [index, file1, configFile, libFile]; - const fileNames = files.map(file => file.path); - // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder - const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); - const expectedWatchedDirectories = createMap(); - const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? - expectedWatchedDirectories : - tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? - expectedWatchedFiles : - createMap(); - // For failed resolution lookup and tsconfig files => cached so only watched only once - mapOfDirectories.set(projectFolder, 1); - // Through above recursive watches - mapOfDirectories.set(projectSrcFolder, 1); - // node_modules/@types folder - mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); - const expectedCompletions = ["file1"]; - const completionPosition = index.content.lastIndexOf('"'); - const environmentVariables = createMap(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - const host = createServerHost(files, { environmentVariables }); - const projectService = createProjectService(host); - projectService.openClientFile(index.path); - - const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path)); - verifyProjectAndCompletions(); - - // Add file2 - const file2: File = { - path: `${projectSrcFolder}/file2.ts`, - content: "" - }; - files.push(file2); - fileNames.push(file2.path); - expectedWatchedFiles.set(file2.path, 1); - expectedCompletions.push("file2"); - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - assert.equal(projectService.configuredProjects.get(configFile.path), project); - verifyProjectAndCompletions(); - - function verifyProjectAndCompletions() { - const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; - checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); - - checkWatchedDirectories(host, emptyArray, /*recursive*/ true); - - checkWatchedFilesDetailed(host, expectedWatchedFiles); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); - checkProjectActualFiles(project, fileNames); - } - } - - it("uses watchFile when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); - }); - - it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); - }); - - it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { - verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); - }); - }); - - describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { - function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { - const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; - const configFile: File = { - path: root + "project/tsconfig.json", - content: "{}" - }; - const file1: File = { - path: root + "project/file1.ts", - content: "let x = 10;" - }; - const file2: File = { - path: root + "project/file2.ts", - content: "let y = 10;" - }; - const files = [configFile, file1, file2, libFile]; - const host = createServerHost(files, { useWindowsStylePaths: true }); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); - checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); - checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, [ - root + "project", - root + "project/node_modules/@types" - ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); - } - - function verifyRootedDirectoryWatch(rootedPath: string) { - it("When project is in rootFolder of style c:/", () => { - verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); - }); - - it("When files at some folder other than root", () => { - verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); - }); - } - - describe("for rootFolder of style c:/", () => { - verifyRootedDirectoryWatch("c:/"); - }); - - describe("for rootFolder of style c:/users/username", () => { - verifyRootedDirectoryWatch("c:/users/username/"); - }); - }); - } -} From 53e2507f603836e6ff268d1a5e2e87ba2265e4c4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Dec 2018 16:04:49 -0800 Subject: [PATCH 090/120] More scenarios in their own test --- src/testRunner/tsconfig.json | 26 +- .../unittests/config/commandLineParsing.ts | 4 +- .../config/configurationExtension.ts | 2 +- .../config/convertCompilerOptionsFromJson.ts | 2 +- .../config/convertTypeAcquisitionFromJson.ts | 2 +- .../unittests/config/initializeTSConfig.ts | 4 +- src/testRunner/unittests/config/matchFiles.ts | 2 +- .../unittests/config/projectReferences.ts | 12 +- src/testRunner/unittests/config/showConfig.ts | 2 +- .../unittests/config/tsconfigParsing.ts | 2 +- .../unittests/evaluation/asyncArrow.ts | 4 +- .../unittests/evaluation/asyncGenerator.ts | 4 +- .../unittests/evaluation/forAwaitOf.ts | 2 +- .../cancellableLanguageServiceOperations.ts | 2 +- .../unittests/services/colorization.ts | 2 +- .../services/convertToAsyncFunction.ts | 2 +- .../unittests/services/documentRegistry.ts | 2 +- .../unittests/services/extract/constants.ts | 2 +- .../unittests/services/extract/functions.ts | 2 +- .../unittests/services/extract/ranges.ts | 4 +- .../services/extract/symbolWalker.ts | 2 +- .../unittests/services/hostNewLineSupport.ts | 4 +- .../unittests/services/languageService.ts | 4 +- .../unittests/services/organizeImports.ts | 2 +- .../unittests/services/patternMatcher.ts | 2 +- .../unittests/services/preProcessFile.ts | 2 +- .../unittests/services/textChanges.ts | 4 +- .../unittests/{ => services}/transpile.ts | 2 +- src/testRunner/unittests/tscWatch/emit.ts | 8 +- src/testRunner/unittests/tscWatch/helpers.ts | 9 + .../unittests/tscWatch/resolutionCache.ts | 4 +- src/testRunner/unittests/tscWatch/watchApi.ts | 2 +- .../unittests/tscWatch/watchEnvironment.ts | 2 +- src/testRunner/unittests/tscWatchMode.ts | 9 - .../unittests/tsserver/cancellationToken.ts | 271 ++ .../unittests/tsserver/completions.ts | 122 + .../unittests/tsserver/configFileSearch.ts | 174 + .../unittests/tsserver/declarationFileMaps.ts | 566 +++ .../unittests/tsserver/documentRegistry.ts | 94 + .../unittests/tsserver/duplicatePackages.ts | 54 + .../forceConsistentCasingInFileNames.ts | 45 + .../unittests/tsserver/formatSettings.ts | 39 + .../tsserver/getEditsForFileRename.ts | 105 + src/testRunner/unittests/tsserver/helpers.ts | 58 +- .../unittests/tsserver/importHelpers.ts | 18 + .../unittests/tsserver/inferredProjects.ts | 229 ++ .../unittests/tsserver/languageService.ts | 19 + .../tsserver/maxNodeModuleJsDepth.ts | 55 + .../unittests/tsserver/metadataInResponse.ts | 99 + src/testRunner/unittests/tsserver/navTo.ts | 30 + .../unittests/tsserver/occurences.ts | 47 + src/testRunner/unittests/tsserver/openFile.ts | 108 + .../unittests/tsserver/projectReferences.ts | 658 ++++ .../unittests/tsserver/refactors.ts | 120 + src/testRunner/unittests/tsserver/rename.ts | 53 + .../unittests/tsserver/resolutionCache.ts | 10 +- .../unittests/tsserver/syntaxOperations.ts | 98 + .../unittests/tsserver/telemetry.ts | 2 +- .../unittests/tsserver/textStorage.ts | 2 +- .../unittests/tsserver/typeAquisition.ts | 52 + .../tsserver/typeReferenceDirectives.ts | 87 + .../unittests/tsserver/typingsInstaller.ts | 20 +- .../unittests/tsserver/untitledFiles.ts | 45 + .../unittests/tsserver/versionCache.ts | 6 +- .../unittests/tsserver/watchEnvironment.ts | 4 +- .../unittests/tsserverProjectSystem.ts | 3185 +---------------- 66 files changed, 3308 insertions(+), 3307 deletions(-) rename src/testRunner/unittests/{ => services}/transpile.ts (97%) create mode 100644 src/testRunner/unittests/tsserver/cancellationToken.ts create mode 100644 src/testRunner/unittests/tsserver/completions.ts create mode 100644 src/testRunner/unittests/tsserver/configFileSearch.ts create mode 100644 src/testRunner/unittests/tsserver/declarationFileMaps.ts create mode 100644 src/testRunner/unittests/tsserver/documentRegistry.ts create mode 100644 src/testRunner/unittests/tsserver/duplicatePackages.ts create mode 100644 src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts create mode 100644 src/testRunner/unittests/tsserver/formatSettings.ts create mode 100644 src/testRunner/unittests/tsserver/getEditsForFileRename.ts create mode 100644 src/testRunner/unittests/tsserver/importHelpers.ts create mode 100644 src/testRunner/unittests/tsserver/inferredProjects.ts create mode 100644 src/testRunner/unittests/tsserver/languageService.ts create mode 100644 src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts create mode 100644 src/testRunner/unittests/tsserver/metadataInResponse.ts create mode 100644 src/testRunner/unittests/tsserver/navTo.ts create mode 100644 src/testRunner/unittests/tsserver/occurences.ts create mode 100644 src/testRunner/unittests/tsserver/openFile.ts create mode 100644 src/testRunner/unittests/tsserver/projectReferences.ts create mode 100644 src/testRunner/unittests/tsserver/refactors.ts create mode 100644 src/testRunner/unittests/tsserver/rename.ts create mode 100644 src/testRunner/unittests/tsserver/syntaxOperations.ts create mode 100644 src/testRunner/unittests/tsserver/typeAquisition.ts create mode 100644 src/testRunner/unittests/tsserver/typeReferenceDirectives.ts create mode 100644 src/testRunner/unittests/tsserver/untitledFiles.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index a7d82b84464..4d200c4b8f9 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -58,7 +58,6 @@ "unittests/reuseProgramStructure.ts", "unittests/semver.ts", "unittests/transform.ts", - "unittests/transpile.ts", "unittests/tsbuild.ts", "unittests/tsbuildWatchMode.ts", "unittests/tscWatchMode.ts", @@ -89,25 +88,50 @@ "unittests/services/patternMatcher.ts", "unittests/services/preProcessFile.ts", "unittests/services/textChanges.ts", + "unittests/services/transpile.ts", "unittests/tscWatch/emit.ts", "unittests/tscWatch/resolutionCache.ts", "unittests/tscWatch/watchEnvironment.ts", "unittests/tscWatch/watchApi.ts", "unittests/tsserver/cachingFileSystemInformation.ts", + "unittests/tsserver/cancellationToken.ts", "unittests/tsserver/compileOnSave.ts", + "unittests/tsserver/completions.ts", + "unittests/tsserver/configFileSearch.ts", + "unittests/tsserver/declarationFileMaps.ts", + "unittests/tsserver/documentRegistry.ts", + "unittests/tsserver/duplicatePackages.ts", "unittests/tsserver/events/largeFileReferenced.ts", "unittests/tsserver/events/projectLoading.ts", "unittests/tsserver/events/projectUpdatedInBackground.ts", "unittests/tsserver/externalProjects.ts", + "unittests/tsserver/forceConsistentCasingInFileNames.ts", + "unittests/tsserver/formatSettings.ts", + "unittests/tsserver/getEditsForFileRename.ts", + "unittests/tsserver/importHelpers.ts", + "unittests/tsserver/inferredProjects.ts", + "unittests/tsserver/languageService.ts", + "unittests/tsserver/maxNodeModuleJsDepth.ts", + "unittests/tsserver/metadataInResponse.ts", + "unittests/tsserver/navTo.ts", + "unittests/tsserver/occurences.ts", + "unittests/tsserver/openFile.ts", "unittests/tsserver/projectErrors.ts", + "unittests/tsserver/projectReferences.ts", + "unittests/tsserver/refactors.ts", "unittests/tsserver/reload.ts", + "unittests/tsserver/rename.ts", "unittests/tsserver/resolutionCache.ts", "unittests/tsserver/session.ts", "unittests/tsserver/skipLibCheck.ts", "unittests/tsserver/symLinks.ts", + "unittests/tsserver/syntaxOperations.ts", "unittests/tsserver/textStorage.ts", "unittests/tsserver/telemetry.ts", + "unittests/tsserver/typeAquisition.ts", + "unittests/tsserver/typeReferenceDirectives.ts", "unittests/tsserver/typingsInstaller.ts", + "unittests/tsserver/untitledFiles.ts", "unittests/tsserver/versionCache.ts", "unittests/tsserver/watchEnvironment.ts" ] diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index 7e8ba8f84bb..be7f6dca3c5 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("commandLineParsing:: parseCommandLine", () => { + describe("config:: commandLineParsing:: parseCommandLine", () => { function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) { const parsed = parseCommandLine(commandLine); @@ -367,7 +367,7 @@ namespace ts { }); }); - describe("commandLineParsing:: parseBuildOptions", () => { + describe("config:: commandLineParsing:: parseBuildOptions", () => { function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) { const parsed = parseBuildCommand(commandLine); const parsedBuildOptions = JSON.stringify(parsed.buildOptions); diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index 57c899eb5a8..0e7cd0821c4 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -208,7 +208,7 @@ namespace ts { } } - describe("configurationExtension", () => { + describe("config:: configurationExtension", () => { forEach<[string, string, fakes.ParseConfigHost], void>([ ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] diff --git a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts index 7b8af54f930..4a3a42ce216 100644 --- a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts +++ b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts @@ -1,5 +1,5 @@ namespace ts { - describe("convertCompilerOptionsFromJson", () => { + describe("config:: convertCompilerOptionsFromJson", () => { const formatDiagnosticHost: FormatDiagnosticsHost = { getCurrentDirectory: () => "/apath/", getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true), diff --git a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts index b46d37f0428..0998b0b0d7a 100644 --- a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts +++ b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts @@ -1,6 +1,6 @@ namespace ts { interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; } - describe("convertTypeAcquisitionFromJson", () => { + describe("config:: convertTypeAcquisitionFromJson", () => { function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { assertTypeAcquisitionWithJson(json, configFileName, expectedResult); assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); diff --git a/src/testRunner/unittests/config/initializeTSConfig.ts b/src/testRunner/unittests/config/initializeTSConfig.ts index 679eecf71b1..09d80c7f5c2 100644 --- a/src/testRunner/unittests/config/initializeTSConfig.ts +++ b/src/testRunner/unittests/config/initializeTSConfig.ts @@ -1,5 +1,5 @@ namespace ts { - describe("initTSConfig", () => { + describe("config:: initTSConfig", () => { function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) { describe(name, () => { const commandLine = parseCommandLine(commandLinesArgs); @@ -30,4 +30,4 @@ namespace ts { initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/config/matchFiles.ts b/src/testRunner/unittests/config/matchFiles.ts index 5ece71245bb..9155cb26a1c 100644 --- a/src/testRunner/unittests/config/matchFiles.ts +++ b/src/testRunner/unittests/config/matchFiles.ts @@ -143,7 +143,7 @@ namespace ts { return createFileDiagnostic(file, start, length, diagnosticMessage, arg0); } - describe("matchFiles", () => { + describe("config:: matchFiles", () => { it("with defaults", () => { const json = {}; const expected: ParsedCommandLine = { diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index f67faa4609b..be8e0be6206 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -96,7 +96,7 @@ namespace ts { checkResult(prog, host); } - describe("project-references meta check", () => { + describe("config:: project-references meta check", () => { it("default setup was created correctly", () => { const spec: TestSpecification = { "/primary": { @@ -118,7 +118,7 @@ namespace ts { /** * Validate that we enforce the basic settings constraints for referenced projects */ - describe("project-references constraint checking for settings", () => { + describe("config:: project-references constraint checking for settings", () => { it("errors when declaration = false", () => { const spec: TestSpecification = { "/primary": { @@ -248,7 +248,7 @@ namespace ts { /** * Path mapping behavior */ - describe("project-references path mapping", () => { + describe("config:: project-references path mapping", () => { it("redirects to the output .d.ts file", () => { const spec: TestSpecification = { "/alpha": { @@ -268,7 +268,7 @@ namespace ts { }); }); - describe("project-references nice-behavior", () => { + describe("config:: project-references nice-behavior", () => { it("issues a nice error when the input file is missing", () => { const spec: TestSpecification = { "/alpha": { @@ -289,7 +289,7 @@ namespace ts { /** * 'composite' behavior */ - describe("project-references behavior changes under composite: true", () => { + describe("config:: project-references behavior changes under composite: true", () => { it("doesn't infer the rootDir from source paths", () => { const spec: TestSpecification = { "/alpha": { @@ -308,7 +308,7 @@ namespace ts { }); }); - describe("project-references errors when a file in a composite project occurs outside the root", () => { + describe("config:: project-references errors when a file in a composite project occurs outside the root", () => { it("Errors when a file is outside the rootdir", () => { const spec: TestSpecification = { "/alpha": { diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index 4040e6563a8..f86b37970f5 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -1,5 +1,5 @@ namespace ts { - describe("showConfig", () => { + describe("config:: showConfig", () => { function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) { describe(name, () => { const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`; diff --git a/src/testRunner/unittests/config/tsconfigParsing.ts b/src/testRunner/unittests/config/tsconfigParsing.ts index 909af429d6e..cc617a0d151 100644 --- a/src/testRunner/unittests/config/tsconfigParsing.ts +++ b/src/testRunner/unittests/config/tsconfigParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("tsconfigParsing:: parseConfigFileTextToJson", () => { + describe("config:: tsconfigParsing:: parseConfigFileTextToJson", () => { function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); diff --git a/src/testRunner/unittests/evaluation/asyncArrow.ts b/src/testRunner/unittests/evaluation/asyncArrow.ts index 994fe8a84be..01959ca150f 100644 --- a/src/testRunner/unittests/evaluation/asyncArrow.ts +++ b/src/testRunner/unittests/evaluation/asyncArrow.ts @@ -1,4 +1,4 @@ -describe("asyncArrowEvaluation", () => { +describe("evaluation:: asyncArrowEvaluation", () => { // https://github.com/Microsoft/TypeScript/issues/24722 it("this capture (es5)", async () => { const result = evaluator.evaluateTypeScript(` @@ -15,4 +15,4 @@ describe("asyncArrowEvaluation", () => { await result.main(); assert.instanceOf(result.output[0].a(), result.A); }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/evaluation/asyncGenerator.ts b/src/testRunner/unittests/evaluation/asyncGenerator.ts index 9963ea921fa..cb630d28724 100644 --- a/src/testRunner/unittests/evaluation/asyncGenerator.ts +++ b/src/testRunner/unittests/evaluation/asyncGenerator.ts @@ -1,4 +1,4 @@ -describe("asyncGeneratorEvaluation", () => { +describe("evaluation:: asyncGeneratorEvaluation", () => { it("return (es5)", async () => { const result = evaluator.evaluateTypeScript(` async function * g() { @@ -27,4 +27,4 @@ describe("asyncGeneratorEvaluation", () => { { value: 0, done: true } ]); }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/evaluation/forAwaitOf.ts b/src/testRunner/unittests/evaluation/forAwaitOf.ts index 20ab5eed0cc..7e7ee41e2f1 100644 --- a/src/testRunner/unittests/evaluation/forAwaitOf.ts +++ b/src/testRunner/unittests/evaluation/forAwaitOf.ts @@ -1,4 +1,4 @@ -describe("forAwaitOfEvaluation", () => { +describe("evaluation:: forAwaitOfEvaluation", () => { it("sync (es5)", async () => { const result = evaluator.evaluateTypeScript(` let i = 0; diff --git a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts index 37f829d67a7..1d7e12d9825 100644 --- a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts +++ b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts @@ -1,5 +1,5 @@ namespace ts { - describe("cancellableLanguageServiceOperations", () => { + describe("services:: cancellableLanguageServiceOperations", () => { const file = ` function foo(): void; function foo(x: T): T; diff --git a/src/testRunner/unittests/services/colorization.ts b/src/testRunner/unittests/services/colorization.ts index e3295e6fee8..54ea98dd563 100644 --- a/src/testRunner/unittests/services/colorization.ts +++ b/src/testRunner/unittests/services/colorization.ts @@ -6,7 +6,7 @@ interface ClassificationEntry { position?: number; } -describe("Colorization", () => { +describe("services:: Colorization", () => { // Use the shim adapter to ensure test coverage of the shim layer for the classifier const languageServiceAdapter = new Harness.LanguageService.ShimLanguageServiceAdapter(/*preprocessToResolve*/ false); const classifier = languageServiceAdapter.getClassifier(); diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index 21c6a29f8e4..e8d857f2ee0 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -343,7 +343,7 @@ interface Array {}` } } - describe("convertToAsyncFunctions", () => { + describe("services:: convertToAsyncFunctions", () => { _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); diff --git a/src/testRunner/unittests/services/documentRegistry.ts b/src/testRunner/unittests/services/documentRegistry.ts index a3dad56f42b..96f2aaff76b 100644 --- a/src/testRunner/unittests/services/documentRegistry.ts +++ b/src/testRunner/unittests/services/documentRegistry.ts @@ -1,4 +1,4 @@ -describe("DocumentRegistry", () => { +describe("services:: DocumentRegistry", () => { it("documents are shared between projects", () => { const documentRegistry = ts.createDocumentRegistry(); const defaultCompilerOptions = ts.getDefaultCompilerOptions(); diff --git a/src/testRunner/unittests/services/extract/constants.ts b/src/testRunner/unittests/services/extract/constants.ts index e0ef305812c..1ca5a719a82 100644 --- a/src/testRunner/unittests/services/extract/constants.ts +++ b/src/testRunner/unittests/services/extract/constants.ts @@ -1,5 +1,5 @@ namespace ts { - describe("extractConstants", () => { + describe("services:: extract:: extractConstants", () => { testExtractConstant("extractConstant_TopLevel", `let x = [#|1|];`); diff --git a/src/testRunner/unittests/services/extract/functions.ts b/src/testRunner/unittests/services/extract/functions.ts index 1f90e1cc600..21c5e8e9001 100644 --- a/src/testRunner/unittests/services/extract/functions.ts +++ b/src/testRunner/unittests/services/extract/functions.ts @@ -1,5 +1,5 @@ namespace ts { - describe("extractFunctions", () => { + describe("services:: extract:: extractFunctions", () => { testExtractFunction("extractFunction1", `namespace A { let x = 1; diff --git a/src/testRunner/unittests/services/extract/ranges.ts b/src/testRunner/unittests/services/extract/ranges.ts index 9cd76dd49e9..dc62618e0c6 100644 --- a/src/testRunner/unittests/services/extract/ranges.ts +++ b/src/testRunner/unittests/services/extract/ranges.ts @@ -42,7 +42,7 @@ namespace ts { } } - describe("extractRanges", () => { + describe("services:: extract:: extractRanges", () => { it("get extract range from selection", () => { testExtractRange(` [#| @@ -418,4 +418,4 @@ switch (x) { testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/services/extract/symbolWalker.ts b/src/testRunner/unittests/services/extract/symbolWalker.ts index a027f0f2ce7..9d323a7544a 100644 --- a/src/testRunner/unittests/services/extract/symbolWalker.ts +++ b/src/testRunner/unittests/services/extract/symbolWalker.ts @@ -1,5 +1,5 @@ namespace ts { - describe("Symbol Walker", () => { + describe("services:: extract:: Symbol Walker", () => { function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) { it(description, () => { const result = Harness.Compiler.compileFiles([{ diff --git a/src/testRunner/unittests/services/hostNewLineSupport.ts b/src/testRunner/unittests/services/hostNewLineSupport.ts index abd79210086..2f00c7b08e5 100644 --- a/src/testRunner/unittests/services/hostNewLineSupport.ts +++ b/src/testRunner/unittests/services/hostNewLineSupport.ts @@ -1,5 +1,5 @@ namespace ts { - describe("hostNewLineSupport", () => { + describe("services:: hostNewLineSupport", () => { function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) { function snapFor(path: string): IScriptSnapshot | undefined { if (path === "lib.d.ts") { @@ -46,4 +46,4 @@ namespace ts { `); }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index ce8fa93d1f9..332d59ca68a 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -1,5 +1,5 @@ namespace ts { - describe("languageService", () => { + describe("services:: languageService", () => { const files: {[index: string]: string} = { "foo.ts": `import Vue from "./vue"; import Component from "./vue-class-component"; @@ -43,4 +43,4 @@ export function Component(x: Config): any;` expect(definitions).to.exist; // tslint:disable-line no-unused-expression }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index 355292c6d89..a45c7548394 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -1,5 +1,5 @@ namespace ts { - describe("Organize imports", () => { + describe("services:: Organize imports", () => { describe("Sort imports", () => { it("Sort - non-relative vs non-relative", () => { assertSortsBefore( diff --git a/src/testRunner/unittests/services/patternMatcher.ts b/src/testRunner/unittests/services/patternMatcher.ts index 5e35d2020db..304a9225e0d 100644 --- a/src/testRunner/unittests/services/patternMatcher.ts +++ b/src/testRunner/unittests/services/patternMatcher.ts @@ -1,4 +1,4 @@ -describe("PatternMatcher", () => { +describe("services:: PatternMatcher", () => { describe("BreakIntoCharacterSpans", () => { it("EmptyIdentifier", () => { verifyBreakIntoCharacterSpans(""); diff --git a/src/testRunner/unittests/services/preProcessFile.ts b/src/testRunner/unittests/services/preProcessFile.ts index a89b6337c84..caf412d6403 100644 --- a/src/testRunner/unittests/services/preProcessFile.ts +++ b/src/testRunner/unittests/services/preProcessFile.ts @@ -1,4 +1,4 @@ -describe("PreProcessFile:", () => { +describe("services:: PreProcessFile:", () => { function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void { const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports); diff --git a/src/testRunner/unittests/services/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts index 164073207b3..fa5e723419a 100644 --- a/src/testRunner/unittests/services/textChanges.ts +++ b/src/testRunner/unittests/services/textChanges.ts @@ -2,7 +2,7 @@ // tslint:disable trim-trailing-whitespace namespace ts { - describe("textChanges", () => { + describe("services:: textChanges", () => { function findChild(name: string, n: Node) { return find(n)!; @@ -753,4 +753,4 @@ let x = foo }); } }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/transpile.ts b/src/testRunner/unittests/services/transpile.ts similarity index 97% rename from src/testRunner/unittests/transpile.ts rename to src/testRunner/unittests/services/transpile.ts index b545b76c3db..533f07884f4 100644 --- a/src/testRunner/unittests/transpile.ts +++ b/src/testRunner/unittests/services/transpile.ts @@ -1,5 +1,5 @@ namespace ts { - describe("Transpile", () => { + describe("services:: Transpile", () => { interface TranspileTestSettings { options?: TranspileOptions; diff --git a/src/testRunner/unittests/tscWatch/emit.ts b/src/testRunner/unittests/tscWatch/emit.ts index e0e9abe516c..2f7a70bd2c6 100644 --- a/src/testRunner/unittests/tscWatch/emit.ts +++ b/src/testRunner/unittests/tscWatch/emit.ts @@ -38,7 +38,7 @@ namespace ts.tscWatch { checkOutputDoesNotContain(host, expectedNonAffectedFiles); } - describe("tsc-watch emit with outFile or out setting", () => { + describe("tsc-watch:: emit with outFile or out setting", () => { function createWatchForOut(out?: string, outFile?: string) { const host = createWatchedSystem([]); const config: FileOrFolderEmit = { @@ -161,7 +161,7 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch emit for configured projects", () => { + describe("tsc-watch:: emit for configured projects", () => { const file1Consumer1Path = "/a/b/file1Consumer1.ts"; const moduleFile1Path = "/a/b/moduleFile1.ts"; const configFilePath = "/a/b/tsconfig.json"; @@ -495,7 +495,7 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch emit file content", () => { + describe("tsc-watch:: emit file content", () => { interface EmittedFile extends File { shouldBeWritten: boolean; } @@ -676,7 +676,7 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch with when module emit is specified as node", () => { + describe("tsc-watch:: emit with when module emit is specified as node", () => { it("when instead of filechanged recursive directory watcher is invoked", () => { const configFile: File = { path: "/a/rootFolder/project/tsconfig.json", diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index f51ecc4e64d..46dc94e20e8 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -12,6 +12,15 @@ namespace ts.tscWatch { export import checkOutputContains = TestFSWithWatch.checkOutputContains; export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; + export const commonFile1: File = { + path: "/a/b/commonFile1.ts", + content: "let x = 1" + }; + export const commonFile2: File = { + path: "/a/b/commonFile2.ts", + content: "let y = 1" + }; + export function checkProgramActualFiles(program: Program, expectedFiles: ReadonlyArray) { checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); } diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts index 759c80e93a6..e7b546edcd9 100644 --- a/src/testRunner/unittests/tscWatch/resolutionCache.ts +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -1,5 +1,5 @@ namespace ts.tscWatch { - describe("resolutionCache:: tsc-watch module resolution caching", () => { + describe("tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { it("works", () => { const root = { path: "/a/d/f0.ts", @@ -404,7 +404,7 @@ declare module "fs" { }); }); - describe("resolutionCache:: tsc-watch with modules linked to sibling folder", () => { + describe("tsc-watch:: resolutionCache:: tsc-watch with modules linked to sibling folder", () => { const projectRoot = "/user/username/projects/project"; const mainPackageRoot = `${projectRoot}/main`; const linkedPackageRoot = `${projectRoot}/linked-package`; diff --git a/src/testRunner/unittests/tscWatch/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts index 60072e24730..334b353f1d5 100644 --- a/src/testRunner/unittests/tscWatch/watchApi.ts +++ b/src/testRunner/unittests/tscWatch/watchApi.ts @@ -1,5 +1,5 @@ namespace ts.tscWatch { - describe("watchAPI:: tsc-watch with custom module resolution", () => { + describe("tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { const projectRoot = "/user/username/projects/project"; const configFileJson: any = { compilerOptions: { module: "commonjs", resolveJsonModule: true }, diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index 646fb59ef09..3d09e2a38f6 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -1,6 +1,6 @@ namespace ts.tscWatch { import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - describe("watchEnvironment:: tsc-watch with different polling/non polling options", () => { + describe("tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { it("watchFile using dynamic priority polling", () => { const projectFolder = "/a/username/project"; const file1: File = { diff --git a/src/testRunner/unittests/tscWatchMode.ts b/src/testRunner/unittests/tscWatchMode.ts index 29726b12424..4d71599a1b5 100644 --- a/src/testRunner/unittests/tscWatchMode.ts +++ b/src/testRunner/unittests/tscWatchMode.ts @@ -25,15 +25,6 @@ namespace ts.tscWatch { } describe("tsc-watch program updates", () => { - const commonFile1: File = { - path: "/a/b/commonFile1.ts", - content: "let x = 1" - }; - const commonFile2: File = { - path: "/a/b/commonFile2.ts", - content: "let y = 1" - }; - it("create watch without config file", () => { const appFile: File = { path: "/a/b/c/app.ts", diff --git a/src/testRunner/unittests/tsserver/cancellationToken.ts b/src/testRunner/unittests/tsserver/cancellationToken.ts new file mode 100644 index 00000000000..5d9cd5527e6 --- /dev/null +++ b/src/testRunner/unittests/tsserver/cancellationToken.ts @@ -0,0 +1,271 @@ +namespace ts.projectSystem { + describe("tsserver:: cancellationToken", () => { + // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test + let oldPrepare: AnyFunction; + before(() => { + oldPrepare = (Error as any).prepareStackTrace; + delete (Error as any).prepareStackTrace; + }); + + after(() => { + (Error as any).prepareStackTrace = oldPrepare; + }); + + it("is attached to request", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let xyz = 1;" + }; + const host = createServerHost([f1]); + let expectedRequestId: number; + const cancellationToken: server.ServerCancellationToken = { + isCancellationRequested: () => false, + setRequest: requestId => { + if (expectedRequestId === undefined) { + assert.isTrue(false, "unexpected call"); + } + assert.equal(requestId, expectedRequestId); + }, + resetRequest: noop + }; + + const session = createSession(host, { cancellationToken }); + + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + }); + + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + }); + + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "occurrences", + arguments: { file: f1.path, line: 1, offset: 6 } + }); + + expectedRequestId = 2; + host.runQueuedImmediateCallbacks(); + expectedRequestId = 2; + host.runQueuedImmediateCallbacks(); + }); + + it("Geterr is cancellable", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {} + }) + }; + + const cancellationToken = new TestServerCancellationToken(); + const host = createServerHost([f1, config]); + const session = createSession(host, { + canUseEvents: true, + eventHandler: noop, + cancellationToken + }); + { + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + }); + // send geterr for missing file + session.executeCommandSeq({ + command: "geterr", + arguments: { files: ["/a/missing"] } + }); + // no files - expect 'completed' event + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(session.getSeq(), 0); + } + { + const getErrId = session.getNextSeq(); + // send geterr for a valid file + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + }); + + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + + // run new request + session.executeCommandSeq({ + command: "projectInfo", + arguments: { file: f1.path } + }); + session.clearMessages(); + + // cancel previously issued Geterr + cancellationToken.setRequestToCancel(getErrId); + host.runQueuedTimeoutCallbacks(); + + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(getErrId, 0); + + cancellationToken.resetToken(); + } + { + const getErrId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + }); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0); + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); + + cancellationToken.setRequestToCancel(getErrId); + host.runQueuedImmediateCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(getErrId, 0); + + cancellationToken.resetToken(); + } + { + const getErrId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + }); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0); + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); + + // the semanticDiag message + host.runQueuedImmediateCallbacks(); + assert.equal(host.getOutput().length, 1); + const e2 = getMessage(0); + assert.equal(e2.event, "semanticDiag"); + session.clearMessages(); + + host.runQueuedImmediateCallbacks(1); + assert.equal(host.getOutput().length, 2); + const e3 = getMessage(0); + assert.equal(e3.event, "suggestionDiag"); + verifyRequestCompleted(getErrId, 1); + + cancellationToken.resetToken(); + } + { + const getErr1 = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + }); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0); + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); + + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + }); + // make sure that getErr1 is completed + verifyRequestCompleted(getErr1, 0); + } + + function verifyRequestCompleted(expectedSeq: number, n: number) { + const event = getMessage(n); + assert.equal(event.event, "requestCompleted"); + assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); + session.clearMessages(); + } + + function getMessage(n: number) { + return JSON.parse(server.extractMessage(host.getOutput()[n])); + } + }); + + it("Lower priority tasks are cancellable", () => { + const f1 = { + path: "/a/app.ts", + content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {} + }) + }; + const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3); + const host = createServerHost([f1, config]); + const session = createSession(host, { + canUseEvents: true, + eventHandler: noop, + cancellationToken, + throttleWaitMilliseconds: 0 + }); + { + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + }); + + // send navbar request (normal priority) + session.executeCommandSeq({ + command: "navbar", + arguments: { file: f1.path } + }); + + // ensure the nav bar request can be canceled + verifyExecuteCommandSeqIsCancellable({ + command: "navbar", + arguments: { file: f1.path } + }); + + // send outlining spans request (normal priority) + session.executeCommandSeq({ + command: "outliningSpans", + arguments: { file: f1.path } + }); + + // ensure the outlining spans request can be canceled + verifyExecuteCommandSeqIsCancellable({ + command: "outliningSpans", + arguments: { file: f1.path } + }); + } + + function verifyExecuteCommandSeqIsCancellable(request: Partial) { + // Set the next request to be cancellable + // The cancellation token will cancel the request the third time + // isCancellationRequested() is called. + cancellationToken.setRequestToCancel(session.getNextSeq()); + let operationCanceledExceptionThrown = false; + + try { + session.executeCommandSeq(request); + } + catch (e) { + assert(e instanceof OperationCanceledException); + operationCanceledExceptionThrown = true; + } + assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); + } + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts new file mode 100644 index 00000000000..ed020bae0ba --- /dev/null +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -0,0 +1,122 @@ +namespace ts.projectSystem { + describe("tsserver:: completions", () => { + it("works", () => { + const aTs: File = { + path: "/a.ts", + content: "export const foo = 0;", + }; + const bTs: File = { + path: "/b.ts", + content: "foo", + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; + + const session = createSession(createServerHost([aTs, bTs, tsconfig])); + openFilesForSession([aTs, bTs], session); + + const requestLocation: protocol.FileLocationRequestArgs = { + file: bTs.path, + line: 1, + offset: 3, + }; + + const response = executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + includeExternalModuleExports: true, + prefix: "foo", + }); + const entry: protocol.CompletionEntry = { + hasAction: true, + insertText: undefined, + isRecommended: undefined, + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + name: "foo", + replacementSpan: undefined, + sortText: "0", + source: "/a", + }; + assert.deepEqual(response, { + isGlobalCompletion: true, + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: [entry], + }); + + const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { + ...requestLocation, + entryNames: [{ name: "foo", source: "/a" }], + }; + + const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); + const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = { + displayParts: [ + keywordPart(SyntaxKind.ConstKeyword), + spacePart(), + displayPart("foo", SymbolDisplayPartKind.localName), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + displayPart("0", SymbolDisplayPartKind.stringLiteral), + ], + documentation: emptyArray, + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + name: "foo", + source: [{ text: "./a", kind: "text" }], + tags: undefined, + }; + assert.deepEqual | undefined>(detailsResponse, [ + { + codeActions: [ + { + description: `Import 'foo' from module "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "./a";\n\n', + }, + ], + }, + ], + commands: undefined, + }, + ], + ...detailsCommon, + }, + ]); + + interface CompletionDetailsFullRequest extends protocol.FileLocationRequest { + readonly command: protocol.CommandTypes.CompletionDetailsFull; + readonly arguments: protocol.CompletionDetailsRequestArgs; + } + interface CompletionDetailsFullResponse extends protocol.Response { + readonly body?: ReadonlyArray; + } + const detailsFullResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); + assert.deepEqual | undefined>(detailsFullResponse, [ + { + codeActions: [ + { + description: `Import 'foo' from module "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], + }, + ], + commands: undefined, + } + ], + ...detailsCommon, + } + ]); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/configFileSearch.ts b/src/testRunner/unittests/tsserver/configFileSearch.ts new file mode 100644 index 00000000000..4d0b0c0efdd --- /dev/null +++ b/src/testRunner/unittests/tsserver/configFileSearch.ts @@ -0,0 +1,174 @@ +namespace ts.projectSystem { + describe("tsserver:: searching for config file", () => { + it("should stop at projectRootPath if given", () => { + const f1 = { + path: "/a/file1.ts", + content: "" + }; + const configFile = { + path: "/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, configFile]); + const service = createProjectService(host); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); + + checkNumberOfConfiguredProjects(service, 0); + checkNumberOfInferredProjects(service, 1); + + service.closeClientFile(f1.path); + service.openClientFile(f1.path); + checkNumberOfConfiguredProjects(service, 1); + checkNumberOfInferredProjects(service, 0); + }); + + it("should use projectRootPath when searching for inferred project again", () => { + const projectDir = "/a/b/projects/project"; + const configFileLocation = `${projectDir}/src`; + const f1 = { + path: `${configFileLocation}/file1.ts`, + content: "" + }; + const configFile = { + path: `${configFileLocation}/tsconfig.json`, + content: "{}" + }; + const configFile2 = { + path: "/a/b/projects/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, libFile, configFile, configFile2]); + const service = createProjectService(host); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + checkNumberOfProjects(service, { configuredProjects: 1 }); + assert.isDefined(service.configuredProjects.get(configFile.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + const typeRootLocations = getTypeRootsFromLocation(configFileLocation); + checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true); + + // Delete config file - should create inferred project and not configured project + host.reloadFS([f1, libFile, configFile2]); + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true); + }); + + it("should use projectRootPath when searching for inferred project again 2", () => { + const projectDir = "/a/b/projects/project"; + const configFileLocation = `${projectDir}/src`; + const f1 = { + path: `${configFileLocation}/file1.ts`, + content: "" + }; + const configFile = { + path: `${configFileLocation}/tsconfig.json`, + content: "{}" + }; + const configFile2 = { + path: "/a/b/projects/tsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, libFile, configFile, configFile2]); + const service = createProjectService(host, { useSingleInferredProject: true }, { useInferredProjectPerProjectRoot: true }); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + checkNumberOfProjects(service, { configuredProjects: 1 }); + assert.isDefined(service.configuredProjects.get(configFile.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true); + + // Delete config file - should create inferred project with project root path set + host.reloadFS([f1, libFile, configFile2]); + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(service, { inferredProjects: 1 }); + assert.equal(service.inferredProjects[0].projectRootPath, projectDir); + checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true); + }); + + describe("when the opened file is not from project root", () => { + const projectRoot = "/a/b/projects/project"; + const file: File = { + path: `${projectRoot}/src/index.ts`, + content: "let y = 10" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + const files = [file, libFile]; + const filesWithConfig = files.concat(tsconfig); + const dirOfFile = getDirectoryPath(file.path); + + function openClientFile(files: File[]) { + const host = createServerHost(files); + const projectService = createProjectService(host); + + projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); + return { host, projectService }; + } + + function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) { + projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 }); + const project = Debug.assertDefined(projectService.configuredProjects.get(tsconfig.path)); + + if (orphanInferredProject) { + const inferredProject = projectService.inferredProjects[0]; + assert.isTrue(inferredProject.isOrphan()); + } + + checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]); + checkWatchedFiles(host, [libFile.path, tsconfig.path]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true); + } + + function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) { + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + assert.isDefined(project); + + const filesToWatch = [libFile.path]; + forEachAncestorDirectory(dirOfFile, ancestor => { + filesToWatch.push(combinePaths(ancestor, "tsconfig.json")); + filesToWatch.push(combinePaths(ancestor, "jsconfig.json")); + }); + + checkProjectActualFiles(project, [file.path, libFile.path]); + checkWatchedFiles(host, filesToWatch); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true); + } + + it("tsconfig for the file exists", () => { + const { host, projectService } = openClientFile(filesWithConfig); + verifyConfiguredProject(host, projectService); + + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + verifyInferredProject(host, projectService); + + host.reloadFS(filesWithConfig); + host.runQueuedTimeoutCallbacks(); + verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); + }); + + it("tsconfig for the file does not exist", () => { + const { host, projectService } = openClientFile(files); + verifyInferredProject(host, projectService); + + host.reloadFS(filesWithConfig); + host.runQueuedTimeoutCallbacks(); + verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); + + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + verifyInferredProject(host, projectService); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/declarationFileMaps.ts b/src/testRunner/unittests/tsserver/declarationFileMaps.ts new file mode 100644 index 00000000000..334af82ca48 --- /dev/null +++ b/src/testRunner/unittests/tsserver/declarationFileMaps.ts @@ -0,0 +1,566 @@ +namespace ts.projectSystem { + function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan { + return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) }; + } + + function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan { + return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) }; + } + + function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { + return documentSpanFromSubstring(file, substring, options); + } + + function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { + return { + ...protocolFileSpanFromSubstring(file, text, options), + isDefinition, + isWriteAccess: isDefinition, + lineText, + }; + } + + function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry { + return { + ...documentSpanFromSubstring(file, text, options), + isDefinition, + isWriteAccess: isDefinition, + isInString: undefined, + }; + } + + function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray): void { + openFilesForSession([file], session); + const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); + const program = project.getCurrentProgram()!; + const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); + closeFilesForSession([file], session); + + Debug.assert(!output.emitSkipped); + assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); + } + + describe("tsserver:: with declaration file maps:: project references", () => { + const aTs: File = { + path: "/a/a.ts", + content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", + }; + const compilerOptions: CompilerOptions = { + outDir: "bin", + declaration: true, + declarationMap: true, + composite: true, + }; + const configContent = JSON.stringify({ compilerOptions }); + const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; + + const aDtsMapContent: RawSourceMap = { + version: 3, + file: "a.d.ts", + sourceRoot: "", + sources: ["../a.ts"], + names: [], + mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" + }; + const aDtsMap: File = { + path: "/a/bin/a.d.ts.map", + content: JSON.stringify(aDtsMapContent), + }; + const aDts: File = { + path: "/a/bin/a.d.ts", + // Need to mangle the sourceMappingURL part or it breaks the build + content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, + }; + + const bTs: File = { + path: "/b/b.ts", + content: "export function fnB() {}", + }; + const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; + + const bDtsMapContent: RawSourceMap = { + version: 3, + file: "b.d.ts", + sourceRoot: "", + sources: ["../b.ts"], + names: [], + mappings: "AAAA,wBAAgB,GAAG,SAAK", + }; + const bDtsMap: File = { + path: "/b/bin/b.d.ts.map", + content: JSON.stringify(bDtsMapContent), + }; + const bDts: File = { + // Need to mangle the sourceMappingURL part or it breaks the build + path: "/b/bin/b.d.ts", + content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, + }; + + const dummyFile: File = { + path: "/dummy/dummy.ts", + content: "let a = 10;" + }; + + const userTs: File = { + path: "/user/user.ts", + content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', + }; + + const userTsForConfigProject: File = { + path: "/user/user.ts", + content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', + }; + + const userTsconfig: File = { + path: "/user/tsconfig.json", + content: JSON.stringify({ + file: ["user.ts"], + references: [{ path: "../a" }, { path: "../b" }] + }) + }; + + function makeSampleProjects(addUserTsConfig?: boolean) { + const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); + const session = createSession(host); + + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); + + // Testing what happens if we delete the original sources. + host.deleteFile(bTs.path); + + openFilesForSession([userTs], session); + const service = session.getProjectService(); + checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 1 } : { inferredProjects: 1 }); + return session; + } + + function verifyInferredProjectUnchanged(session: TestSession) { + checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); + } + + function verifyDummyProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); + } + + function verifyOnlyOrphanInferredProject(session: TestSession) { + openFilesForSession([dummyFile], session); + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); + verifyDummyProject(session); + } + + function verifySingleInferredProject(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); + verifyInferredProjectUnchanged(session); + + // Close user file should close all the projects after opening dummy file + closeFilesForSession([userTs], session); + verifyOnlyOrphanInferredProject(session); + } + + function verifyATsConfigProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); + } + + function verifyATsConfigOriginalProject(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyInferredProjectUnchanged(session); + verifyATsConfigProject(session); + // Close user file should close all the projects + closeFilesForSession([userTs], session); + verifyOnlyOrphanInferredProject(session); + } + + function verifyATsConfigWhenOpened(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyInferredProjectUnchanged(session); + verifyATsConfigProject(session); + + closeFilesForSession([userTs], session); + openFilesForSession([dummyFile], session); + checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyDummyProject(session); + verifyATsConfigProject(session); // ATsConfig should still be alive + } + + function verifyUserTsConfigProject(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]); + } + + it("goToDefinition", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]); + verifySingleInferredProject(session); + }); + + it("getDefinitionAndBoundSpan", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [protocolFileSpanFromSubstring(aTs, "fnA")], + }); + verifySingleInferredProject(session); + }); + + it("getDefinitionAndBoundSpan with file navigation", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true); + const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [protocolFileSpanFromSubstring(aTs, "fnA")], + }); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + verifyUserTsConfigProject(session); + + // Navigate to the definition + closeFilesForSession([userTs], session); + openFilesForSession([aTs], session); + + // UserTs configured project should be alive + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + verifyUserTsConfigProject(session); + verifyATsConfigProject(session); + + closeFilesForSession([aTs], session); + verifyOnlyOrphanInferredProject(session); + }); + + it("goToType", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA")); + assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "IfaceA")]); + verifySingleInferredProject(session); + }); + + it("goToImplementation", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]); + verifySingleInferredProject(session); + }); + + it("goToDefinition -- target does not exist", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()")); + // bTs does not exist, so stick with bDts + assert.deepEqual(response, [protocolFileSpanFromSubstring(bDts, "fnB")]); + verifySingleInferredProject(session); + }); + + it("navigateTo", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); + assert.deepEqual | undefined>(response, [ + { + ...protocolFileSpanFromSubstring(bDts, "export declare function fnB(): void;"), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export,declare", + }, + { + ...protocolFileSpanFromSubstring(userTs, "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"), + name: "fnUser", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + }, + { + ...protocolFileSpanFromSubstring(aTs, "export function fnA() {}"), + name: "fnA", + matchKind: "prefix", + isCaseSensitive: true, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + }, + ]); + + verifyATsConfigOriginalProject(session); + }); + + const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem(aTs, /*isDefinition*/ true, "fnA", "export function fnA() {}"); + const referencesUserTs = (userTs: File): ReadonlyArray => [ + makeReferenceItem(userTs, /*isDefinition*/ false, "fnA", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"), + ]; + + it("findAllReferences", () => { + const session = makeSampleProjects(); + + const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + refs: [...referencesUserTs(userTs), referenceATs(aTs)], + symbolName: "fnA", + symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset, + symbolDisplayString: "function fnA(): void", + }); + + verifyATsConfigOriginalProject(session); + }); + + it("findAllReferences -- starting at definition", () => { + const session = makeSampleProjects(); + openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. + const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA")); + assert.deepEqual(response, { + refs: [referenceATs(aTs), ...referencesUserTs(userTs)], + symbolName: "fnA", + symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset, + symbolDisplayString: "function fnA(): void", + }); + verifyATsConfigWhenOpened(session); + }); + + interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } + interface ReferencesFullResponse extends protocol.Response { readonly body: ReadonlyArray; } + + it("findAllReferencesFull", () => { + const session = makeSampleProjects(); + + const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); + + assert.deepEqual>(responseFull, [ + { + definition: { + ...documentSpanFromSubstring(aTs, "fnA"), + kind: ScriptElementKind.functionElement, + name: "function fnA(): void", + containerKind: ScriptElementKind.unknown, + containerName: "", + displayParts: [ + keywordPart(SyntaxKind.FunctionKeyword), + spacePart(), + displayPart("fnA", SymbolDisplayPartKind.functionName), + punctuationPart(SyntaxKind.OpenParenToken), + punctuationPart(SyntaxKind.CloseParenToken), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + keywordPart(SyntaxKind.VoidKeyword), + ], + }, + references: [ + makeReferenceEntry(userTs, /*isDefinition*/ false, "fnA"), + makeReferenceEntry(aTs, /*isDefinition*/ true, "fnA"), + ], + }, + ]); + verifyATsConfigOriginalProject(session); + }); + + it("findAllReferencesFull definition is in mapped file", () => { + const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), + }; + const bTs: File = { path: "/b/b.ts", content: `f();` }; + const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; + const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; + const aDtsMap: File = { + path: "/bin/a.d.ts.map", + content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), + }; + + const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + openFilesForSession([bTs], session); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + + const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); + + assert.deepEqual>(responseFull, [ + { + definition: { + containerKind: ScriptElementKind.unknown, + containerName: "", + displayParts: [ + keywordPart(SyntaxKind.FunctionKeyword), + spacePart(), + displayPart("f", SymbolDisplayPartKind.functionName), + punctuationPart(SyntaxKind.OpenParenToken), + punctuationPart(SyntaxKind.CloseParenToken), + punctuationPart(SyntaxKind.ColonToken), + spacePart(), + keywordPart(SyntaxKind.VoidKeyword), + ], + fileName: aTs.path, + kind: ScriptElementKind.functionElement, + name: "function f(): void", + textSpan: { start: 9, length: 1 }, + }, + references: [ + { + fileName: bTs.path, + isDefinition: false, + isInString: undefined, + isWriteAccess: false, + textSpan: { start: 0, length: 1 }, + }, + { + fileName: aTs.path, + isDefinition: true, + isInString: undefined, + isWriteAccess: true, + textSpan: { start: 9, length: 1 }, + }, + ], + } + ]); + }); + + it("findAllReferences -- target does not exist", () => { + const session = makeSampleProjects(); + + const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()")); + assert.deepEqual(response, { + refs: [ + makeReferenceItem(bDts, /*isDefinition*/ true, "fnB", "export declare function fnB(): void;"), + makeReferenceItem(userTs, /*isDefinition*/ false, "fnB", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"), + ], + symbolName: "fnB", + symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset, + symbolDisplayString: "function fnB(): void", + }); + verifySingleInferredProject(session); + }); + + const renameATs = (aTs: File): protocol.SpanGroup => ({ + file: aTs.path, + locs: [protocolRenameSpanFromSubstring(aTs.content, "fnA")], + }); + const renameUserTs = (userTs: File): protocol.SpanGroup => ({ + file: userTs.path, + locs: [protocolRenameSpanFromSubstring(userTs.content, "fnA")], + }); + + it("renameLocations", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnA", + fileToRename: undefined, + fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. + kind: ScriptElementKind.functionElement, + kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), + triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), + }, + locs: [renameUserTs(userTs), renameATs(aTs)], + }); + verifyATsConfigOriginalProject(session); + }); + + it("renameLocations -- starting at definition", () => { + const session = makeSampleProjects(); + openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnA", + fileToRename: undefined, + fullDisplayName: '"/a/a".fnA', + kind: ScriptElementKind.functionElement, + kindModifiers: ScriptElementKindModifier.exportedModifier, + triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"), + }, + locs: [renameATs(aTs), renameUserTs(userTs)], + }); + verifyATsConfigWhenOpened(session); + }); + + it("renameLocationsFull", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual>(response, [ + renameLocation(userTs, "fnA"), + renameLocation(aTs, "fnA"), + ]); + verifyATsConfigOriginalProject(session); + }); + + it("renameLocations -- target does not exist", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnB", + fileToRename: undefined, + fullDisplayName: '"/b/bin/b".fnB', + kind: ScriptElementKind.functionElement, + kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), + triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), + }, + locs: [ + { + file: bDts.path, + locs: [protocolRenameSpanFromSubstring(bDts.content, "fnB")], + }, + { + file: userTs.path, + locs: [protocolRenameSpanFromSubstring(userTs.content, "fnB")], + }, + ], + }); + verifySingleInferredProject(session); + }); + + it("getEditsForFileRename", () => { + const session = makeSampleProjects(); + const response = executeSessionRequest(session, protocol.CommandTypes.GetEditsForFileRename, { + oldFilePath: aTs.path, + newFilePath: "/a/aNew.ts", + }); + assert.deepEqual>(response, [ + { + fileName: userTs.path, + textChanges: [ + { ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, + ], + }, + ]); + verifySingleInferredProject(session); + }); + + it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { + const aTs: File = { path: "/a/src/a.ts", content: "" }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: true, + outDir: "./build", + } + }), + }; + const bTs: File = { path: "/b/src/b.ts", content: "" }; + const bTsconfig: File = { + path: "/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./build", + }, + include: ["./src"], + references: [{ path: "../a" }], + }), + }; + + const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); + const session = createSession(host); + openFilesForSession([aTs, bTs], session); + const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { + oldFilePath: aTs.path, + newFilePath: "/a/src/a1.ts", + }); + assert.deepEqual>(response, []); // Should not change anything + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts new file mode 100644 index 00000000000..0fd812a89d2 --- /dev/null +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -0,0 +1,94 @@ +namespace ts.projectSystem { + describe("tsserver:: document registry in project service", () => { + const projectRootPath = "/user/username/projects/project"; + const importModuleContent = `import {a} from "./module1"`; + const file: File = { + path: `${projectRootPath}/index.ts`, + content: importModuleContent + }; + const moduleFile: File = { + path: `${projectRootPath}/module1.d.ts`, + content: "export const a: number;" + }; + const configFile: File = { + path: `${projectRootPath}/tsconfig.json`, + content: JSON.stringify({ files: ["index.ts"] }) + }; + + function getProject(service: TestProjectService) { + return service.configuredProjects.get(configFile.path)!; + } + + function checkProject(service: TestProjectService, moduleIsOrphan: boolean) { + // Update the project + const project = getProject(service); + project.getLanguageService(); + checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + assert.isDefined(moduleInfo); + assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); + const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings()); + assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path), [[key, moduleIsOrphan ? undefined : 1]]); + } + + function createServiceAndHost() { + const host = createServerHost([file, moduleFile, libFile, configFile]); + const service = createProjectService(host); + service.openClientFile(file.path); + checkProject(service, /*moduleIsOrphan*/ false); + return { host, service }; + } + + function changeFileToNotImportModule(service: TestProjectService) { + const info = service.getScriptInfo(file.path)!; + service.applyChangesToFile(info, [{ span: { start: 0, length: importModuleContent.length }, newText: "" }]); + checkProject(service, /*moduleIsOrphan*/ true); + } + + function changeFileToImportModule(service: TestProjectService) { + const info = service.getScriptInfo(file.path)!; + service.applyChangesToFile(info, [{ span: { start: 0, length: 0 }, newText: importModuleContent }]); + checkProject(service, /*moduleIsOrphan*/ false); + } + + it("Caches the source file if script info is orphan", () => { + const { service } = createServiceAndHost(); + const project = getProject(service); + + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + + // edit file + changeFileToNotImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + + // write content back + changeFileToImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + }); + + it("Caches the source file if script info is orphan, and orphan script info changes", () => { + const { host, service } = createServiceAndHost(); + const project = getProject(service); + + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + + // edit file + changeFileToNotImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + + const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; + host.writeFile(moduleFile.path, updatedModuleContent); + + // write content back + changeFileToImportModule(service); + assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/duplicatePackages.ts b/src/testRunner/unittests/tsserver/duplicatePackages.ts new file mode 100644 index 00000000000..f8a282ee7d2 --- /dev/null +++ b/src/testRunner/unittests/tsserver/duplicatePackages.ts @@ -0,0 +1,54 @@ +namespace ts.projectSystem { + describe("tsserver:: duplicate packages", () => { + // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. + it("works with import fixes", () => { + const packageContent = "export const foo: number;"; + const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" }); + const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent }; + const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent }; + const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent }; + const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent }; + + const userContent = 'import("foo");\nfoo'; + const aUser: File = { path: "/a/user.ts", content: userContent }; + const bUser: File = { path: "/b/user.ts", content: userContent }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; + + const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); + const session = createSession(host); + + openFilesForSession([aUser, bUser], session); + + for (const user of [aUser, bUser]) { + const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { + file: user.path, + startLine: 2, + startOffset: 1, + endLine: 2, + endOffset: 4, + errorCodes: [Diagnostics.Cannot_find_name_0.code], + }); + assert.deepEqual | undefined>(response, [ + { + description: `Import 'foo' from module "foo"`, + fixName: "import", + fixId: "fixMissingImport", + fixAllDescription: "Add all missing imports", + changes: [{ + fileName: user.path, + textChanges: [{ + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "foo";\n\n', + }], + }], + commands: undefined, + }, + ]); + } + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts new file mode 100644 index 00000000000..d790a1aa25a --- /dev/null +++ b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts @@ -0,0 +1,45 @@ +namespace ts.projectSystem { + describe("tsserver:: forceConsistentCasingInFileNames", () => { + it("works when extends is specified with a case insensitive file system", () => { + const rootPath = "/Users/username/dev/project"; + const file1: File = { + path: `${rootPath}/index.ts`, + content: 'import {x} from "file2";', + }; + const file2: File = { + path: `${rootPath}/file2.js`, + content: "", + }; + const file2Dts: File = { + path: `${rootPath}/types/file2/index.d.ts`, + content: "export declare const x: string;", + }; + const tsconfigAll: File = { + path: `${rootPath}/tsconfig.all.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: ".", + paths: { file2: ["./file2.js"] }, + typeRoots: ["./types"], + forceConsistentCasingInFileNames: true, + }, + }), + }; + const tsconfig: File = { + path: `${rootPath}/tsconfig.json`, + content: JSON.stringify({ extends: "./tsconfig.all.json" }), + }; + + const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); + const session = createSession(host); + + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/formatSettings.ts b/src/testRunner/unittests/tsserver/formatSettings.ts new file mode 100644 index 00000000000..4e3e2ebd418 --- /dev/null +++ b/src/testRunner/unittests/tsserver/formatSettings.ts @@ -0,0 +1,39 @@ +namespace ts.projectSystem { + describe("tsserver:: format settings", () => { + it("can be set globally", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x;" + }; + const host = createServerHost([f1]); + const projectService = createProjectService(host); + projectService.openClientFile(f1.path); + + const defaultSettings = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); + + // set global settings + const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks }; + projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 }); + + // get format options for file - should be equal to new global settings + const s1 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); + assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings"); + + // set per file format options + const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter }; + projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path }); + + // get format options for file - should be equal to new per-file settings + const s2 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); + assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings"); + + // set new global settings - they should not affect ones that were set per-file + const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements }; + projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 }); + + // get format options for file - should be equal to new per-file settings + const s3 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); + assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings"); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts new file mode 100644 index 00000000000..3fadf99c3f8 --- /dev/null +++ b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts @@ -0,0 +1,105 @@ +namespace ts.projectSystem { + describe("tsserver:: getEditsForFileRename", () => { + it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { + const userTs: File = { + path: "/user.ts", + content: 'import { x } from "./old";', + }; + const newTs: File = { + path: "/new.ts", + content: "export const x = 0;", + }; + const tsconfig: File = { + path: "/tsconfig.json", + content: "{}", + }; + + const host = createServerHost([userTs, newTs, tsconfig]); + const projectService = createProjectService(host); + projectService.openClientFile(userTs.path); + const project = projectService.configuredProjects.get(tsconfig.path)!; + + Debug.assert(!!project.resolveModuleNames); + + const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions); + assert.deepEqual>(edits, [{ + fileName: "/user.ts", + textChanges: [{ + span: textSpanFromSubstring(userTs.content, "./old"), + newText: "./new", + }], + }]); + }); + + it("works with multiple projects", () => { + const aUserTs: File = { + path: "/a/user.ts", + content: 'import { x } from "./old";', + }; + const aOldTs: File = { + path: "/a/old.ts", + content: "export const x = 0;", + }; + const aTsconfig: File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }), + }; + const bUserTs: File = { + path: "/b/user.ts", + content: 'import { x } from "../a/old";', + }; + const bTsconfig: File = { + path: "/b/tsconfig.json", + content: "{}", + }; + + const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); + const session = createSession(host); + openFilesForSession([aUserTs, bUserTs], session); + + const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { + oldFilePath: aOldTs.path, + newFilePath: "/a/new.ts", + }); + assert.deepEqual>(response, [ + { + fileName: aTsconfig.path, + textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }], + }, + { + fileName: aUserTs.path, + textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }], + }, + { + fileName: bUserTs.path, + textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }], + }, + ]); + }); + + it("works with file moved to inferred project", () => { + const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' }; + const cTs: File = { path: "/c.ts", content: "export {};" }; + const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) }; + + const host = createServerHost([aTs, cTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs, cTs], session); + + const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { + oldFilePath: "/b.ts", + newFilePath: cTs.path, + }); + assert.deepEqual>(response, [ + { + fileName: "/tsconfig.json", + textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }], + }, + { + fileName: "/a.ts", + textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }], + }, + ]); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 86dfddf3bb3..1db6a9b36c8 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -15,6 +15,9 @@ namespace ts.projectSystem { export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories; export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed; + export import commonFile1 = tscWatch.commonFile1; + export import commonFile2 = tscWatch.commonFile2; + const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; export function mapOutputToJson(s: string) { return convertToObject( @@ -459,9 +462,13 @@ namespace ts.projectSystem { return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); } - //function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { - // checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); - //} + export function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { + checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); + } + + export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray) { + checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); + } export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location { const start = str.indexOf(substring); @@ -497,18 +504,10 @@ namespace ts.projectSystem { Debug.assert(start !== -1); return createTextSpan(start, substring.length); } - //function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { - // return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; - //} - //function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan { - // return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) }; - //} - //function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan { - // return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) }; - //} - //function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { - // return documentSpanFromSubstring(file, substring, options); - //} + + export function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { + return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; + } export interface SpanFromSubstringOptions { readonly index: number; @@ -648,33 +647,4 @@ namespace ts.projectSystem { assert.strictEqual(outputs.length, index + 1, JSON.stringify(outputs)); } } - - //function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { - // return { - // ...protocolFileSpanFromSubstring(file, text, options), - // isDefinition, - // isWriteAccess: isDefinition, - // lineText, - // }; - //} - - //function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry { - // return { - // ...documentSpanFromSubstring(file, text, options), - // isDefinition, - // isWriteAccess: isDefinition, - // isInString: undefined, - // }; - //} - - //function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray): void { - // openFilesForSession([file], session); - // const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); - // const program = project.getCurrentProgram()!; - // const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); - // closeFilesForSession([file], session); - - // Debug.assert(!output.emitSkipped); - // assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); - //} } diff --git a/src/testRunner/unittests/tsserver/importHelpers.ts b/src/testRunner/unittests/tsserver/importHelpers.ts new file mode 100644 index 00000000000..42e38aed122 --- /dev/null +++ b/src/testRunner/unittests/tsserver/importHelpers.ts @@ -0,0 +1,18 @@ +namespace ts.projectSystem { + describe("tsserver:: import helpers", () => { + it("should not crash in tsserver", () => { + const f1 = { + path: "/a/app.ts", + content: "export async function foo() { return 100; }" + }; + const tslib = { + path: "/a/node_modules/tslib/index.d.ts", + content: "" + }; + const host = createServerHost([f1, tslib]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } }); + service.checkNumberOfProjects({ externalProjects: 1 }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts new file mode 100644 index 00000000000..b22413c6ab3 --- /dev/null +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -0,0 +1,229 @@ +namespace ts.projectSystem { + describe("tsserver:: Inferred projects", () => { + it("should support files without extensions", () => { + const f = { + path: "/a/compile", + content: "let x = 1" + }; + const host = createServerHost([f]); + const session = createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "compilerOptionsForInferredProjects", + arguments: { + options: { + allowJs: true + } + } + }); + session.executeCommand({ + seq: 2, + type: "request", + command: "open", + arguments: { + file: f.path, + fileContent: f.content, + scriptKindName: "JS" + } + }); + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [f.path]); + }); + + it("inferred projects per project root", () => { + const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" }; + const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" }; + const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; + const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; + const host = createServerHost([file1, file2, file3, file4]); + const session = createSession(host, { + useSingleInferredProject: true, + useInferredProjectPerProjectRoot: true + }); + session.executeCommand({ + seq: 1, + type: "request", + command: CommandNames.CompilerOptionsForInferredProjects, + arguments: { + options: { + allowJs: true, + target: ScriptTarget.ESNext + } + } + }); + session.executeCommand({ + seq: 2, + type: "request", + command: CommandNames.CompilerOptionsForInferredProjects, + arguments: { + options: { + allowJs: true, + target: ScriptTarget.ES2015 + }, + projectRootPath: "/b" + } + }); + session.executeCommand({ + seq: 3, + type: "request", + command: CommandNames.Open, + arguments: { + file: file1.path, + fileContent: file1.content, + scriptKindName: "JS", + projectRootPath: file1.projectRootPath + } + }); + session.executeCommand({ + seq: 4, + type: "request", + command: CommandNames.Open, + arguments: { + file: file2.path, + fileContent: file2.content, + scriptKindName: "JS", + projectRootPath: file2.projectRootPath + } + }); + session.executeCommand({ + seq: 5, + type: "request", + command: CommandNames.Open, + arguments: { + file: file3.path, + fileContent: file3.content, + scriptKindName: "JS", + projectRootPath: file3.projectRootPath + } + }); + session.executeCommand({ + seq: 6, + type: "request", + command: CommandNames.Open, + arguments: { + file: file4.path, + fileContent: file4.content, + scriptKindName: "JS" + } + }); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { inferredProjects: 3 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); + checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); + assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); + }); + + function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) { + checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path)); + assert.equal(inferredProject.getCompilationSettings().target, target); + } + + function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const files: [File, File, File, File] = [ + { path: "/a/file1.ts", content: "let x = 1;" }, + { path: "/A/file2.ts", content: "let y = 2;" }, + { path: "/b/file2.ts", content: "let x = 3;" }, + { path: "/c/file3.ts", content: "let z = 4;" } + ]; + const host = createServerHost(files, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host, { useSingleInferredProject: true, }, { useInferredProjectPerProjectRoot: true }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ESNext + }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ES2015 + }, "/a"); + + openClientFiles(["/a", "/a", "/b", undefined]); + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], ScriptTarget.ES2015], + [[files[2]], ScriptTarget.ESNext] + ]); + closeClientFiles(); + + openClientFiles(["/a", "/A", "/b", undefined]); + if (useCaseSensitiveFileNames) { + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0]], ScriptTarget.ES2015], + [[files[1]], ScriptTarget.ESNext], + [[files[2]], ScriptTarget.ESNext] + ]); + } + else { + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], ScriptTarget.ES2015], + [[files[2]], ScriptTarget.ESNext] + ]); + } + closeClientFiles(); + + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ScriptTarget.ES2017 + }, "/A"); + + openClientFiles(["/a", "/a", "/b", undefined]); + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017], + [[files[2]], ScriptTarget.ESNext] + ]); + closeClientFiles(); + + openClientFiles(["/a", "/A", "/b", undefined]); + if (useCaseSensitiveFileNames) { + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0]], ScriptTarget.ES2015], + [[files[1]], ScriptTarget.ES2017], + [[files[2]], ScriptTarget.ESNext] + ]); + } + else { + verifyInferredProjectsState([ + [[files[3]], ScriptTarget.ESNext], + [[files[0], files[1]], ScriptTarget.ES2017], + [[files[2]], ScriptTarget.ESNext] + ]); + } + closeClientFiles(); + + function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) { + files.forEach((file, index) => { + projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]); + }); + } + + function closeClientFiles() { + files.forEach(file => projectService.closeClientFile(file.path)); + } + + function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) { + checkNumberOfProjects(projectService, { inferredProjects: expected.length }); + projectService.inferredProjects.forEach((p, index) => { + const [actualFiles, target] = expected[index]; + checkInferredProject(p, actualFiles, target); + }); + } + } + + it("inferred projects per project root with case sensitive system", () => { + verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); + + it("inferred projects per project root with case insensitive system", () => { + verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/languageService.ts b/src/testRunner/unittests/tsserver/languageService.ts new file mode 100644 index 00000000000..6ee68e74c15 --- /dev/null +++ b/src/testRunner/unittests/tsserver/languageService.ts @@ -0,0 +1,19 @@ +namespace ts.projectSystem { + describe("tsserver:: Language service", () => { + it("should work correctly on case-sensitive file systems", () => { + const lib = { + path: "/a/Lib/lib.d.ts", + content: "let x: number" + }; + const f = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + projectService.inferredProjects[0].getLanguageService().getProgram(); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts new file mode 100644 index 00000000000..dd0b954824d --- /dev/null +++ b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts @@ -0,0 +1,55 @@ +namespace ts.projectSystem { + describe("tsserver:: maxNodeModuleJsDepth for inferred projects", () => { + it("should be set to 2 if the project has js root files", () => { + const file1: File = { + path: "/a/b/file1.js", + content: `var t = require("test"); t.` + }; + const moduleFile: File = { + path: "/a/b/node_modules/test/index.js", + content: `var v = 10; module.exports = v;` + }; + + const host = createServerHost([file1, moduleFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + + let project = projectService.inferredProjects[0]; + let options = project.getCompilationSettings(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); + + // Assert the option sticks + projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); + project = projectService.inferredProjects[0]; + options = project.getCompilationSettings(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); + }); + + it("should return to normal state when all js root files are removed from project", () => { + const file1 = { + path: "/a/file1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/file2.js", + content: "let x =1;" + }; + + const host = createServerHost([file1, file2, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + + projectService.openClientFile(file1.path); + checkNumberOfInferredProjects(projectService, 1); + let project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); + + projectService.openClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); + + projectService.closeClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/metadataInResponse.ts b/src/testRunner/unittests/tsserver/metadataInResponse.ts new file mode 100644 index 00000000000..783fce4dd16 --- /dev/null +++ b/src/testRunner/unittests/tsserver/metadataInResponse.ts @@ -0,0 +1,99 @@ +namespace ts.projectSystem { + describe("tsserver:: with metadata in response", () => { + const metadata = "Extra Info"; + function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { + const output = host.getOutput().map(mapOutputToJson); + assert.deepEqual(output, [expectedResponse]); + host.clearOutput(); + } + + function verifyCommandWithMetadata(session: TestSession, host: TestServerHost, command: Partial, expectedResponseBody: U) { + command.seq = session.getSeq(); + command.type = "request"; + session.onMessage(JSON.stringify(command)); + verifyOutput(host, expectedResponseBody ? + { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } : + { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." } + ); + } + + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { plugins: [{ name: "myplugin" }] } + }) + }; + function createHostWithPlugin(files: ReadonlyArray) { + const host = createServerHost(files); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: server.PluginCreateInfo) { + const proxy = Harness.LanguageService.makeDefaultProxy(info); + proxy.getCompletionsAtPosition = (filename, position, options) => { + const result = info.languageService.getCompletionsAtPosition(filename, position, options); + if (result) { + result.metadata = metadata; + } + return result; + }; + return proxy; + } + }), + error: undefined + }; + }; + return host; + } + + describe("With completion requests", () => { + const completionRequestArgs: protocol.CompletionsRequestArgs = { + file: aTs.path, + line: 1, + offset: aTs.content.indexOf("this.") + 1 + "this.".length + }; + const expectedCompletionEntries: ReadonlyArray = [ + { name: "foo", kind: ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: "0" }, + { name: "prop", kind: ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: "0" } + ]; + + it("can pass through metadata when the command returns array", () => { + const host = createHostWithPlugin([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + verifyCommandWithMetadata>(session, host, { + command: protocol.CommandTypes.Completions, + arguments: completionRequestArgs + }, expectedCompletionEntries); + }); + + it("can pass through metadata when the command returns object", () => { + const host = createHostWithPlugin([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: protocol.CommandTypes.CompletionInfo, + arguments: completionRequestArgs + }, { + isGlobalCompletion: false, + isMemberCompletion: true, + isNewIdentifierLocation: false, + entries: expectedCompletionEntries + }); + }); + + it("returns undefined correctly", () => { + const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; + const host = createHostWithPlugin([aTs, tsconfig]); + const session = createSession(host); + openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: protocol.CommandTypes.Completions, + arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 } + }, /*expectedResponseBody*/ undefined); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/navTo.ts b/src/testRunner/unittests/tsserver/navTo.ts new file mode 100644 index 00000000000..5d45e537ede --- /dev/null +++ b/src/testRunner/unittests/tsserver/navTo.ts @@ -0,0 +1,30 @@ +namespace ts.projectSystem { + describe("tsserver:: navigate-to for javascript project", () => { + function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { + return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined; + } + + it("should not include type symbols", () => { + const file1: File = { + path: "/a/b/file1.js", + content: "function foo() {}" + }; + const configFile: File = { + path: "/a/b/jsconfig.json", + content: "{}" + }; + const host = createServerHost([file1, configFile, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); + + // Try to find some interface type defined in lib.d.ts + const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); + const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; + assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); + + const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); + const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[]; + assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/occurences.ts b/src/testRunner/unittests/tsserver/occurences.ts new file mode 100644 index 00000000000..bcdadd3872c --- /dev/null +++ b/src/testRunner/unittests/tsserver/occurences.ts @@ -0,0 +1,47 @@ +namespace ts.projectSystem { + describe("tsserver:: occurence highlight on string", () => { + it("should be marked if only on string values", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];` + }; + + const host = createServerHost([file1]); + const session = createSession(host); + const projectService = session.getProjectService(); + + projectService.openClientFile(file1.path); + { + const highlightRequest = makeSessionRequest( + CommandNames.Occurrences, + { file: file1.path, line: 1, offset: 11 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; + const firstOccurence = highlightResponse[0]; + assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); + } + + { + const highlightRequest = makeSessionRequest( + CommandNames.Occurrences, + { file: file1.path, line: 3, offset: 13 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; + assert.isTrue(highlightResponse.length === 2); + const firstOccurence = highlightResponse[0]; + assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); + } + + { + const highlightRequest = makeSessionRequest( + CommandNames.Occurrences, + { file: file1.path, line: 4, offset: 14 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; + assert.isTrue(highlightResponse.length === 2); + const firstOccurence = highlightResponse[0]; + assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); + } + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/openFile.ts b/src/testRunner/unittests/tsserver/openFile.ts new file mode 100644 index 00000000000..187b5043fcc --- /dev/null +++ b/src/testRunner/unittests/tsserver/openFile.ts @@ -0,0 +1,108 @@ +namespace ts.projectSystem { + describe("tsserver:: Open-file", () => { + it("can be reloaded with empty content", () => { + const f = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const projectFileName = "externalProject"; + const host = createServerHost([f]); + const projectService = createProjectService(host); + // create a project + projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + + const p = projectService.externalProjects[0]; + // force to load the content of the file + p.updateGraph(); + + const scriptInfo = p.getScriptInfo(f.path)!; + checkSnapLength(scriptInfo.getSnapshot(), f.content.length); + + // open project and replace its content with empty string + projectService.openClientFile(f.path, ""); + checkSnapLength(scriptInfo.getSnapshot(), 0); + }); + function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) { + assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size"); + } + + function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) { + const file1: File = { + path: "/a/b/src/app.ts", + content: "let x = 10;" + }; + const file2: File = { + path: "/a/B/lib/module2.ts", + content: "let z = 10;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "" + }; + const configFile2: File = { + path: "/a/tsconfig.json", + content: "" + }; + const host = createServerHost([file1, file2, configFile, configFile2], { + useCaseSensitiveFileNames + }); + const service = createProjectService(host); + + // Open file1 -> configFile + verifyConfigFileName(file1, "/a", configFile); + verifyConfigFileName(file1, "/a/b", configFile); + verifyConfigFileName(file1, "/a/B", configFile); + + // Open file2 use root "/a/b" + verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile); + verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile); + verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile); + + function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) { + const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); + assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path); + service.closeClientFile(file.path); + } + } + it("works when project root is used with case-sensitive system", () => { + verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true); + }); + + 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: File = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: File = { + 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: File = { + 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)); + } + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts new file mode 100644 index 00000000000..c7a42d6b813 --- /dev/null +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -0,0 +1,658 @@ +namespace ts.projectSystem { + describe("tsserver:: with project references and tsbuild", () => { + function createHost(files: ReadonlyArray, rootNames: ReadonlyArray) { + const host = createServerHost(files); + + // ts build should succeed + const solutionBuilder = tscWatch.createSolutionBuilder(host, rootNames, {}); + solutionBuilder.buildAllProjects(); + assert.equal(host.getOutput().length, 0); + + return host; + } + + describe("with container project", () => { + function getProjectFiles(project: string): [File, File] { + return [ + TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), + TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), + ]; + } + + const project = "container"; + const containerLib = getProjectFiles("container/lib"); + const containerExec = getProjectFiles("container/exec"); + const containerCompositeExec = getProjectFiles("container/compositeExec"); + const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); + const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; + + it("does not error on container only project", () => { + const host = createHost(files, [containerConfig.path]); + + // Open external project for the folder + const session = createSession(host); + const service = session.getProjectService(); + service.openExternalProjects([{ + projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), + rootFiles: files.map(f => ({ fileName: f.path })), + options: {} + }]); + checkNumberOfProjects(service, { configuredProjects: 4 }); + files.forEach(f => { + const args: protocol.FileRequestArgs = { + file: f.path, + projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined + }; + const syntaxDiagnostics = session.executeCommandSeq({ + command: protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: args + }).response; + assert.deepEqual(syntaxDiagnostics, []); + const semanticDiagnostics = session.executeCommandSeq({ + command: protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: args + }).response; + assert.deepEqual(semanticDiagnostics, []); + }); + const containerProject = service.configuredProjects.get(containerConfig.path)!; + checkProjectActualFiles(containerProject, [containerConfig.path]); + const optionsDiagnostics = session.executeCommandSeq({ + command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, + arguments: { projectFileName: containerProject.projectName } + }).response; + assert.deepEqual(optionsDiagnostics, []); + }); + + it("can successfully find references with --out options", () => { + const host = createHost(files, [containerConfig.path]); + const session = createSession(host); + openFilesForSession([containerCompositeExec[1]], session); + const service = session.getProjectService(); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { + file: containerCompositeExec[1].path, + ...locationOfMyConst + } + }).response as protocol.RenameResponseBody; + + + const myConstLen = "myConst".length; + const locationOfMyConstInLib = protocolLocationFromSubstring(containerLib[1].content, "myConst"); + assert.deepEqual(response.locs, [ + { file: containerCompositeExec[1].path, locs: [{ start: locationOfMyConst, end: { line: locationOfMyConst.line, offset: locationOfMyConst.offset + myConstLen } }] }, + { file: containerLib[1].path, locs: [{ start: locationOfMyConstInLib, end: { line: locationOfMyConstInLib.line, offset: locationOfMyConstInLib.offset + myConstLen } }] } + ]); + }); + }); + + describe("with main and depedency project", () => { + const projectLocation = "/user/username/projects/myproject"; + const dependecyLocation = `${projectLocation}/dependency`; + const mainLocation = `${projectLocation}/main`; + const dependencyTs: File = { + path: `${dependecyLocation}/FnS.ts`, + content: `export function fn1() { } +export function fn2() { } +export function fn3() { } +export function fn4() { } +export function fn5() { } +` + }; + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true } }) + }; + + const mainTs: File = { + path: `${mainLocation}/main.ts`, + content: `import { + fn1, + fn2, + fn3, + fn4, + fn5 +} from '../dependency/fns' + +fn1(); +fn2(); +fn3(); +fn4(); +fn5(); +` + }; + const mainConfig: File = { + path: `${mainLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true }, + references: [{ path: "../dependency" }] + }) + }; + + const randomFile: File = { + path: `${projectLocation}/random/random.ts`, + content: "let a = 10;" + }; + const randomConfig: File = { + path: `${projectLocation}/random/tsconfig.json`, + content: "{}" + }; + const dtsLocation = `${dependecyLocation}/FnS.d.ts`; + const dtsPath = dtsLocation.toLowerCase() as Path; + const dtsMapLocation = `${dtsLocation}.map`; + const dtsMapPath = dtsMapLocation.toLowerCase() as Path; + + const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; + + function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos)); + checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); + } + + function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { + verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); + } + + function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { + verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]); + } + + // Returns request and expected Response, expected response when no map file + interface SessionAction { + reqName: string; + request: Partial; + expectedResponse: Response; + expectedResponseNoMap?: Response; + expectedResponseNoDts?: Response; + } + function gotoDefintinionFromMainTs(fn: number): SessionAction { + const textSpan = usageSpan(fn); + const definition: protocol.FileSpan = { file: dependencyTs.path, ...definitionSpan(fn) }; + const declareSpaceLength = "declare ".length; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To dependency + definitions: [definition], + textSpan + }, + expectedResponseNoMap: { + // To the dts + definitions: [{ file: dtsPath, start: { line: fn, offset: definition.start.offset + declareSpaceLength }, end: { line: fn, offset: definition.end.offset + declareSpaceLength } }], + textSpan + }, + expectedResponseNoDts: { + // To import declaration + definitions: [{ file: mainTs.path, ...importSpan(fn) }], + textSpan + } + }; + } + + function definitionSpan(fn: number): protocol.TextSpan { + return { start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }; + } + function importSpan(fn: number): protocol.TextSpan { + return { start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } }; + } + function usageSpan(fn: number): protocol.TextSpan { + return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; + } + + function renameFromDependencyTs(fn: number): SessionAction { + const triggerSpan = definitionSpan(fn); + return { + reqName: "rename", + request: { + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...triggerSpan.start } + }, + expectedResponse: { + info: { + canRename: true, + fileToRename: undefined, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + triggerSpan + }, + locs: [ + { file: dependencyTs.path, locs: [triggerSpan] } + ] + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction { + const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, + locs: [ + locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + }, + // Only dependency result + expectedResponseNoMap: expectedResponse, + expectedResponseNoDts: expectedResponse + }; + } + + // Returns request and expected Response + type SessionActionGetter = (fn: number) => SessionAction; + // Open File, expectedProjectActualFiles, actionGetter, openFileLastLine + interface DocumentPositionMapperVerifier { + openFile: File; + expectedProjectActualFiles: ReadonlyArray; + actionGetter: SessionActionGetter; + openFileLastLine: number; + } + function verifyDocumentPositionMapperUpdates( + mainScenario: string, + verifier: ReadonlyArray, + closedInfos: ReadonlyArray) { + + const openFiles = verifier.map(v => v.openFile); + const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles); + const actionGetters = verifier.map(v => v.actionGetter); + const openFileLastLines = verifier.map(v => v.openFileLastLine); + + const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); + const openInfos = openFiles.map(f => f.path); + // When usage and dependency are used, dependency config is part of closedInfo so ignore + const otherWatchedFiles = verifier.length > 1 ? [configFiles[0]] : configFiles; + function openTsFile(onHostCreate?: (host: TestServerHost) => void) { + const host = createHost(files, [mainConfig.path]); + if (onHostCreate) { + onHostCreate(host); + } + const session = createSession(host); + openFilesForSession([...openFiles, randomFile], session); + return { host, session }; + } + + function checkProject(session: TestSession, noDts?: true) { + const service = session.getProjectService(); + checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); + configFiles.forEach((configFile, index) => { + checkProjectActualFiles( + service.configuredProjects.get(configFile)!, + noDts ? + expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) : + expectedProjectActualFiles[index] + ); + }); + } + + function verifyInfos(session: TestSession, host: TestServerHost) { + verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); + } + + function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: true) { + const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); + verifyInfosWithRandom( + session, + host, + openInfos, + closedInfos.filter(f => f !== dtsMapClosedInfo && (dependencyTsOK || f !== dependencyTs.path)), + dtsMapClosedInfo ? otherWatchedFiles.concat(dtsMapClosedInfo) : otherWatchedFiles + ); + } + + function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { + const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); + const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); + verifyInfosWithRandom( + session, + host, + openInfos, + closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)), + // When project actual file contains dts, it needs to be watched + dtsClosedInfo && expectedProjectActualFiles.some(expectedProjectActualFiles => expectedProjectActualFiles.some(f => f.toLowerCase() === dtsPath)) ? + otherWatchedFiles.concat(dtsClosedInfo) : + otherWatchedFiles + ); + } + + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); + if (notEqual) { + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + else { + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + } + + function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { + const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = actionGetter(fn); + const { response } = session.executeCommandSeq(request); + return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts }; + } + + function firstAction(session: TestSession) { + actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); + } + + function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) { + // action + let isFirst = true; + for (const actionGetter of actionGetters) { + for (let fn = 1; fn <= 5; fn++) { + const result = action(actionGetter, fn, session); + const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); + if (dtsAbsent) { + assert.isUndefined(dtsInfo); + } + else { + assert.isDefined(dtsInfo); + } + verifyAction(result, dtsInfo, isFirst); + isFirst = false; + } + } + } + + function verifyAllFnAction( + session: TestSession, + host: TestServerHost, + firstDocumentPositionMapperNotEquals?: true, + dependencyMap?: server.ScriptInfo, + documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] + ) { + // action + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }, dtsInfo, isFirst) => { + assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); + verifyInfos(session, host); + assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath); + if (isFirst) { + if (dependencyMap) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); + documentPositionMapper = dependencyMap.documentPositionMapper; + } + else { + dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; + documentPositionMapper = dependencyMap.documentPositionMapper; + } + } + else { + verifyDocumentPositionMapper(session, dependencyMap!, documentPositionMapper); + } + }); + return { dependencyMap: dependencyMap!, documentPositionMapper }; + } + + function verifyAllFnActionWithNoMap( + session: TestSession, + host: TestServerHost, + dependencyTsOK?: true + ) { + let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; + // action + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { + assert.deepEqual(response, expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); + verifyInfosWhenNoMapFile(session, host, dependencyTsOK); + assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); + if (isFirst) { + assert.isNotString(dtsInfo!.sourceMapFilePath); + assert.isNotFalse(dtsInfo!.sourceMapFilePath); + assert.isDefined(dtsInfo!.sourceMapFilePath); + sourceMapFilePath = dtsInfo!.sourceMapFilePath; + } + else { + assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); + } + }); + return sourceMapFilePath; + } + + function verifyAllFnActionWithNoDts( + session: TestSession, + host: TestServerHost, + dependencyTsAndMapOk?: true + ) { + // action + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts }) => { + assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); + verifyInfosWhenNoDtsFile(session, host, dependencyTsAndMapOk); + }, /*dtsAbsent*/ true); + } + + function verifyScenarioWithChangesWorker( + change: (host: TestServerHost, session: TestSession) => void, + afterActionDocumentPositionMapperNotEquals: true | undefined, + timeoutBeforeAction: boolean + ) { + const { host, session } = openTsFile(); + + // Create DocumentPositionMapper + firstAction(session); + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; + const documentPositionMapper = dependencyMap.documentPositionMapper; + + // change + change(host, session); + if (timeoutBeforeAction) { + host.runQueuedTimeoutCallbacks(); + checkProject(session); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); + } + + // action + verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); + } + + function verifyScenarioWithChanges( + scenarioName: string, + change: (host: TestServerHost, session: TestSession) => void, + afterActionDocumentPositionMapperNotEquals?: true + ) { + describe(scenarioName, () => { + it("when timeout occurs before request", () => { + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); + }); + + it("when timeout does not occur before request", () => { + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); + }); + }); + } + + function verifyMainScenarioAndScriptInfoCollection(session: TestSession, host: TestServerHost) { + // Main scenario action + const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host); + checkProject(session); + verifyInfos(session, host); + + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfos(session, host); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { + // Main scenario action + verifyAllFnActionWithNoMap(session, host, dependencyTsOKInScenario); + + // Collecting at this point retains dependency.d.ts and map watcher + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfosWhenNoMapFile(session, host); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { + // Main scenario action + verifyAllFnActionWithNoDts(session, host, dependencyTsAndMapOk); + + // Collecting at this point retains dependency.d.ts and map watcher + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfosWhenNoDtsFile(session, host); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + function verifyScenarioWhenFileNotPresent( + scenarioName: string, + fileLocation: string, + verifyScenarioAndScriptInfoCollection: (session: TestSession, host: TestServerHost, dependencyTsOk?: true) => void, + noDts?: true + ) { + describe(scenarioName, () => { + it(mainScenario, () => { + const { host, session } = openTsFile(host => host.deleteFile(fileLocation)); + checkProject(session, noDts); + + verifyScenarioAndScriptInfoCollection(session, host); + }); + + it("when file is created", () => { + let fileContents: string | undefined; + const { host, session } = openTsFile(host => { + fileContents = host.readFile(fileLocation); + host.deleteFile(fileLocation); + }); + firstAction(session); + + host.writeFile(fileLocation, fileContents!); + verifyMainScenarioAndScriptInfoCollection(session, host); + }); + + it("when file is deleted", () => { + const { host, session } = openTsFile(); + firstAction(session); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(fileLocation); + verifyScenarioAndScriptInfoCollection(session, host, /*dependencyTsOk*/ true); + }); + }); + } + + it(mainScenario, () => { + const { host, session } = openTsFile(); + checkProject(session); + + verifyMainScenarioAndScriptInfoCollection(session, host); + }); + + // Edit + verifyScenarioWithChanges( + "when usage file changes, document position mapper doesnt change", + (_host, session) => openFiles.forEach( + (openFile, index) => session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } + }) + ) + ); + + // Edit dts to add new fn + verifyScenarioWithChanges( + "when dependency .d.ts changes, document position mapper doesnt change", + host => host.writeFile( + dtsLocation, + host.readFile(dtsLocation)!.replace( + "//# sourceMappingURL=FnS.d.ts.map", + `export declare function fn6(): void; +//# sourceMappingURL=FnS.d.ts.map` + ) + ) + ); + + // Edit map file to represent added new line + verifyScenarioWithChanges( + "when dependency file's map changes", + host => host.writeFile( + dtsMapLocation, + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` + ), + /*afterActionDocumentPositionMapperNotEquals*/ true + ); + + verifyScenarioWhenFileNotPresent( + "when map file is not present", + dtsMapLocation, + verifyMainScenarioAndScriptInfoCollectionWithNoMap + ); + + verifyScenarioWhenFileNotPresent( + "when .d.ts file is not present", + dtsLocation, + verifyMainScenarioAndScriptInfoCollectionWithNoDts, + /*noDts*/ true + ); + } + + const usageVerifier: DocumentPositionMapperVerifier = { + openFile: mainTs, + expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], + actionGetter: gotoDefintinionFromMainTs, + openFileLastLine: 14 + }; + describe("from project that uses dependency", () => { + const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation]; + verifyDocumentPositionMapperUpdates( + "can go to definition correctly", + [usageVerifier], + closedInfos + ); + }); + + const definingVerifier: DocumentPositionMapperVerifier = { + openFile: dependencyTs, + expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], + actionGetter: renameFromDependencyTs, + openFileLastLine: 6 + }; + describe("from defining project", () => { + const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; + verifyDocumentPositionMapperUpdates( + "rename locations from dependency", + [definingVerifier], + closedInfos + ); + }); + + describe("when opening depedency and usage project", () => { + const closedInfos = [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path]; + verifyDocumentPositionMapperUpdates( + "goto Definition in usage and rename locations from defining project", + [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], + closedInfos + ); + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/refactors.ts b/src/testRunner/unittests/tsserver/refactors.ts new file mode 100644 index 00000000000..2e916f50f9b --- /dev/null +++ b/src/testRunner/unittests/tsserver/refactors.ts @@ -0,0 +1,120 @@ +namespace ts.projectSystem { + describe("tsserver:: refactors", () => { + it("use formatting options", () => { + const file = { + path: "/a.ts", + content: "function f() {\n 1;\n}", + }; + const host = createServerHost([file]); + const session = createSession(host); + openFilesForSession([file], session); + + const response0 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.Configure, + arguments: { + formatOptions: { + indentSize: 2, + }, + }, + }).response; + assert.deepEqual(response0, /*expected*/ undefined); + + const response1 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.GetEditsForRefactor, + arguments: { + refactor: "Extract Symbol", + action: "function_scope_1", + file: "/a.ts", + startLine: 2, + startOffset: 3, + endLine: 2, + endOffset: 4, + }, + }).response; + assert.deepEqual(response1, { + edits: [ + { + fileName: "/a.ts", + textChanges: [ + { + start: { line: 2, offset: 3 }, + end: { line: 2, offset: 5 }, + newText: "newFunction();", + }, + { + start: { line: 3, offset: 2 }, + end: { line: 3, offset: 2 }, + newText: "\n\nfunction newFunction() {\n 1;\n}\n", + }, + ] + } + ], + renameFilename: "/a.ts", + renameLocation: { line: 2, offset: 3 }, + }); + }); + + it("handles text changes in tsconfig.json", () => { + const aTs = { + path: "/a.ts", + content: "export const a = 0;", + }; + const tsconfig = { + path: "/tsconfig.json", + content: '{ "files": ["./a.ts"] }', + }; + + const session = createSession(createServerHost([aTs, tsconfig])); + openFilesForSession([aTs], session); + + const response1 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.GetEditsForRefactor, + arguments: { + refactor: "Move to a new file", + action: "Move to a new file", + file: "/a.ts", + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 20, + }, + }).response; + assert.deepEqual(response1, { + edits: [ + { + fileName: "/a.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 20 }, + newText: "", + }, + ], + }, + { + fileName: "/tsconfig.json", + textChanges: [ + { + start: { line: 1, offset: 21 }, + end: { line: 1, offset: 21 }, + newText: ", \"./a.1.ts\"", + }, + ], + }, + { + fileName: "/a.1.ts", + textChanges: [ + { + start: { line: 0, offset: 0 }, + end: { line: 0, offset: 0 }, + newText: "export const a = 0;\n", + }, + ], + } + ], + renameFilename: undefined, + renameLocation: undefined, + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/rename.ts b/src/testRunner/unittests/tsserver/rename.ts new file mode 100644 index 00000000000..3b4d77263e6 --- /dev/null +++ b/src/testRunner/unittests/tsserver/rename.ts @@ -0,0 +1,53 @@ +namespace ts.projectSystem { + describe("tsserver:: rename", () => { + it("works with fileToRename", () => { + const aTs: File = { path: "/a.ts", content: "export const a = 0;" }; + const bTs: File = { path: "/b.ts", content: 'import { a } from "./a";' }; + + const session = createSession(createServerHost([aTs, bTs])); + openFilesForSession([bTs], session); + + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";')); + assert.deepEqual(response, { + info: { + canRename: true, + fileToRename: aTs.path, + displayName: aTs.path, + fullDisplayName: aTs.path, + kind: ScriptElementKind.moduleElement, + kindModifiers: "", + triggerSpan: protocolTextSpanFromSubstring(bTs.content, "a", { index: 1 }), + }, + locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }], + }); + }); + + it("works with prefixText and suffixText", () => { + const aTs: File = { path: "/a.ts", content: "const x = 0; const o = { x };" }; + const session = createSession(createServerHost([aTs])); + openFilesForSession([aTs], session); + + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x")); + assert.deepEqual(response, { + info: { + canRename: true, + fileToRename: undefined, + displayName: "x", + fullDisplayName: "x", + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"), + }, + locs: [ + { + file: aTs.path, + locs: [ + protocolRenameSpanFromSubstring(aTs.content, "x"), + protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }, { prefixText: "x: " }), + ], + }, + ], + }); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index a07cb7721d4..9f75455e226 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -5,7 +5,7 @@ namespace ts.projectSystem { return resolutionTrace; } - describe("resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => { + describe("tsserver:: resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => { it("can load typings that are proper modules", () => { const file1 = { path: "/a/b/app.js", @@ -46,7 +46,7 @@ namespace ts.projectSystem { }); }); - describe("resolutionCache:: tsserverProjectSystem watching @types", () => { + describe("tsserver:: resolutionCache:: tsserverProjectSystem watching @types", () => { it("works correctly when typings are added or removed", () => { const f1 = { path: "/a/b/app.ts", @@ -92,7 +92,7 @@ namespace ts.projectSystem { }); }); - describe("resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { + describe("tsserver:: resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { it("should remove the `module not found` error", () => { const moduleFile = { path: "/a/b/moduleFile.ts", @@ -358,7 +358,7 @@ namespace ts.projectSystem { }); }); - describe("resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { + describe("tsserver:: resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { it("should restore the states for inferred projects", () => { const moduleFile = { path: "/a/b/moduleFile.ts", @@ -493,7 +493,7 @@ namespace ts.projectSystem { }); }); - describe("resolutionCache:: tsserverProjectSystem module resolution caching", () => { + describe("tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => { const projectLocation = "/user/username/projects/myproject"; const configFile: File = { path: `${projectLocation}/tsconfig.json`, diff --git a/src/testRunner/unittests/tsserver/syntaxOperations.ts b/src/testRunner/unittests/tsserver/syntaxOperations.ts new file mode 100644 index 00000000000..c2ec877137a --- /dev/null +++ b/src/testRunner/unittests/tsserver/syntaxOperations.ts @@ -0,0 +1,98 @@ +namespace ts.projectSystem { + describe("tsserver:: syntax operations", () => { + function navBarFull(session: TestSession, file: File) { + return JSON.stringify(session.executeCommandSeq({ + command: protocol.CommandTypes.NavBarFull, + arguments: { file: file.path } + }).response); + } + + function openFile(session: TestSession, file: File) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { file: file.path, fileContent: file.content } + }); + } + + it("works when file is removed and added with different content", () => { + const projectRoot = "/user/username/projects/myproject"; + const app: File = { + path: `${projectRoot}/app.ts`, + content: "console.log('Hello world');" + }; + const unitTest1: File = { + path: `${projectRoot}/unitTest1.ts`, + content: `import assert = require('assert'); + +describe("Test Suite 1", () => { + it("Test A", () => { + assert.ok(true, "This shouldn't fail"); + }); + + it("Test B", () => { + assert.ok(1 === 1, "This shouldn't fail"); + assert.ok(false, "This should fail"); + }); +});` + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + const files = [app, libFile, tsconfig]; + const host = createServerHost(files); + const session = createSession(host); + const service = session.getProjectService(); + openFile(session, app); + + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(tsconfig.path)!; + const expectedFilesWithoutUnitTest1 = files.map(f => f.path); + checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); + + host.writeFile(unitTest1.path, unitTest1.content); + host.runQueuedTimeoutCallbacks(); + const expectedFilesWithUnitTest1 = expectedFilesWithoutUnitTest1.concat(unitTest1.path); + checkProjectActualFiles(project, expectedFilesWithUnitTest1); + + openFile(session, unitTest1); + checkProjectActualFiles(project, expectedFilesWithUnitTest1); + + const navBarResultUnitTest1 = navBarFull(session, unitTest1); + host.deleteFile(unitTest1.path); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); + + session.executeCommandSeq({ + command: protocol.CommandTypes.Close, + arguments: { file: unitTest1.path } + }); + checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); + + const unitTest1WithChangedContent: File = { + path: unitTest1.path, + content: `import assert = require('assert'); + +export function Test1() { + assert.ok(true, "This shouldn't fail"); +}; + +export function Test2() { + assert.ok(1 === 1, "This shouldn't fail"); + assert.ok(false, "This should fail"); +};` + }; + host.writeFile(unitTest1.path, unitTest1WithChangedContent.content); + host.runQueuedTimeoutCallbacks(); + checkProjectActualFiles(project, expectedFilesWithUnitTest1); + + openFile(session, unitTest1WithChangedContent); + checkProjectActualFiles(project, expectedFilesWithUnitTest1); + const sourceFile = project.getLanguageService().getNonBoundSourceFile(unitTest1WithChangedContent.path); + assert.strictEqual(sourceFile.text, unitTest1WithChangedContent.content); + + const navBarResultUnitTest1WithChangedContent = navBarFull(session, unitTest1WithChangedContent); + assert.notStrictEqual(navBarResultUnitTest1WithChangedContent, navBarResultUnitTest1, "With changes in contents of unitTest file, we should see changed naviagation bar item result"); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/telemetry.ts b/src/testRunner/unittests/tsserver/telemetry.ts index f2a7d854124..4f4d8ba8b19 100644 --- a/src/testRunner/unittests/tsserver/telemetry.ts +++ b/src/testRunner/unittests/tsserver/telemetry.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("project telemetry", () => { + describe("tsserver:: project telemetry", () => { it("does nothing for inferred project", () => { const file = makeFile("/a.js"); const et = new TestServerEventManager([file]); diff --git a/src/testRunner/unittests/tsserver/textStorage.ts b/src/testRunner/unittests/tsserver/textStorage.ts index 0bb14aa2d90..157734da931 100644 --- a/src/testRunner/unittests/tsserver/textStorage.ts +++ b/src/testRunner/unittests/tsserver/textStorage.ts @@ -1,5 +1,5 @@ namespace ts.textStorage { - describe("Text storage", () => { + describe("tsserver:: Text storage", () => { const f = { path: "/a/app.ts", content: ` diff --git a/src/testRunner/unittests/tsserver/typeAquisition.ts b/src/testRunner/unittests/tsserver/typeAquisition.ts new file mode 100644 index 00000000000..9f487896e03 --- /dev/null +++ b/src/testRunner/unittests/tsserver/typeAquisition.ts @@ -0,0 +1,52 @@ +namespace ts.projectSystem { + describe("tsserver:: autoDiscovery", () => { + it("does not depend on extension", () => { + const file1 = { + path: "/a/b/app.html", + content: "" + }; + const file2 = { + path: "/a/b/app.d.ts", + content: "" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + projectFileName: "/a/b/proj.csproj", + rootFiles: [toExternalFile(file2.path), { fileName: file1.path, hasMixedContent: true, scriptKind: ScriptKind.JS }], + options: {} + }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + const typeAcquisition = projectService.externalProjects[0].getTypeAcquisition(); + assert.isTrue(typeAcquisition.enable, "Typine acquisition should be enabled"); + }); + }); + + describe("tsserver:: prefer typings to js", () => { + it("during second resolution pass", () => { + const typingsCacheLocation = "/a/typings"; + const f1 = { + path: "/a/b/app.js", + content: "var x = require('bar')" + }; + const barjs = { + path: "/a/b/node_modules/bar/index.js", + content: "export let x = 1" + }; + const barTypings = { + path: `${typingsCacheLocation}/node_modules/@types/bar/index.d.ts`, + content: "export let y: number" + }; + const config = { + path: "/a/b/jsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: ["node_modules"] }) + }; + const host = createServerHost([f1, barjs, barTypings, config]); + const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller(typingsCacheLocation, /*throttleLimit*/ 5, host) }); + + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, barTypings.path, config.path]); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts b/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts new file mode 100644 index 00000000000..8318f402a1a --- /dev/null +++ b/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts @@ -0,0 +1,87 @@ +namespace ts.projectSystem { + describe("tsserver:: typeReferenceDirectives", () => { + it("when typeReferenceDirective contains UpperCasePackage", () => { + const projectLocation = "/user/username/projects/myproject"; + const libProjectLocation = `${projectLocation}/lib`; + const typeLib: File = { + path: `${libProjectLocation}/@types/UpperCasePackage/index.d.ts`, + content: `declare class BrokenTest { + constructor(name: string, width: number, height: number, onSelect: Function); + Name: string; + SelectedFile: string; +}` + }; + const appLib: File = { + path: `${libProjectLocation}/@app/lib/index.d.ts`, + content: `/// +declare class TestLib { + issue: BrokenTest; + constructor(); + test(): void; +}` + }; + const testProjectLocation = `${projectLocation}/test`; + const testFile: File = { + path: `${testProjectLocation}/test.ts`, + content: `class TestClass1 { + + constructor() { + var l = new TestLib(); + + } + + public test2() { + var x = new BrokenTest('',0,0,null); + + } +}` + }; + const testConfig: File = { + path: `${testProjectLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "amd", + typeRoots: ["../lib/@types", "../lib/@app"] + } + }) + }; + + const files = [typeLib, appLib, testFile, testConfig, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(testFile.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(testConfig.path)!; + checkProjectActualFiles(project, files.map(f => f.path)); + host.writeFile(appLib.path, appLib.content.replace("test()", "test2()")); + host.checkTimeoutQueueLengthAndRun(2); + }); + + it("when typeReferenceDirective is relative path and in a sibling folder", () => { + const projectRootPath = "/user/username/projects/browser-addon"; + const projectPath = `${projectRootPath}/background`; + const file: File = { + path: `${projectPath}/a.ts`, + content: "let x = 10;" + }; + const tsconfig: File = { + path: `${projectPath}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + types: [ + "../typedefs/filesystem" + ] + } + }) + }; + const filesystem: File = { + path: `${projectRootPath}/typedefs/filesystem.d.ts`, + content: `interface LocalFileSystem { someProperty: string; }` + }; + const files = [file, tsconfig, filesystem, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(file.path); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index f0cd9549e6c..18117a5f961 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -47,7 +47,7 @@ namespace ts.projectSystem { import typingsName = TI.typingsName; - describe("typingsInstaller:: local module", () => { + describe("tsserver:: typingsInstaller:: local module", () => { it("should not be picked up", () => { const f1 = { path: "/a/app.js", @@ -86,7 +86,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: General functionality", () => { + describe("tsserver:: typingsInstaller:: General functionality", () => { it("configured projects (typings installed) 1", () => { const file1 = { path: "/a/b/app.js", @@ -1249,7 +1249,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: Validate package name:", () => { + describe("tsserver:: typingsInstaller:: Validate package name:", () => { it("name cannot be too long", () => { let packageName = "a"; for (let i = 0; i < 8; i++) { @@ -1273,7 +1273,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: Invalid package names", () => { + describe("tsserver:: typingsInstaller:: Invalid package names", () => { it("should not be installed", () => { const f1 = { path: "/a/b/app.js", @@ -1305,7 +1305,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: discover typings", () => { + describe("tsserver:: typingsInstaller:: discover typings", () => { const emptySafeList = emptyMap; it("should use mappings from safe list", () => { @@ -1517,7 +1517,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: telemetry events", () => { + describe("tsserver:: typingsInstaller:: telemetry events", () => { it("should be received", () => { const f1 = { path: "/a/app.js", @@ -1567,7 +1567,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: progress notifications", () => { + describe("tsserver:: typingsInstaller:: progress notifications", () => { it("should be sent for success", () => { const f1 = { path: "/a/app.js", @@ -1676,7 +1676,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: npm installation command", () => { + describe("tsserver:: typingsInstaller:: npm installation command", () => { const npmPath = "npm", tsVersion = "2.9.0-dev.20180410"; const packageNames = ["@types/graphql@ts2.8", "@types/highlight.js@ts2.8", "@types/jest@ts2.8", "@types/mini-css-extract-plugin@ts2.8", "@types/mongoose@ts2.8", "@types/pg@ts2.8", "@types/webpack-bundle-analyzer@ts2.8", "@types/enhanced-resolve@ts2.8", "@types/eslint-plugin-prettier@ts2.8", "@types/friendly-errors-webpack-plugin@ts2.8", "@types/hammerjs@ts2.8", "@types/history@ts2.8", "@types/image-size@ts2.8", "@types/js-cookie@ts2.8", "@types/koa-compress@ts2.8", "@types/less@ts2.8", "@types/material-ui@ts2.8", "@types/mysql@ts2.8", "@types/nodemailer@ts2.8", "@types/prettier@ts2.8", "@types/query-string@ts2.8", "@types/react-places-autocomplete@ts2.8", "@types/react-router@ts2.8", "@types/react-router-config@ts2.8", "@types/react-select@ts2.8", "@types/react-transition-group@ts2.8", "@types/redux-form@ts2.8", "@types/abbrev@ts2.8", "@types/accepts@ts2.8", "@types/acorn@ts2.8", "@types/ansi-regex@ts2.8", "@types/ansi-styles@ts2.8", "@types/anymatch@ts2.8", "@types/apollo-codegen@ts2.8", "@types/are-we-there-yet@ts2.8", "@types/argparse@ts2.8", "@types/arr-union@ts2.8", "@types/array-find-index@ts2.8", "@types/array-uniq@ts2.8", "@types/array-unique@ts2.8", "@types/arrify@ts2.8", "@types/assert-plus@ts2.8", "@types/async@ts2.8", "@types/autoprefixer@ts2.8", "@types/aws4@ts2.8", "@types/babel-code-frame@ts2.8", "@types/babel-generator@ts2.8", "@types/babel-plugin-syntax-jsx@ts2.8", "@types/babel-template@ts2.8", "@types/babel-traverse@ts2.8", "@types/babel-types@ts2.8", "@types/babylon@ts2.8", "@types/base64-js@ts2.8", "@types/basic-auth@ts2.8", "@types/big.js@ts2.8", "@types/bl@ts2.8", "@types/bluebird@ts2.8", "@types/body-parser@ts2.8", "@types/bonjour@ts2.8", "@types/boom@ts2.8", "@types/brace-expansion@ts2.8", "@types/braces@ts2.8", "@types/brorand@ts2.8", "@types/browser-resolve@ts2.8", "@types/bson@ts2.8", "@types/buffer-equal@ts2.8", "@types/builtin-modules@ts2.8", "@types/bytes@ts2.8", "@types/callsites@ts2.8", "@types/camelcase@ts2.8", "@types/camelcase-keys@ts2.8", "@types/caseless@ts2.8", "@types/change-emitter@ts2.8", "@types/check-types@ts2.8", "@types/cheerio@ts2.8", "@types/chokidar@ts2.8", "@types/chownr@ts2.8", "@types/circular-json@ts2.8", "@types/classnames@ts2.8", "@types/clean-css@ts2.8", "@types/clone@ts2.8", "@types/co-body@ts2.8", "@types/color@ts2.8", "@types/color-convert@ts2.8", "@types/color-name@ts2.8", "@types/color-string@ts2.8", "@types/colors@ts2.8", "@types/combined-stream@ts2.8", "@types/common-tags@ts2.8", "@types/component-emitter@ts2.8", "@types/compressible@ts2.8", "@types/compression@ts2.8", "@types/concat-stream@ts2.8", "@types/connect-history-api-fallback@ts2.8", "@types/content-disposition@ts2.8", "@types/content-type@ts2.8", "@types/convert-source-map@ts2.8", "@types/cookie@ts2.8", "@types/cookie-signature@ts2.8", "@types/cookies@ts2.8", "@types/core-js@ts2.8", "@types/cosmiconfig@ts2.8", "@types/create-react-class@ts2.8", "@types/cross-spawn@ts2.8", "@types/cryptiles@ts2.8", "@types/css-modules-require-hook@ts2.8", "@types/dargs@ts2.8", "@types/dateformat@ts2.8", "@types/debug@ts2.8", "@types/decamelize@ts2.8", "@types/decompress@ts2.8", "@types/decompress-response@ts2.8", "@types/deep-equal@ts2.8", "@types/deep-extend@ts2.8", "@types/deepmerge@ts2.8", "@types/defined@ts2.8", "@types/del@ts2.8", "@types/depd@ts2.8", "@types/destroy@ts2.8", "@types/detect-indent@ts2.8", "@types/detect-newline@ts2.8", "@types/diff@ts2.8", "@types/doctrine@ts2.8", "@types/download@ts2.8", "@types/draft-js@ts2.8", "@types/duplexer2@ts2.8", "@types/duplexer3@ts2.8", "@types/duplexify@ts2.8", "@types/ejs@ts2.8", "@types/end-of-stream@ts2.8", "@types/entities@ts2.8", "@types/escape-html@ts2.8", "@types/escape-string-regexp@ts2.8", "@types/escodegen@ts2.8", "@types/eslint-scope@ts2.8", "@types/eslint-visitor-keys@ts2.8", "@types/esprima@ts2.8", "@types/estraverse@ts2.8", "@types/etag@ts2.8", "@types/events@ts2.8", "@types/execa@ts2.8", "@types/exenv@ts2.8", "@types/exit@ts2.8", "@types/exit-hook@ts2.8", "@types/expect@ts2.8", "@types/express@ts2.8", "@types/express-graphql@ts2.8", "@types/extend@ts2.8", "@types/extract-zip@ts2.8", "@types/fancy-log@ts2.8", "@types/fast-diff@ts2.8", "@types/fast-levenshtein@ts2.8", "@types/figures@ts2.8", "@types/file-type@ts2.8", "@types/filenamify@ts2.8", "@types/filesize@ts2.8", "@types/finalhandler@ts2.8", "@types/find-root@ts2.8", "@types/find-up@ts2.8", "@types/findup-sync@ts2.8", "@types/forever-agent@ts2.8", "@types/form-data@ts2.8", "@types/forwarded@ts2.8", "@types/fresh@ts2.8", "@types/from2@ts2.8", "@types/fs-extra@ts2.8", "@types/get-caller-file@ts2.8", "@types/get-stdin@ts2.8", "@types/get-stream@ts2.8", "@types/get-value@ts2.8", "@types/glob-base@ts2.8", "@types/glob-parent@ts2.8", "@types/glob-stream@ts2.8", "@types/globby@ts2.8", "@types/globule@ts2.8", "@types/got@ts2.8", "@types/graceful-fs@ts2.8", "@types/gulp-rename@ts2.8", "@types/gulp-sourcemaps@ts2.8", "@types/gulp-util@ts2.8", "@types/gzip-size@ts2.8", "@types/handlebars@ts2.8", "@types/has-ansi@ts2.8", "@types/hasha@ts2.8", "@types/he@ts2.8", "@types/hoek@ts2.8", "@types/html-entities@ts2.8", "@types/html-minifier@ts2.8", "@types/htmlparser2@ts2.8", "@types/http-assert@ts2.8", "@types/http-errors@ts2.8", "@types/http-proxy@ts2.8", "@types/http-proxy-middleware@ts2.8", "@types/indent-string@ts2.8", "@types/inflected@ts2.8", "@types/inherits@ts2.8", "@types/ini@ts2.8", "@types/inline-style-prefixer@ts2.8", "@types/inquirer@ts2.8", "@types/internal-ip@ts2.8", "@types/into-stream@ts2.8", "@types/invariant@ts2.8", "@types/ip@ts2.8", "@types/ip-regex@ts2.8", "@types/is-absolute-url@ts2.8", "@types/is-binary-path@ts2.8", "@types/is-finite@ts2.8", "@types/is-glob@ts2.8", "@types/is-my-json-valid@ts2.8", "@types/is-number@ts2.8", "@types/is-object@ts2.8", "@types/is-path-cwd@ts2.8", "@types/is-path-in-cwd@ts2.8", "@types/is-promise@ts2.8", "@types/is-scoped@ts2.8", "@types/is-stream@ts2.8", "@types/is-svg@ts2.8", "@types/is-url@ts2.8", "@types/is-windows@ts2.8", "@types/istanbul-lib-coverage@ts2.8", "@types/istanbul-lib-hook@ts2.8", "@types/istanbul-lib-instrument@ts2.8", "@types/istanbul-lib-report@ts2.8", "@types/istanbul-lib-source-maps@ts2.8", "@types/istanbul-reports@ts2.8", "@types/jest-diff@ts2.8", "@types/jest-docblock@ts2.8", "@types/jest-get-type@ts2.8", "@types/jest-matcher-utils@ts2.8", "@types/jest-validate@ts2.8", "@types/jpeg-js@ts2.8", "@types/js-base64@ts2.8", "@types/js-string-escape@ts2.8", "@types/js-yaml@ts2.8", "@types/jsbn@ts2.8", "@types/jsdom@ts2.8", "@types/jsesc@ts2.8", "@types/json-parse-better-errors@ts2.8", "@types/json-schema@ts2.8", "@types/json-stable-stringify@ts2.8", "@types/json-stringify-safe@ts2.8", "@types/json5@ts2.8", "@types/jsonfile@ts2.8", "@types/jsontoxml@ts2.8", "@types/jss@ts2.8", "@types/keygrip@ts2.8", "@types/keymirror@ts2.8", "@types/keyv@ts2.8", "@types/klaw@ts2.8", "@types/koa-send@ts2.8", "@types/leven@ts2.8", "@types/listr@ts2.8", "@types/load-json-file@ts2.8", "@types/loader-runner@ts2.8", "@types/loader-utils@ts2.8", "@types/locate-path@ts2.8", "@types/lodash-es@ts2.8", "@types/lodash.assign@ts2.8", "@types/lodash.camelcase@ts2.8", "@types/lodash.clonedeep@ts2.8", "@types/lodash.debounce@ts2.8", "@types/lodash.escape@ts2.8", "@types/lodash.flowright@ts2.8", "@types/lodash.get@ts2.8", "@types/lodash.isarguments@ts2.8", "@types/lodash.isarray@ts2.8", "@types/lodash.isequal@ts2.8", "@types/lodash.isobject@ts2.8", "@types/lodash.isstring@ts2.8", "@types/lodash.keys@ts2.8", "@types/lodash.memoize@ts2.8", "@types/lodash.merge@ts2.8", "@types/lodash.mergewith@ts2.8", "@types/lodash.pick@ts2.8", "@types/lodash.sortby@ts2.8", "@types/lodash.tail@ts2.8", "@types/lodash.template@ts2.8", "@types/lodash.throttle@ts2.8", "@types/lodash.unescape@ts2.8", "@types/lodash.uniq@ts2.8", "@types/log-symbols@ts2.8", "@types/log-update@ts2.8", "@types/loglevel@ts2.8", "@types/loud-rejection@ts2.8", "@types/lru-cache@ts2.8", "@types/make-dir@ts2.8", "@types/map-obj@ts2.8", "@types/media-typer@ts2.8", "@types/mem@ts2.8", "@types/mem-fs@ts2.8", "@types/memory-fs@ts2.8", "@types/meow@ts2.8", "@types/merge-descriptors@ts2.8", "@types/merge-stream@ts2.8", "@types/methods@ts2.8", "@types/micromatch@ts2.8", "@types/mime@ts2.8", "@types/mime-db@ts2.8", "@types/mime-types@ts2.8", "@types/minimatch@ts2.8", "@types/minimist@ts2.8", "@types/minipass@ts2.8", "@types/mkdirp@ts2.8", "@types/mongodb@ts2.8", "@types/morgan@ts2.8", "@types/move-concurrently@ts2.8", "@types/ms@ts2.8", "@types/msgpack-lite@ts2.8", "@types/multimatch@ts2.8", "@types/mz@ts2.8", "@types/negotiator@ts2.8", "@types/node-dir@ts2.8", "@types/node-fetch@ts2.8", "@types/node-forge@ts2.8", "@types/node-int64@ts2.8", "@types/node-ipc@ts2.8", "@types/node-notifier@ts2.8", "@types/nomnom@ts2.8", "@types/nopt@ts2.8", "@types/normalize-package-data@ts2.8", "@types/normalize-url@ts2.8", "@types/number-is-nan@ts2.8", "@types/object-assign@ts2.8", "@types/on-finished@ts2.8", "@types/on-headers@ts2.8", "@types/once@ts2.8", "@types/onetime@ts2.8", "@types/opener@ts2.8", "@types/opn@ts2.8", "@types/optimist@ts2.8", "@types/ora@ts2.8", "@types/os-homedir@ts2.8", "@types/os-locale@ts2.8", "@types/os-tmpdir@ts2.8", "@types/p-cancelable@ts2.8", "@types/p-each-series@ts2.8", "@types/p-event@ts2.8", "@types/p-lazy@ts2.8", "@types/p-limit@ts2.8", "@types/p-locate@ts2.8", "@types/p-map@ts2.8", "@types/p-map-series@ts2.8", "@types/p-reduce@ts2.8", "@types/p-timeout@ts2.8", "@types/p-try@ts2.8", "@types/pako@ts2.8", "@types/parse-glob@ts2.8", "@types/parse-json@ts2.8", "@types/parseurl@ts2.8", "@types/path-exists@ts2.8", "@types/path-is-absolute@ts2.8", "@types/path-parse@ts2.8", "@types/pg-pool@ts2.8", "@types/pg-types@ts2.8", "@types/pify@ts2.8", "@types/pixelmatch@ts2.8", "@types/pkg-dir@ts2.8", "@types/pluralize@ts2.8", "@types/pngjs@ts2.8", "@types/prelude-ls@ts2.8", "@types/pretty-bytes@ts2.8", "@types/pretty-format@ts2.8", "@types/progress@ts2.8", "@types/promise-retry@ts2.8", "@types/proxy-addr@ts2.8", "@types/pump@ts2.8", "@types/q@ts2.8", "@types/qs@ts2.8", "@types/range-parser@ts2.8", "@types/rc@ts2.8", "@types/rc-select@ts2.8", "@types/rc-slider@ts2.8", "@types/rc-tooltip@ts2.8", "@types/rc-tree@ts2.8", "@types/react-event-listener@ts2.8", "@types/react-side-effect@ts2.8", "@types/react-slick@ts2.8", "@types/read-chunk@ts2.8", "@types/read-pkg@ts2.8", "@types/read-pkg-up@ts2.8", "@types/recompose@ts2.8", "@types/recursive-readdir@ts2.8", "@types/relateurl@ts2.8", "@types/replace-ext@ts2.8", "@types/request@ts2.8", "@types/request-promise-native@ts2.8", "@types/require-directory@ts2.8", "@types/require-from-string@ts2.8", "@types/require-relative@ts2.8", "@types/resolve@ts2.8", "@types/resolve-from@ts2.8", "@types/retry@ts2.8", "@types/rx@ts2.8", "@types/rx-lite@ts2.8", "@types/rx-lite-aggregates@ts2.8", "@types/safe-regex@ts2.8", "@types/sane@ts2.8", "@types/sass-graph@ts2.8", "@types/sax@ts2.8", "@types/scriptjs@ts2.8", "@types/semver@ts2.8", "@types/send@ts2.8", "@types/serialize-javascript@ts2.8", "@types/serve-index@ts2.8", "@types/serve-static@ts2.8", "@types/set-value@ts2.8", "@types/shallowequal@ts2.8", "@types/shelljs@ts2.8", "@types/sockjs@ts2.8", "@types/sockjs-client@ts2.8", "@types/source-list-map@ts2.8", "@types/source-map-support@ts2.8", "@types/spdx-correct@ts2.8", "@types/spdy@ts2.8", "@types/split@ts2.8", "@types/sprintf@ts2.8", "@types/sprintf-js@ts2.8", "@types/sqlstring@ts2.8", "@types/sshpk@ts2.8", "@types/stack-utils@ts2.8", "@types/stat-mode@ts2.8", "@types/statuses@ts2.8", "@types/strict-uri-encode@ts2.8", "@types/string-template@ts2.8", "@types/strip-ansi@ts2.8", "@types/strip-bom@ts2.8", "@types/strip-json-comments@ts2.8", "@types/supports-color@ts2.8", "@types/svg2png@ts2.8", "@types/svgo@ts2.8", "@types/table@ts2.8", "@types/tapable@ts2.8", "@types/tar@ts2.8", "@types/temp@ts2.8", "@types/tempfile@ts2.8", "@types/through@ts2.8", "@types/through2@ts2.8", "@types/tinycolor2@ts2.8", "@types/tmp@ts2.8", "@types/to-absolute-glob@ts2.8", "@types/tough-cookie@ts2.8", "@types/trim@ts2.8", "@types/tryer@ts2.8", "@types/type-check@ts2.8", "@types/type-is@ts2.8", "@types/ua-parser-js@ts2.8", "@types/uglify-js@ts2.8", "@types/uglifyjs-webpack-plugin@ts2.8", "@types/underscore@ts2.8", "@types/uniq@ts2.8", "@types/uniqid@ts2.8", "@types/untildify@ts2.8", "@types/urijs@ts2.8", "@types/url-join@ts2.8", "@types/url-parse@ts2.8", "@types/url-regex@ts2.8", "@types/user-home@ts2.8", "@types/util-deprecate@ts2.8", "@types/util.promisify@ts2.8", "@types/utils-merge@ts2.8", "@types/uuid@ts2.8", "@types/vali-date@ts2.8", "@types/vary@ts2.8", "@types/verror@ts2.8", "@types/vinyl@ts2.8", "@types/vinyl-fs@ts2.8", "@types/warning@ts2.8", "@types/watch@ts2.8", "@types/watchpack@ts2.8", "@types/webpack-dev-middleware@ts2.8", "@types/webpack-sources@ts2.8", "@types/which@ts2.8", "@types/window-size@ts2.8", "@types/wrap-ansi@ts2.8", "@types/write-file-atomic@ts2.8", "@types/ws@ts2.8", "@types/xml2js@ts2.8", "@types/xmlbuilder@ts2.8", "@types/xtend@ts2.8", "@types/yallist@ts2.8", "@types/yargs@ts2.8", "@types/yauzl@ts2.8", "@types/yeoman-generator@ts2.8", "@types/zen-observable@ts2.8", "@types/react-content-loader@ts2.8"]; const expectedCommands = [ @@ -1704,7 +1704,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: recomputing resolutions of unresolved imports", () => { + describe("tsserver:: typingsInstaller:: recomputing resolutions of unresolved imports", () => { const globalTypingsCacheLocation = "/tmp"; const appPath = "/a/b/app.js" as Path; const foooPath = "/a/b/node_modules/fooo/index.d.ts"; @@ -1775,7 +1775,7 @@ namespace ts.projectSystem { }); }); - describe("typingsInstaller:: tsserver:: with inferred Project", () => { + describe("tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => { it("when projectRootPath is provided", () => { const projects = "/users/username/projects"; const projectRootPath = `${projects}/san2`; diff --git a/src/testRunner/unittests/tsserver/untitledFiles.ts b/src/testRunner/unittests/tsserver/untitledFiles.ts new file mode 100644 index 00000000000..2c429707a94 --- /dev/null +++ b/src/testRunner/unittests/tsserver/untitledFiles.ts @@ -0,0 +1,45 @@ +namespace ts.projectSystem { + describe("tsserver:: Untitled files", () => { + it("Can convert positions to locations", () => { + const aTs: File = { path: "/proj/a.ts", content: "" }; + const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; + const session = createSession(createServerHost([aTs, tsconfig])); + + openFilesForSession([aTs], session); + + const untitledFile = "untitled:^Untitled-1"; + executeSessionRequestNoResponse(session, protocol.CommandTypes.Open, { + file: untitledFile, + fileContent: `/// \nlet foo = 1;\nfooo/**/`, + scriptKindName: "TS", + projectRootPath: "/proj", + }); + + const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { + file: untitledFile, + startLine: 3, + startOffset: 1, + endLine: 3, + endOffset: 5, + errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], + }); + assert.deepEqual | undefined>(response, [ + { + description: "Change spelling to 'foo'", + fixAllDescription: "Fix all detected spelling errors", + fixId: "fixSpelling", + fixName: "spelling", + changes: [{ + fileName: untitledFile, + textChanges: [{ + start: { line: 3, offset: 1 }, + end: { line: 3, offset: 5 }, + newText: "foo", + }], + }], + commands: undefined, + }, + ]); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/versionCache.ts b/src/testRunner/unittests/tsserver/versionCache.ts index cc534b6fa5b..bd75b2bbaf4 100644 --- a/src/testRunner/unittests/tsserver/versionCache.ts +++ b/src/testRunner/unittests/tsserver/versionCache.ts @@ -15,7 +15,7 @@ namespace ts { assert.equal(editedText, checkText); } - describe(`VersionCache TS code`, () => { + describe(`tsserver:: VersionCache TS code`, () => { let validateEditAtLineCharIndex: (line: number, char: number, deleteLength: number, insertString: string) => void; before(() => { @@ -77,7 +77,7 @@ var q:Point=p;`; }); }); - describe(`VersionCache simple text`, () => { + describe(`tsserver:: VersionCache simple text`, () => { let validateEditAtPosition: (position: number, deleteLength: number, insertString: string) => void; let testContent: string; let lines: string[]; @@ -181,7 +181,7 @@ and grew 1cm per day`; }); }); - describe(`VersionCache stress test`, () => { + describe(`tsserver:: VersionCache stress test`, () => { let rsa: number[] = []; let la: number[] = []; let las: number[] = []; diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 8bce3505626..5ca4d6f4570 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -1,6 +1,6 @@ namespace ts.projectSystem { import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - describe("watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { + describe("tsserver:: watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { const projectFolder = "/a/username/project"; const projectSrcFolder = `${projectFolder}/src`; @@ -83,7 +83,7 @@ namespace ts.projectSystem { }); }); - describe("watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { + describe("tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; const configFile: File = { diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 85be445171a..1f851a10c47 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -1,35 +1,5 @@ namespace ts.projectSystem { - function checkOpenFiles(projectService: server.ProjectService, expectedFiles: File[]) { - checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path)); - } - - function checkScriptInfos(projectService: server.ProjectService, expectedFiles: ReadonlyArray) { - checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles); - } - - function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs { - return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) }; - } - function protocolFileSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileSpan { - return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring, options) }; - } - function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan { - return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) }; - } - function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { - return documentSpanFromSubstring(file, substring, options); - } - describe("tsserverProjectSystem general functionality", () => { - const commonFile1: File = { - path: "/a/b/commonFile1.ts", - content: "let x = 1" - }; - const commonFile2: File = { - path: "/a/b/commonFile2.ts", - content: "let y = 1" - }; - it("create inferred project", () => { const appFile: File = { path: "/a/b/c/app.ts", @@ -3046,7 +3016,7 @@ var x = 10;` }; const tsconfig: File = { path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { } }), + content: JSON.stringify({ compilerOptions: {} }), }; const host = createServerHost([file, tsconfig]); const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); @@ -3115,7 +3085,7 @@ var x = 10;` }; const tsconfig: File = { path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ }), + content: JSON.stringify({}), }; const host = createServerHost([file, tsconfig]); const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); @@ -3228,249 +3198,6 @@ var x = 10;` }); }); - describe("tsserverProjectSystem autoDiscovery", () => { - it("does not depend on extension", () => { - const file1 = { - path: "/a/b/app.html", - content: "" - }; - const file2 = { - path: "/a/b/app.d.ts", - content: "" - }; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - projectFileName: "/a/b/proj.csproj", - rootFiles: [toExternalFile(file2.path), { fileName: file1.path, hasMixedContent: true, scriptKind: ScriptKind.JS }], - options: {} - }); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - const typeAcquisition = projectService.externalProjects[0].getTypeAcquisition(); - assert.isTrue(typeAcquisition.enable, "Typine acquisition should be enabled"); - }); - }); - - describe("tsserverProjectSystem navigate-to for javascript project", () => { - function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { - return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined; - } - - it("should not include type symbols", () => { - const file1: File = { - path: "/a/b/file1.js", - content: "function foo() {}" - }; - const configFile: File = { - path: "/a/b/jsconfig.json", - content: "{}" - }; - const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); - - // Try to find some interface type defined in lib.d.ts - const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); - const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; - assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); - - const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); - const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[]; - assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); - }); - }); - - describe("tsserverProjectSystem prefer typings to js", () => { - it("during second resolution pass", () => { - const typingsCacheLocation = "/a/typings"; - const f1 = { - path: "/a/b/app.js", - content: "var x = require('bar')" - }; - const barjs = { - path: "/a/b/node_modules/bar/index.js", - content: "export let x = 1" - }; - const barTypings = { - path: `${typingsCacheLocation}/node_modules/@types/bar/index.d.ts`, - content: "export let y: number" - }; - const config = { - path: "/a/b/jsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: ["node_modules"] }) - }; - const host = createServerHost([f1, barjs, barTypings, config]); - const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller(typingsCacheLocation, /*throttleLimit*/ 5, host) }); - - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, barTypings.path, config.path]); - }); - }); - - describe("tsserverProjectSystem format settings", () => { - it("can be set globally", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x;" - }; - const host = createServerHost([f1]); - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); - - const defaultSettings = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); - - // set global settings - const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks }; - projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 }); - - // get format options for file - should be equal to new global settings - const s1 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings"); - - // set per file format options - const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter }; - projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path }); - - // get format options for file - should be equal to new per-file settings - const s2 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings"); - - // set new global settings - they should not affect ones that were set per-file - const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements }; - projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 }); - - // get format options for file - should be equal to new per-file settings - const s3 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings"); - }); - }); - - describe("tsserverProjectSystem Open-file", () => { - it("can be reloaded with empty content", () => { - const f = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const projectFileName = "externalProject"; - const host = createServerHost([f]); - const projectService = createProjectService(host); - // create a project - projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} }); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - - const p = projectService.externalProjects[0]; - // force to load the content of the file - p.updateGraph(); - - const scriptInfo = p.getScriptInfo(f.path)!; - checkSnapLength(scriptInfo.getSnapshot(), f.content.length); - - // open project and replace its content with empty string - projectService.openClientFile(f.path, ""); - checkSnapLength(scriptInfo.getSnapshot(), 0); - }); - function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) { - assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size"); - } - - function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) { - const file1: File = { - path: "/a/b/src/app.ts", - content: "let x = 10;" - }; - const file2: File = { - path: "/a/B/lib/module2.ts", - content: "let z = 10;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "" - }; - const configFile2: File = { - path: "/a/tsconfig.json", - content: "" - }; - const host = createServerHost([file1, file2, configFile, configFile2], { - useCaseSensitiveFileNames - }); - const service = createProjectService(host); - - // Open file1 -> configFile - verifyConfigFileName(file1, "/a", configFile); - verifyConfigFileName(file1, "/a/b", configFile); - verifyConfigFileName(file1, "/a/B", configFile); - - // Open file2 use root "/a/b" - verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile); - verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile); - verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile); - - function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) { - const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); - assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path); - service.closeClientFile(file.path); - } - } - it("works when project root is used with case-sensitive system", () => { - verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true); - }); - - 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: File = { - path: `${projectFolder}/src/a.ts`, - content: "export const x = 0;" - }; - const configFile: File = { - 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: File = { - 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", () => { - it("should work correctly on case-sensitive file systems", () => { - const lib = { - path: "/a/Lib/lib.d.ts", - content: "let x: number" - }; - const f = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - projectService.inferredProjects[0].getLanguageService().getProgram(); - }); - }); - describe("tsserverProjectSystem non-existing directories listed in config file input array", () => { it("should be tolerated without crashing the server", () => { const configFile = { @@ -3551,2912 +3278,4 @@ var x = 10;` projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 }); }); }); - - describe("tsserverProjectSystem Inferred projects", () => { - it("should support files without extensions", () => { - const f = { - path: "/a/compile", - content: "let x = 1" - }; - const host = createServerHost([f]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "compilerOptionsForInferredProjects", - arguments: { - options: { - allowJs: true - } - } - }); - session.executeCommand({ - seq: 2, - type: "request", - command: "open", - arguments: { - file: f.path, - fileContent: f.content, - scriptKindName: "JS" - } - }); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [f.path]); - }); - - it("inferred projects per project root", () => { - const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" }; - const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" }; - const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; - const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; - const host = createServerHost([file1, file2, file3, file4]); - const session = createSession(host, { - useSingleInferredProject: true, - useInferredProjectPerProjectRoot: true - }); - session.executeCommand({ - seq: 1, - type: "request", - command: CommandNames.CompilerOptionsForInferredProjects, - arguments: { - options: { - allowJs: true, - target: ScriptTarget.ESNext - } - } - }); - session.executeCommand({ - seq: 2, - type: "request", - command: CommandNames.CompilerOptionsForInferredProjects, - arguments: { - options: { - allowJs: true, - target: ScriptTarget.ES2015 - }, - projectRootPath: "/b" - } - }); - session.executeCommand({ - seq: 3, - type: "request", - command: CommandNames.Open, - arguments: { - file: file1.path, - fileContent: file1.content, - scriptKindName: "JS", - projectRootPath: file1.projectRootPath - } - }); - session.executeCommand({ - seq: 4, - type: "request", - command: CommandNames.Open, - arguments: { - file: file2.path, - fileContent: file2.content, - scriptKindName: "JS", - projectRootPath: file2.projectRootPath - } - }); - session.executeCommand({ - seq: 5, - type: "request", - command: CommandNames.Open, - arguments: { - file: file3.path, - fileContent: file3.content, - scriptKindName: "JS", - projectRootPath: file3.projectRootPath - } - }); - session.executeCommand({ - seq: 6, - type: "request", - command: CommandNames.Open, - arguments: { - file: file4.path, - fileContent: file4.content, - scriptKindName: "JS" - } - }); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); - checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); - }); - - function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) { - checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path)); - assert.equal(inferredProject.getCompilationSettings().target, target); - } - - function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const files: [File, File, File, File] = [ - { path: "/a/file1.ts", content: "let x = 1;" }, - { path: "/A/file2.ts", content: "let y = 2;" }, - { path: "/b/file2.ts", content: "let x = 3;" }, - { path: "/c/file3.ts", content: "let z = 4;" } - ]; - const host = createServerHost(files, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host, { useSingleInferredProject: true, }, { useInferredProjectPerProjectRoot: true }); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ESNext - }); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2015 - }, "/a"); - - openClientFiles(["/a", "/a", "/b", undefined]); - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2015], - [[files[2]], ScriptTarget.ESNext] - ]); - closeClientFiles(); - - openClientFiles(["/a", "/A", "/b", undefined]); - if (useCaseSensitiveFileNames) { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0]], ScriptTarget.ES2015], - [[files[1]], ScriptTarget.ESNext], - [[files[2]], ScriptTarget.ESNext] - ]); - } - else { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2015], - [[files[2]], ScriptTarget.ESNext] - ]); - } - closeClientFiles(); - - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2017 - }, "/A"); - - openClientFiles(["/a", "/a", "/b", undefined]); - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - closeClientFiles(); - - openClientFiles(["/a", "/A", "/b", undefined]); - if (useCaseSensitiveFileNames) { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0]], ScriptTarget.ES2015], - [[files[1]], ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - } - else { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - } - closeClientFiles(); - - function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) { - files.forEach((file, index) => { - projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]); - }); - } - - function closeClientFiles() { - files.forEach(file => projectService.closeClientFile(file.path)); - } - - function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) { - checkNumberOfProjects(projectService, { inferredProjects: expected.length }); - projectService.inferredProjects.forEach((p, index) => { - const [actualFiles, target] = expected[index]; - checkInferredProject(p, actualFiles, target); - }); - } - } - - it("inferred projects per project root with case sensitive system", () => { - verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); - - it("inferred projects per project root with case insensitive system", () => { - verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - }); - }); - - describe("tsserverProjectSystem import helpers", () => { - it("should not crash in tsserver", () => { - const f1 = { - path: "/a/app.ts", - content: "export async function foo() { return 100; }" - }; - const tslib = { - path: "/a/node_modules/tslib/index.d.ts", - content: "" - }; - const host = createServerHost([f1, tslib]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } }); - service.checkNumberOfProjects({ externalProjects: 1 }); - }); - }); - - describe("tsserverProjectSystem searching for config file", () => { - it("should stop at projectRootPath if given", () => { - const f1 = { - path: "/a/file1.ts", - content: "" - }; - const configFile = { - path: "/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, configFile]); - const service = createProjectService(host); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); - - checkNumberOfConfiguredProjects(service, 0); - checkNumberOfInferredProjects(service, 1); - - service.closeClientFile(f1.path); - service.openClientFile(f1.path); - checkNumberOfConfiguredProjects(service, 1); - checkNumberOfInferredProjects(service, 0); - }); - - it("should use projectRootPath when searching for inferred project again", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; - const f1 = { - path: `${configFileLocation}/file1.ts`, - content: "" - }; - const configFile = { - path: `${configFileLocation}/tsconfig.json`, - content: "{}" - }; - const configFile2 = { - path: "/a/b/projects/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - checkNumberOfProjects(service, { configuredProjects: 1 }); - assert.isDefined(service.configuredProjects.get(configFile.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - const typeRootLocations = getTypeRootsFromLocation(configFileLocation); - checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true); - - // Delete config file - should create inferred project and not configured project - host.reloadFS([f1, libFile, configFile2]); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true); - }); - - it("should use projectRootPath when searching for inferred project again 2", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; - const f1 = { - path: `${configFileLocation}/file1.ts`, - content: "" - }; - const configFile = { - path: `${configFileLocation}/tsconfig.json`, - content: "{}" - }; - const configFile2 = { - path: "/a/b/projects/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { useSingleInferredProject: true }, { useInferredProjectPerProjectRoot: true }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - checkNumberOfProjects(service, { configuredProjects: 1 }); - assert.isDefined(service.configuredProjects.get(configFile.path)); - checkWatchedFiles(host, [libFile.path, configFile.path]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true); - - // Delete config file - should create inferred project with project root path set - host.reloadFS([f1, libFile, configFile2]); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - assert.equal(service.inferredProjects[0].projectRootPath, projectDir); - checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true); - }); - - describe("when the opened file is not from project root", () => { - const projectRoot = "/a/b/projects/project"; - const file: File = { - path: `${projectRoot}/src/index.ts`, - content: "let y = 10" - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const files = [file, libFile]; - const filesWithConfig = files.concat(tsconfig); - const dirOfFile = getDirectoryPath(file.path); - - function openClientFile(files: File[]) { - const host = createServerHost(files); - const projectService = createProjectService(host); - - projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); - return { host, projectService }; - } - - function verifyConfiguredProject(host: TestServerHost, projectService: TestProjectService, orphanInferredProject?: boolean) { - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: orphanInferredProject ? 1 : 0 }); - const project = Debug.assertDefined(projectService.configuredProjects.get(tsconfig.path)); - - if (orphanInferredProject) { - const inferredProject = projectService.inferredProjects[0]; - assert.isTrue(inferredProject.isOrphan()); - } - - checkProjectActualFiles(project, [file.path, libFile.path, tsconfig.path]); - checkWatchedFiles(host, [libFile.path, tsconfig.path]); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, (orphanInferredProject ? [projectRoot, `${dirOfFile}/node_modules/@types`] : [projectRoot]).concat(getTypeRootsFromLocation(projectRoot)), /*recursive*/ true); - } - - function verifyInferredProject(host: TestServerHost, projectService: TestProjectService) { - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - assert.isDefined(project); - - const filesToWatch = [libFile.path]; - forEachAncestorDirectory(dirOfFile, ancestor => { - filesToWatch.push(combinePaths(ancestor, "tsconfig.json")); - filesToWatch.push(combinePaths(ancestor, "jsconfig.json")); - }); - - checkProjectActualFiles(project, [file.path, libFile.path]); - checkWatchedFiles(host, filesToWatch); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, getTypeRootsFromLocation(dirOfFile), /*recursive*/ true); - } - - it("tsconfig for the file exists", () => { - const { host, projectService } = openClientFile(filesWithConfig); - verifyConfiguredProject(host, projectService); - - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - verifyInferredProject(host, projectService); - - host.reloadFS(filesWithConfig); - host.runQueuedTimeoutCallbacks(); - verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); - }); - - it("tsconfig for the file does not exist", () => { - const { host, projectService } = openClientFile(files); - verifyInferredProject(host, projectService); - - host.reloadFS(filesWithConfig); - host.runQueuedTimeoutCallbacks(); - verifyConfiguredProject(host, projectService, /*orphanInferredProject*/ true); - - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - verifyInferredProject(host, projectService); - }); - }); - }); - - describe("tsserverProjectSystem cancellationToken", () => { - // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test - let oldPrepare: AnyFunction; - before(() => { - oldPrepare = (Error as any).prepareStackTrace; - delete (Error as any).prepareStackTrace; - }); - - after(() => { - (Error as any).prepareStackTrace = oldPrepare; - }); - - it("is attached to request", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let xyz = 1;" - }; - const host = createServerHost([f1]); - let expectedRequestId: number; - const cancellationToken: server.ServerCancellationToken = { - isCancellationRequested: () => false, - setRequest: requestId => { - if (expectedRequestId === undefined) { - assert.isTrue(false, "unexpected call"); - } - assert.equal(requestId, expectedRequestId); - }, - resetRequest: noop - }; - - const session = createSession(host, { cancellationToken }); - - expectedRequestId = session.getNextSeq(); - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - }); - - expectedRequestId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - }); - - expectedRequestId = session.getNextSeq(); - session.executeCommandSeq({ - command: "occurrences", - arguments: { file: f1.path, line: 1, offset: 6 } - }); - - expectedRequestId = 2; - host.runQueuedImmediateCallbacks(); - expectedRequestId = 2; - host.runQueuedImmediateCallbacks(); - }); - - it("Geterr is cancellable", () => { - const f1 = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {} - }) - }; - - const cancellationToken = new TestServerCancellationToken(); - const host = createServerHost([f1, config]); - const session = createSession(host, { - canUseEvents: true, - eventHandler: noop, - cancellationToken - }); - { - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - }); - // send geterr for missing file - session.executeCommandSeq({ - command: "geterr", - arguments: { files: ["/a/missing"] } - }); - // no files - expect 'completed' event - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(session.getSeq(), 0); - } - { - const getErrId = session.getNextSeq(); - // send geterr for a valid file - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - }); - - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - - // run new request - session.executeCommandSeq({ - command: "projectInfo", - arguments: { file: f1.path } - }); - session.clearMessages(); - - // cancel previously issued Geterr - cancellationToken.setRequestToCancel(getErrId); - host.runQueuedTimeoutCallbacks(); - - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(getErrId, 0); - - cancellationToken.resetToken(); - } - { - const getErrId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - }); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0); - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); - - cancellationToken.setRequestToCancel(getErrId); - host.runQueuedImmediateCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(getErrId, 0); - - cancellationToken.resetToken(); - } - { - const getErrId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - }); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0); - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); - - // the semanticDiag message - host.runQueuedImmediateCallbacks(); - assert.equal(host.getOutput().length, 1); - const e2 = getMessage(0); - assert.equal(e2.event, "semanticDiag"); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - assert.equal(host.getOutput().length, 2); - const e3 = getMessage(0); - assert.equal(e3.event, "suggestionDiag"); - verifyRequestCompleted(getErrId, 1); - - cancellationToken.resetToken(); - } - { - const getErr1 = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - }); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0); - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); - - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - }); - // make sure that getErr1 is completed - verifyRequestCompleted(getErr1, 0); - } - - function verifyRequestCompleted(expectedSeq: number, n: number) { - const event = getMessage(n); - assert.equal(event.event, "requestCompleted"); - assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); - session.clearMessages(); - } - - function getMessage(n: number) { - return JSON.parse(server.extractMessage(host.getOutput()[n])); - } - }); - - it("Lower priority tasks are cancellable", () => { - const f1 = { - path: "/a/app.ts", - content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {} - }) - }; - const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3); - const host = createServerHost([f1, config]); - const session = createSession(host, { - canUseEvents: true, - eventHandler: noop, - cancellationToken, - throttleWaitMilliseconds: 0 - }); - { - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - }); - - // send navbar request (normal priority) - session.executeCommandSeq({ - command: "navbar", - arguments: { file: f1.path } - }); - - // ensure the nav bar request can be canceled - verifyExecuteCommandSeqIsCancellable({ - command: "navbar", - arguments: { file: f1.path } - }); - - // send outlining spans request (normal priority) - session.executeCommandSeq({ - command: "outliningSpans", - arguments: { file: f1.path } - }); - - // ensure the outlining spans request can be canceled - verifyExecuteCommandSeqIsCancellable({ - command: "outliningSpans", - arguments: { file: f1.path } - }); - } - - function verifyExecuteCommandSeqIsCancellable(request: Partial) { - // Set the next request to be cancellable - // The cancellation token will cancel the request the third time - // isCancellationRequested() is called. - cancellationToken.setRequestToCancel(session.getNextSeq()); - let operationCanceledExceptionThrown = false; - - try { - session.executeCommandSeq(request); - } - catch (e) { - assert(e instanceof OperationCanceledException); - operationCanceledExceptionThrown = true; - } - assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); - } - }); - }); - - describe("tsserverProjectSystem occurence highlight on string", () => { - it("should be marked if only on string values", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];` - }; - - const host = createServerHost([file1]); - const session = createSession(host); - const projectService = session.getProjectService(); - - projectService.openClientFile(file1.path); - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 1, offset: 11 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - const firstOccurence = highlightResponse[0]; - assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); - } - - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 3, offset: 13 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert.isTrue(highlightResponse.length === 2); - const firstOccurence = highlightResponse[0]; - assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); - } - - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 4, offset: 14 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert.isTrue(highlightResponse.length === 2); - const firstOccurence = highlightResponse[0]; - assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); - } - }); - }); - - describe("tsserverProjectSystem maxNodeModuleJsDepth for inferred projects", () => { - it("should be set to 2 if the project has js root files", () => { - const file1: File = { - path: "/a/b/file1.js", - content: `var t = require("test"); t.` - }; - const moduleFile: File = { - path: "/a/b/node_modules/test/index.js", - content: `var v = 10; module.exports = v;` - }; - - const host = createServerHost([file1, moduleFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - - let project = projectService.inferredProjects[0]; - let options = project.getCompilationSettings(); - assert.isTrue(options.maxNodeModuleJsDepth === 2); - - // Assert the option sticks - projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); - project = projectService.inferredProjects[0]; - options = project.getCompilationSettings(); - assert.isTrue(options.maxNodeModuleJsDepth === 2); - }); - - it("should return to normal state when all js root files are removed from project", () => { - const file1 = { - path: "/a/file1.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/file2.js", - content: "let x =1;" - }; - - const host = createServerHost([file1, file2, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - - projectService.openClientFile(file1.path); - checkNumberOfInferredProjects(projectService, 1); - let project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); - - projectService.openClientFile(file2.path); - project = projectService.inferredProjects[0]; - assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); - - projectService.closeClientFile(file2.path); - project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); - }); - }); - - describe("tsserverProjectSystem refactors", () => { - it("use formatting options", () => { - const file = { - path: "/a.ts", - content: "function f() {\n 1;\n}", - }; - const host = createServerHost([file]); - const session = createSession(host); - openFilesForSession([file], session); - - const response0 = session.executeCommandSeq({ - command: server.protocol.CommandTypes.Configure, - arguments: { - formatOptions: { - indentSize: 2, - }, - }, - }).response; - assert.deepEqual(response0, /*expected*/ undefined); - - const response1 = session.executeCommandSeq({ - command: server.protocol.CommandTypes.GetEditsForRefactor, - arguments: { - refactor: "Extract Symbol", - action: "function_scope_1", - file: "/a.ts", - startLine: 2, - startOffset: 3, - endLine: 2, - endOffset: 4, - }, - }).response; - assert.deepEqual(response1, { - edits: [ - { - fileName: "/a.ts", - textChanges: [ - { - start: { line: 2, offset: 3 }, - end: { line: 2, offset: 5 }, - newText: "newFunction();", - }, - { - start: { line: 3, offset: 2 }, - end: { line: 3, offset: 2 }, - newText: "\n\nfunction newFunction() {\n 1;\n}\n", - }, - ] - } - ], - renameFilename: "/a.ts", - renameLocation: { line: 2, offset: 3 }, - }); - }); - - it("handles text changes in tsconfig.json", () => { - const aTs = { - path: "/a.ts", - content: "export const a = 0;", - }; - const tsconfig = { - path: "/tsconfig.json", - content: '{ "files": ["./a.ts"] }', - }; - - const session = createSession(createServerHost([aTs, tsconfig])); - openFilesForSession([aTs], session); - - const response1 = session.executeCommandSeq({ - command: server.protocol.CommandTypes.GetEditsForRefactor, - arguments: { - refactor: "Move to a new file", - action: "Move to a new file", - file: "/a.ts", - startLine: 1, - startOffset: 1, - endLine: 1, - endOffset: 20, - }, - }).response; - assert.deepEqual(response1, { - edits: [ - { - fileName: "/a.ts", - textChanges: [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 20 }, - newText: "", - }, - ], - }, - { - fileName: "/tsconfig.json", - textChanges: [ - { - start: { line: 1, offset: 21 }, - end: { line: 1, offset: 21 }, - newText: ", \"./a.1.ts\"", - }, - ], - }, - { - fileName: "/a.1.ts", - textChanges: [ - { - start: { line: 0, offset: 0 }, - end: { line: 0, offset: 0 }, - newText: "export const a = 0;\n", - }, - ], - } - ], - renameFilename: undefined, - renameLocation: undefined, - }); - }); - }); - - describe("tsserverProjectSystem forceConsistentCasingInFileNames", () => { - it("works when extends is specified with a case insensitive file system", () => { - const rootPath = "/Users/username/dev/project"; - const file1: File = { - path: `${rootPath}/index.ts`, - content: 'import {x} from "file2";', - }; - const file2: File = { - path: `${rootPath}/file2.js`, - content: "", - }; - const file2Dts: File = { - path: `${rootPath}/types/file2/index.d.ts`, - content: "export declare const x: string;", - }; - const tsconfigAll: File = { - path: `${rootPath}/tsconfig.all.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: ".", - paths: { file2: ["./file2.js"] }, - typeRoots: ["./types"], - forceConsistentCasingInFileNames: true, - }, - }), - }; - const tsconfig: File = { - path: `${rootPath}/tsconfig.json`, - content: JSON.stringify({ extends: "./tsconfig.all.json" }), - }; - - const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); - const session = createSession(host); - - openFilesForSession([file1], session); - const projectService = session.getProjectService(); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - - const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); - }); - }); - - describe("tsserverProjectSystem getEditsForFileRename", () => { - it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { - const userTs: File = { - path: "/user.ts", - content: 'import { x } from "./old";', - }; - const newTs: File = { - path: "/new.ts", - content: "export const x = 0;", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; - - const host = createServerHost([userTs, newTs, tsconfig]); - const projectService = createProjectService(host); - projectService.openClientFile(userTs.path); - const project = projectService.configuredProjects.get(tsconfig.path)!; - - Debug.assert(!!project.resolveModuleNames); - - const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions); - assert.deepEqual>(edits, [{ - fileName: "/user.ts", - textChanges: [{ - span: textSpanFromSubstring(userTs.content, "./old"), - newText: "./new", - }], - }]); - }); - - it("works with multiple projects", () => { - const aUserTs: File = { - path: "/a/user.ts", - content: 'import { x } from "./old";', - }; - const aOldTs: File = { - path: "/a/old.ts", - content: "export const x = 0;", - }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }), - }; - const bUserTs: File = { - path: "/b/user.ts", - content: 'import { x } from "../a/old";', - }; - const bTsconfig: File = { - path: "/b/tsconfig.json", - content: "{}", - }; - - const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); - const session = createSession(host); - openFilesForSession([aUserTs, bUserTs], session); - - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: aOldTs.path, - newFilePath: "/a/new.ts", - }); - assert.deepEqual>(response, [ - { - fileName: aTsconfig.path, - textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }], - }, - { - fileName: aUserTs.path, - textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }], - }, - { - fileName: bUserTs.path, - textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }], - }, - ]); - }); - - it("works with file moved to inferred project", () => { - const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' }; - const cTs: File = { path: "/c.ts", content: "export {};" }; - const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) }; - - const host = createServerHost([aTs, cTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs, cTs], session); - - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: "/b.ts", - newFilePath: cTs.path, - }); - assert.deepEqual>(response, [ - { - fileName: "/tsconfig.json", - textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }], - }, - { - fileName: "/a.ts", - textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }], - }, - ]); - }); - }); - - describe("tsserverProjectSystem document registry in project service", () => { - const projectRootPath = "/user/username/projects/project"; - const importModuleContent = `import {a} from "./module1"`; - const file: File = { - path: `${projectRootPath}/index.ts`, - content: importModuleContent - }; - const moduleFile: File = { - path: `${projectRootPath}/module1.d.ts`, - content: "export const a: number;" - }; - const configFile: File = { - path: `${projectRootPath}/tsconfig.json`, - content: JSON.stringify({ files: ["index.ts"] }) - }; - - function getProject(service: TestProjectService) { - return service.configuredProjects.get(configFile.path)!; - } - - function checkProject(service: TestProjectService, moduleIsOrphan: boolean) { - // Update the project - const project = getProject(service); - project.getLanguageService(); - checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - assert.isDefined(moduleInfo); - assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); - const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings()); - assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path), [[key, moduleIsOrphan ? undefined : 1]]); - } - - function createServiceAndHost() { - const host = createServerHost([file, moduleFile, libFile, configFile]); - const service = createProjectService(host); - service.openClientFile(file.path); - checkProject(service, /*moduleIsOrphan*/ false); - return { host, service }; - } - - function changeFileToNotImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, [{ span: { start: 0, length: importModuleContent.length }, newText: "" }]); - checkProject(service, /*moduleIsOrphan*/ true); - } - - function changeFileToImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, [{ span: { start: 0, length: 0 }, newText: importModuleContent }]); - checkProject(service, /*moduleIsOrphan*/ false); - } - - it("Caches the source file if script info is orphan", () => { - const { service } = createServiceAndHost(); - const project = getProject(service); - - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - - // edit file - changeFileToNotImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - - // write content back - changeFileToImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - }); - - it("Caches the source file if script info is orphan, and orphan script info changes", () => { - const { host, service } = createServiceAndHost(); - const project = getProject(service); - - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - - // edit file - changeFileToNotImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - - const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; - host.writeFile(moduleFile.path, updatedModuleContent); - - // write content back - changeFileToImportModule(service); - assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); - }); - }); - - describe("tsserverProjectSystem syntax operations", () => { - function navBarFull(session: TestSession, file: File) { - return JSON.stringify(session.executeCommandSeq({ - command: protocol.CommandTypes.NavBarFull, - arguments: { file: file.path } - }).response); - } - - function openFile(session: TestSession, file: File) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { file: file.path, fileContent: file.content } - }); - } - - it("works when file is removed and added with different content", () => { - const projectRoot = "/user/username/projects/myproject"; - const app: File = { - path: `${projectRoot}/app.ts`, - content: "console.log('Hello world');" - }; - const unitTest1: File = { - path: `${projectRoot}/unitTest1.ts`, - content: `import assert = require('assert'); - -describe("Test Suite 1", () => { - it("Test A", () => { - assert.ok(true, "This shouldn't fail"); - }); - - it("Test B", () => { - assert.ok(1 === 1, "This shouldn't fail"); - assert.ok(false, "This should fail"); - }); -});` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const files = [app, libFile, tsconfig]; - const host = createServerHost(files); - const session = createSession(host); - const service = session.getProjectService(); - openFile(session, app); - - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = service.configuredProjects.get(tsconfig.path)!; - const expectedFilesWithoutUnitTest1 = files.map(f => f.path); - checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); - - host.writeFile(unitTest1.path, unitTest1.content); - host.runQueuedTimeoutCallbacks(); - const expectedFilesWithUnitTest1 = expectedFilesWithoutUnitTest1.concat(unitTest1.path); - checkProjectActualFiles(project, expectedFilesWithUnitTest1); - - openFile(session, unitTest1); - checkProjectActualFiles(project, expectedFilesWithUnitTest1); - - const navBarResultUnitTest1 = navBarFull(session, unitTest1); - host.deleteFile(unitTest1.path); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); - - session.executeCommandSeq({ - command: protocol.CommandTypes.Close, - arguments: { file: unitTest1.path } - }); - checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); - - const unitTest1WithChangedContent: File = { - path: unitTest1.path, - content: `import assert = require('assert'); - -export function Test1() { - assert.ok(true, "This shouldn't fail"); -}; - -export function Test2() { - assert.ok(1 === 1, "This shouldn't fail"); - assert.ok(false, "This should fail"); -};` - }; - host.writeFile(unitTest1.path, unitTest1WithChangedContent.content); - host.runQueuedTimeoutCallbacks(); - checkProjectActualFiles(project, expectedFilesWithUnitTest1); - - openFile(session, unitTest1WithChangedContent); - checkProjectActualFiles(project, expectedFilesWithUnitTest1); - const sourceFile = project.getLanguageService().getNonBoundSourceFile(unitTest1WithChangedContent.path); - assert.strictEqual(sourceFile.text, unitTest1WithChangedContent.content); - - const navBarResultUnitTest1WithChangedContent = navBarFull(session, unitTest1WithChangedContent); - assert.notStrictEqual(navBarResultUnitTest1WithChangedContent, navBarResultUnitTest1, "With changes in contents of unitTest file, we should see changed naviagation bar item result"); - }); - }); - - describe("tsserverProjectSystem completions", () => { - it("works", () => { - const aTs: File = { - path: "/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/b.ts", - content: "foo", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; - - const session = createSession(createServerHost([aTs, bTs, tsconfig])); - openFilesForSession([aTs, bTs], session); - - const requestLocation: protocol.FileLocationRequestArgs = { - file: bTs.path, - line: 1, - offset: 3, - }; - - const response = executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - includeExternalModuleExports: true, - prefix: "foo", - }); - const entry: protocol.CompletionEntry = { - hasAction: true, - insertText: undefined, - isRecommended: undefined, - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - name: "foo", - replacementSpan: undefined, - sortText: "0", - source: "/a", - }; - assert.deepEqual(response, { - isGlobalCompletion: true, - isMemberCompletion: false, - isNewIdentifierLocation: false, - entries: [entry], - }); - - const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { - ...requestLocation, - entryNames: [{ name: "foo", source: "/a" }], - }; - - const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); - const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = { - displayParts: [ - keywordPart(SyntaxKind.ConstKeyword), - spacePart(), - displayPart("foo", SymbolDisplayPartKind.localName), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - displayPart("0", SymbolDisplayPartKind.stringLiteral), - ], - documentation: emptyArray, - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - name: "foo", - source: [{ text: "./a", kind: "text" }], - tags: undefined, - }; - assert.deepEqual | undefined>(detailsResponse, [ - { - codeActions: [ - { - description: `Import 'foo' from module "./a"`, - changes: [ - { - fileName: "/b.ts", - textChanges: [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: 'import { foo } from "./a";\n\n', - }, - ], - }, - ], - commands: undefined, - }, - ], - ...detailsCommon, - }, - ]); - - interface CompletionDetailsFullRequest extends protocol.FileLocationRequest { - readonly command: protocol.CommandTypes.CompletionDetailsFull; - readonly arguments: protocol.CompletionDetailsRequestArgs; - } - interface CompletionDetailsFullResponse extends protocol.Response { - readonly body?: ReadonlyArray; - } - const detailsFullResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); - assert.deepEqual | undefined>(detailsFullResponse, [ - { - codeActions: [ - { - description: `Import 'foo' from module "./a"`, - changes: [ - { - fileName: "/b.ts", - textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], - }, - ], - commands: undefined, - } - ], - ...detailsCommon, - } - ]); - }); - }); - - describe("tsserverProjectSystem rename", () => { - it("works with fileToRename", () => { - const aTs: File = { path: "/a.ts", content: "export const a = 0;" }; - const bTs: File = { path: "/b.ts", content: 'import { a } from "./a";' }; - - const session = createSession(createServerHost([aTs, bTs])); - openFilesForSession([bTs], session); - - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(bTs, 'a";')); - assert.deepEqual(response, { - info: { - canRename: true, - fileToRename: aTs.path, - displayName: aTs.path, - fullDisplayName: aTs.path, - kind: ScriptElementKind.moduleElement, - kindModifiers: "", - triggerSpan: protocolTextSpanFromSubstring(bTs.content, "a", { index: 1 }), - }, - locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }], - }); - }); - - it("works with prefixText and suffixText", () => { - const aTs: File = { path: "/a.ts", content: "const x = 0; const o = { x };" }; - const session = createSession(createServerHost([aTs])); - openFilesForSession([aTs], session); - - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x")); - assert.deepEqual(response, { - info: { - canRename: true, - fileToRename: undefined, - displayName: "x", - fullDisplayName: "x", - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.none, - triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"), - }, - locs: [ - { - file: aTs.path, - locs: [ - protocolRenameSpanFromSubstring(aTs.content, "x"), - protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }, { prefixText: "x: " }), - ], - }, - ], - }); - }); - }); - - describe("tsserverProjectSystem typeReferenceDirectives", () => { - it("when typeReferenceDirective contains UpperCasePackage", () => { - const projectLocation = "/user/username/projects/myproject"; - const libProjectLocation = `${projectLocation}/lib`; - const typeLib: File = { - path: `${libProjectLocation}/@types/UpperCasePackage/index.d.ts`, - content: `declare class BrokenTest { - constructor(name: string, width: number, height: number, onSelect: Function); - Name: string; - SelectedFile: string; -}` - }; - const appLib: File = { - path: `${libProjectLocation}/@app/lib/index.d.ts`, - content: `/// -declare class TestLib { - issue: BrokenTest; - constructor(); - test(): void; -}` - }; - const testProjectLocation = `${projectLocation}/test`; - const testFile: File = { - path: `${testProjectLocation}/test.ts`, - content: `class TestClass1 { - - constructor() { - var l = new TestLib(); - - } - - public test2() { - var x = new BrokenTest('',0,0,null); - - } -}` - }; - const testConfig: File = { - path: `${testProjectLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "amd", - typeRoots: ["../lib/@types", "../lib/@app"] - } - }) - }; - - const files = [typeLib, appLib, testFile, testConfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(testFile.path); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = service.configuredProjects.get(testConfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - host.writeFile(appLib.path, appLib.content.replace("test()", "test2()")); - host.checkTimeoutQueueLengthAndRun(2); - }); - - it("when typeReferenceDirective is relative path and in a sibling folder", () => { - const projectRootPath = "/user/username/projects/browser-addon"; - const projectPath = `${projectRootPath}/background`; - const file: File = { - path: `${projectPath}/a.ts`, - content: "let x = 10;" - }; - const tsconfig: File = { - path: `${projectPath}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - types: [ - "../typedefs/filesystem" - ] } - }) - }; - const filesystem: File = { - path: `${projectRootPath}/typedefs/filesystem.d.ts`, - content: `interface LocalFileSystem { someProperty: string; }` - }; - const files = [file, tsconfig, filesystem, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(file.path); - }); - }); - - describe("tsserverProjectSystem project references", () => { - const aTs: File = { - path: "/a/a.ts", - content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", - }; - const compilerOptions: CompilerOptions = { - outDir: "bin", - declaration: true, - declarationMap: true, - composite: true, - }; - const configContent = JSON.stringify({ compilerOptions }); - const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; - - const aDtsMapContent: RawSourceMap = { - version: 3, - file: "a.d.ts", - sourceRoot: "", - sources: ["../a.ts"], - names: [], - mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" - }; - const aDtsMap: File = { - path: "/a/bin/a.d.ts.map", - content: JSON.stringify(aDtsMapContent), - }; - const aDts: File = { - path: "/a/bin/a.d.ts", - // Need to mangle the sourceMappingURL part or it breaks the build - content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, - }; - - const bTs: File = { - path: "/b/b.ts", - content: "export function fnB() {}", - }; - const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; - - const bDtsMapContent: RawSourceMap = { - version: 3, - file: "b.d.ts", - sourceRoot: "", - sources: ["../b.ts"], - names: [], - mappings: "AAAA,wBAAgB,GAAG,SAAK", - }; - const bDtsMap: File = { - path: "/b/bin/b.d.ts.map", - content: JSON.stringify(bDtsMapContent), - }; - const bDts: File = { - // Need to mangle the sourceMappingURL part or it breaks the build - path: "/b/bin/b.d.ts", - content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, - }; - - const dummyFile: File = { - path: "/dummy/dummy.ts", - content: "let a = 10;" - }; - - const userTs: File = { - path: "/user/user.ts", - content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', - }; - - const userTsForConfigProject: File = { - path: "/user/user.ts", - content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', - }; - - const userTsconfig: File = { - path: "/user/tsconfig.json", - content: JSON.stringify({ - file: ["user.ts"], - references: [{ path: "../a" }, { path: "../b" }] - }) - }; - - function makeSampleProjects(addUserTsConfig?: boolean) { - const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); - const session = createSession(host); - - checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); - checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); - - // Testing what happens if we delete the original sources. - host.deleteFile(bTs.path); - - openFilesForSession([userTs], session); - const service = session.getProjectService(); - checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 1 } : { inferredProjects: 1 }); - return session; - } - - function verifyInferredProjectUnchanged(session: TestSession) { - checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); - } - - function verifyDummyProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); - } - - function verifyOnlyOrphanInferredProject(session: TestSession) { - openFilesForSession([dummyFile], session); - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); - verifyDummyProject(session); - } - - function verifySingleInferredProject(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); - verifyInferredProjectUnchanged(session); - - // Close user file should close all the projects after opening dummy file - closeFilesForSession([userTs], session); - verifyOnlyOrphanInferredProject(session); - } - - function verifyATsConfigProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); - } - - function verifyATsConfigOriginalProject(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); - // Close user file should close all the projects - closeFilesForSession([userTs], session); - verifyOnlyOrphanInferredProject(session); - } - - function verifyATsConfigWhenOpened(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); - - closeFilesForSession([userTs], session); - openFilesForSession([dummyFile], session); - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyDummyProject(session); - verifyATsConfigProject(session); // ATsConfig should still be alive - } - - function verifyUserTsConfigProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]); - } - - it("goToDefinition", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]); - verifySingleInferredProject(session); - }); - - it("getDefinitionAndBoundSpan", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [protocolFileSpanFromSubstring(aTs, "fnA")], - }); - verifySingleInferredProject(session); - }); - - it("getDefinitionAndBoundSpan with file navigation", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true); - const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [protocolFileSpanFromSubstring(aTs, "fnA")], - }); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); - verifyUserTsConfigProject(session); - - // Navigate to the definition - closeFilesForSession([userTs], session); - openFilesForSession([aTs], session); - - // UserTs configured project should be alive - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - verifyUserTsConfigProject(session); - verifyATsConfigProject(session); - - closeFilesForSession([aTs], session); - verifyOnlyOrphanInferredProject(session); - }); - - it("goToType", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA")); - assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "IfaceA")]); - verifySingleInferredProject(session); - }); - - it("goToImplementation", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [protocolFileSpanFromSubstring(aTs, "fnA")]); - verifySingleInferredProject(session); - }); - - it("goToDefinition -- target does not exist", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()")); - // bTs does not exist, so stick with bDts - assert.deepEqual(response, [protocolFileSpanFromSubstring(bDts, "fnB")]); - verifySingleInferredProject(session); - }); - - it("navigateTo", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); - assert.deepEqual | undefined>(response, [ - { - ...protocolFileSpanFromSubstring(bDts, "export declare function fnB(): void;"), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export,declare", - }, - { - ...protocolFileSpanFromSubstring(userTs, "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"), - name: "fnUser", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - { - ...protocolFileSpanFromSubstring(aTs, "export function fnA() {}"), - name: "fnA", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - ]); - - verifyATsConfigOriginalProject(session); - }); - - const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem(aTs, /*isDefinition*/ true, "fnA", "export function fnA() {}"); - const referencesUserTs = (userTs: File): ReadonlyArray => [ - makeReferenceItem(userTs, /*isDefinition*/ false, "fnA", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"), - ]; - - it("findAllReferences", () => { - const session = makeSampleProjects(); - - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - refs: [...referencesUserTs(userTs), referenceATs(aTs)], - symbolName: "fnA", - symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset, - symbolDisplayString: "function fnA(): void", - }); - - verifyATsConfigOriginalProject(session); - }); - - it("findAllReferences -- starting at definition", () => { - const session = makeSampleProjects(); - openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA")); - assert.deepEqual(response, { - refs: [referenceATs(aTs), ...referencesUserTs(userTs)], - symbolName: "fnA", - symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset, - symbolDisplayString: "function fnA(): void", - }); - verifyATsConfigWhenOpened(session); - }); - - interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } - interface ReferencesFullResponse extends protocol.Response { readonly body: ReadonlyArray; } - - it("findAllReferencesFull", () => { - const session = makeSampleProjects(); - - const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); - - assert.deepEqual>(responseFull, [ - { - definition: { - ...documentSpanFromSubstring(aTs, "fnA"), - kind: ScriptElementKind.functionElement, - name: "function fnA(): void", - containerKind: ScriptElementKind.unknown, - containerName: "", - displayParts: [ - keywordPart(SyntaxKind.FunctionKeyword), - spacePart(), - displayPart("fnA", SymbolDisplayPartKind.functionName), - punctuationPart(SyntaxKind.OpenParenToken), - punctuationPart(SyntaxKind.CloseParenToken), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - keywordPart(SyntaxKind.VoidKeyword), - ], - }, - references: [ - makeReferenceEntry(userTs, /*isDefinition*/ false, "fnA"), - makeReferenceEntry(aTs, /*isDefinition*/ true, "fnA"), - ], - }, - ]); - verifyATsConfigOriginalProject(session); - }); - - it("findAllReferencesFull definition is in mapped file", () => { - const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), - }; - const bTs: File = { path: "/b/b.ts", content: `f();` }; - const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; - const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; - const aDtsMap: File = { - path: "/bin/a.d.ts.map", - content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), - }; - - const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); - checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); - openFilesForSession([bTs], session); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); - - const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); - - assert.deepEqual>(responseFull, [ - { - definition: { - containerKind: ScriptElementKind.unknown, - containerName: "", - displayParts: [ - keywordPart(SyntaxKind.FunctionKeyword), - spacePart(), - displayPart("f", SymbolDisplayPartKind.functionName), - punctuationPart(SyntaxKind.OpenParenToken), - punctuationPart(SyntaxKind.CloseParenToken), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - keywordPart(SyntaxKind.VoidKeyword), - ], - fileName: aTs.path, - kind: ScriptElementKind.functionElement, - name: "function f(): void", - textSpan: { start: 9, length: 1 }, - }, - references: [ - { - fileName: bTs.path, - isDefinition: false, - isInString: undefined, - isWriteAccess: false, - textSpan: { start: 0, length: 1 }, - }, - { - fileName: aTs.path, - isDefinition: true, - isInString: undefined, - isWriteAccess: true, - textSpan: { start: 9, length: 1 }, - }, - ], - } - ]); - }); - - it("findAllReferences -- target does not exist", () => { - const session = makeSampleProjects(); - - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()")); - assert.deepEqual(response, { - refs: [ - makeReferenceItem(bDts, /*isDefinition*/ true, "fnB", "export declare function fnB(): void;"), - makeReferenceItem(userTs, /*isDefinition*/ false, "fnB", "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"), - ], - symbolName: "fnB", - symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset, - symbolDisplayString: "function fnB(): void", - }); - verifySingleInferredProject(session); - }); - - const renameATs = (aTs: File): protocol.SpanGroup => ({ - file: aTs.path, - locs: [protocolRenameSpanFromSubstring(aTs.content, "fnA")], - }); - const renameUserTs = (userTs: File): protocol.SpanGroup => ({ - file: userTs.path, - locs: [protocolRenameSpanFromSubstring(userTs.content, "fnA")], - }); - - it("renameLocations", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnA", - fileToRename: undefined, - fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. - kind: ScriptElementKind.functionElement, - kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), - triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - }, - locs: [renameUserTs(userTs), renameATs(aTs)], - }); - verifyATsConfigOriginalProject(session); - }); - - it("renameLocations -- starting at definition", () => { - const session = makeSampleProjects(); - openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnA", - fileToRename: undefined, - fullDisplayName: '"/a/a".fnA', - kind: ScriptElementKind.functionElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"), - }, - locs: [renameATs(aTs), renameUserTs(userTs)], - }); - verifyATsConfigWhenOpened(session); - }); - - it("renameLocationsFull", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual>(response, [ - renameLocation(userTs, "fnA"), - renameLocation(aTs, "fnA"), - ]); - verifyATsConfigOriginalProject(session); - }); - - it("renameLocations -- target does not exist", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnB", - fileToRename: undefined, - fullDisplayName: '"/b/bin/b".fnB', - kind: ScriptElementKind.functionElement, - kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), - triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), - }, - locs: [ - { - file: bDts.path, - locs: [protocolRenameSpanFromSubstring(bDts.content, "fnB")], - }, - { - file: userTs.path, - locs: [protocolRenameSpanFromSubstring(userTs.content, "fnB")], - }, - ], - }); - verifySingleInferredProject(session); - }); - - it("getEditsForFileRename", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.GetEditsForFileRename, { - oldFilePath: aTs.path, - newFilePath: "/a/aNew.ts", - }); - assert.deepEqual>(response, [ - { - fileName: userTs.path, - textChanges: [ - { ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, - ], - }, - ]); - verifySingleInferredProject(session); - }); - - it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { - const aTs: File = { path: "/a/src/a.ts", content: "" }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: true, - outDir: "./build", - } - }), - }; - const bTs: File = { path: "/b/src/b.ts", content: "" }; - const bTsconfig: File = { - path: "/b/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./build", - }, - include: ["./src"], - references: [{ path: "../a" }], - }), - }; - - const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); - const session = createSession(host); - openFilesForSession([aTs, bTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: aTs.path, - newFilePath: "/a/src/a1.ts", - }); - assert.deepEqual>(response, []); // Should not change anything - }); - }); - - describe("tsserverProjectSystem with tsbuild projects", () => { - function createHost(files: ReadonlyArray, rootNames: ReadonlyArray) { - const host = createServerHost(files); - - // ts build should succeed - const solutionBuilder = tscWatch.createSolutionBuilder(host, rootNames, {}); - solutionBuilder.buildAllProjects(); - assert.equal(host.getOutput().length, 0); - - return host; - } - - describe("with container project", () => { - function getProjectFiles(project: string): [File, File] { - return [ - TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), - ]; - } - - const project = "container"; - const containerLib = getProjectFiles("container/lib"); - const containerExec = getProjectFiles("container/exec"); - const containerCompositeExec = getProjectFiles("container/compositeExec"); - const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); - const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; - - it("does not error on container only project", () => { - const host = createHost(files, [containerConfig.path]); - - // Open external project for the folder - const session = createSession(host); - const service = session.getProjectService(); - service.openExternalProjects([{ - projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), - rootFiles: files.map(f => ({ fileName: f.path })), - options: {} - }]); - checkNumberOfProjects(service, { configuredProjects: 4 }); - files.forEach(f => { - const args: protocol.FileRequestArgs = { - file: f.path, - projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined - }; - const syntaxDiagnostics = session.executeCommandSeq({ - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: args - }).response; - assert.deepEqual(syntaxDiagnostics, []); - const semanticDiagnostics = session.executeCommandSeq({ - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: args - }).response; - assert.deepEqual(semanticDiagnostics, []); - }); - const containerProject = service.configuredProjects.get(containerConfig.path)!; - checkProjectActualFiles(containerProject, [containerConfig.path]); - const optionsDiagnostics = session.executeCommandSeq({ - command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, - arguments: { projectFileName: containerProject.projectName } - }).response; - assert.deepEqual(optionsDiagnostics, []); - }); - - it("can successfully find references with --out options", () => { - const host = createHost(files, [containerConfig.path]); - const session = createSession(host); - openFilesForSession([containerCompositeExec[1]], session); - const service = session.getProjectService(); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { - file: containerCompositeExec[1].path, - ...locationOfMyConst - } - }).response as protocol.RenameResponseBody; - - - const myConstLen = "myConst".length; - const locationOfMyConstInLib = protocolLocationFromSubstring(containerLib[1].content, "myConst"); - assert.deepEqual(response.locs, [ - { file: containerCompositeExec[1].path, locs: [{ start: locationOfMyConst, end: { line: locationOfMyConst.line, offset: locationOfMyConst.offset + myConstLen } }] }, - { file: containerLib[1].path, locs: [{ start: locationOfMyConstInLib, end: { line: locationOfMyConstInLib.line, offset: locationOfMyConstInLib.offset + myConstLen } }] } - ]); - }); - }); - - describe("with main and depedency project", () => { - const projectLocation = "/user/username/projects/myproject"; - const dependecyLocation = `${projectLocation}/dependency`; - const mainLocation = `${projectLocation}/main`; - const dependencyTs: File = { - path: `${dependecyLocation}/FnS.ts`, - content: `export function fn1() { } -export function fn2() { } -export function fn3() { } -export function fn4() { } -export function fn5() { } -` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true } }) - }; - - const mainTs: File = { - path: `${mainLocation}/main.ts`, - content: `import { - fn1, - fn2, - fn3, - fn4, - fn5 -} from '../dependency/fns' - -fn1(); -fn2(); -fn3(); -fn4(); -fn5(); -` - }; - const mainConfig: File = { - path: `${mainLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true }, - references: [{ path: "../dependency" }] - }) - }; - - const randomFile: File = { - path: `${projectLocation}/random/random.ts`, - content: "let a = 10;" - }; - const randomConfig: File = { - path: `${projectLocation}/random/tsconfig.json`, - content: "{}" - }; - const dtsLocation = `${dependecyLocation}/FnS.d.ts`; - const dtsPath = dtsLocation.toLowerCase() as Path; - const dtsMapLocation = `${dtsLocation}.map`; - const dtsMapPath = dtsMapLocation.toLowerCase() as Path; - - const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - - function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos)); - checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase())); - } - - function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: ReadonlyArray, closedInfos: ReadonlyArray, otherWatchedFiles: ReadonlyArray) { - verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path)); - } - - function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { - verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path]); - } - - // Returns request and expected Response, expected response when no map file - interface SessionAction { - reqName: string; - request: Partial; - expectedResponse: Response; - expectedResponseNoMap?: Response; - expectedResponseNoDts?: Response; - } - function gotoDefintinionFromMainTs(fn: number): SessionAction { - const textSpan = usageSpan(fn); - const definition: protocol.FileSpan = { file: dependencyTs.path, ...definitionSpan(fn) }; - const declareSpaceLength = "declare ".length; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To dependency - definitions: [definition], - textSpan - }, - expectedResponseNoMap: { - // To the dts - definitions: [{ file: dtsPath, start: { line: fn, offset: definition.start.offset + declareSpaceLength }, end: { line: fn, offset: definition.end.offset + declareSpaceLength } }], - textSpan - }, - expectedResponseNoDts: { - // To import declaration - definitions: [{ file: mainTs.path, ...importSpan(fn) }], - textSpan - } - }; - } - - function definitionSpan(fn: number): protocol.TextSpan { - return { start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } }; - } - function importSpan(fn: number): protocol.TextSpan { - return { start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } }; - } - function usageSpan(fn: number): protocol.TextSpan { - return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; - } - - function renameFromDependencyTs(fn: number): SessionAction { - const triggerSpan = definitionSpan(fn); - return { - reqName: "rename", - request: { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...triggerSpan.start } - }, - expectedResponse: { - info: { - canRename: true, - fileToRename: undefined, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - triggerSpan - }, - locs: [ - { file: dependencyTs.path, locs: [triggerSpan] } - ] - } - }; - } - - function renameFromDependencyTsWithBothProjectsOpen(fn: number): SessionAction { - const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] - } - ] - }, - // Only dependency result - expectedResponseNoMap: expectedResponse, - expectedResponseNoDts: expectedResponse - }; - } - - // Returns request and expected Response - type SessionActionGetter = (fn: number) => SessionAction; - // Open File, expectedProjectActualFiles, actionGetter, openFileLastLine - interface DocumentPositionMapperVerifier { - openFile: File; - expectedProjectActualFiles: ReadonlyArray; - actionGetter: SessionActionGetter; - openFileLastLine: number; - } - function verifyDocumentPositionMapperUpdates( - mainScenario: string, - verifier: ReadonlyArray, - closedInfos: ReadonlyArray) { - - const openFiles = verifier.map(v => v.openFile); - const expectedProjectActualFiles = verifier.map(v => v.expectedProjectActualFiles); - const actionGetters = verifier.map(v => v.actionGetter); - const openFileLastLines = verifier.map(v => v.openFileLastLine); - - const configFiles = openFiles.map(openFile => `${getDirectoryPath(openFile.path)}/tsconfig.json`); - const openInfos = openFiles.map(f => f.path); - // When usage and dependency are used, dependency config is part of closedInfo so ignore - const otherWatchedFiles = verifier.length > 1 ? [configFiles[0]] : configFiles; - function openTsFile(onHostCreate?: (host: TestServerHost) => void) { - const host = createHost(files, [mainConfig.path]); - if (onHostCreate) { - onHostCreate(host); - } - const session = createSession(host); - openFilesForSession([...openFiles, randomFile], session); - return { host, session }; - } - - function checkProject(session: TestSession, noDts?: true) { - const service = session.getProjectService(); - checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length }); - configFiles.forEach((configFile, index) => { - checkProjectActualFiles( - service.configuredProjects.get(configFile)!, - noDts ? - expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) : - expectedProjectActualFiles[index] - ); - }); - } - - function verifyInfos(session: TestSession, host: TestServerHost) { - verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); - } - - function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: true) { - const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); - verifyInfosWithRandom( - session, - host, - openInfos, - closedInfos.filter(f => f !== dtsMapClosedInfo && (dependencyTsOK || f !== dependencyTs.path)), - dtsMapClosedInfo ? otherWatchedFiles.concat(dtsMapClosedInfo) : otherWatchedFiles - ); - } - - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { - const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); - const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); - verifyInfosWithRandom( - session, - host, - openInfos, - closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)), - // When project actual file contains dts, it needs to be watched - dtsClosedInfo && expectedProjectActualFiles.some(expectedProjectActualFiles => expectedProjectActualFiles.some(f => f.toLowerCase() === dtsPath)) ? - otherWatchedFiles.concat(dtsClosedInfo) : - otherWatchedFiles - ); - } - - function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); - if (notEqual) { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } - else { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } - } - - function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) { - const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = actionGetter(fn); - const { response } = session.executeCommandSeq(request); - return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts }; - } - - function firstAction(session: TestSession) { - actionGetters.forEach(actionGetter => action(actionGetter, 1, session)); - } - - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) { - // action - let isFirst = true; - for (const actionGetter of actionGetters) { - for (let fn = 1; fn <= 5; fn++) { - const result = action(actionGetter, fn, session); - const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); - if (dtsAbsent) { - assert.isUndefined(dtsInfo); - } - else { - assert.isDefined(dtsInfo); - } - verifyAction(result, dtsInfo, isFirst); - isFirst = false; - } - } - } - - function verifyAllFnAction( - session: TestSession, - host: TestServerHost, - firstDocumentPositionMapperNotEquals?: true, - dependencyMap?: server.ScriptInfo, - documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] - ) { - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); - verifyInfos(session, host); - assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath); - if (isFirst) { - if (dependencyMap) { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); - documentPositionMapper = dependencyMap.documentPositionMapper; - } - else { - dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; - documentPositionMapper = dependencyMap.documentPositionMapper; - } - } - else { - verifyDocumentPositionMapper(session, dependencyMap!, documentPositionMapper); - } - }); - return { dependencyMap: dependencyMap!, documentPositionMapper }; - } - - function verifyAllFnActionWithNoMap( - session: TestSession, - host: TestServerHost, - dependencyTsOK?: true - ) { - let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoMapFile(session, host, dependencyTsOK); - assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); - if (isFirst) { - assert.isNotString(dtsInfo!.sourceMapFilePath); - assert.isNotFalse(dtsInfo!.sourceMapFilePath); - assert.isDefined(dtsInfo!.sourceMapFilePath); - sourceMapFilePath = dtsInfo!.sourceMapFilePath; - } - else { - assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); - } - }); - return sourceMapFilePath; - } - - function verifyAllFnActionWithNoDts( - session: TestSession, - host: TestServerHost, - dependencyTsAndMapOk?: true - ) { - // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts }) => { - assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); - verifyInfosWhenNoDtsFile(session, host, dependencyTsAndMapOk); - }, /*dtsAbsent*/ true); - } - - function verifyScenarioWithChangesWorker( - change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals: true | undefined, - timeoutBeforeAction: boolean - ) { - const { host, session } = openTsFile(); - - // Create DocumentPositionMapper - firstAction(session); - const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; - const documentPositionMapper = dependencyMap.documentPositionMapper; - - // change - change(host, session); - if (timeoutBeforeAction) { - host.runQueuedTimeoutCallbacks(); - checkProject(session); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); - } - - // action - verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); - } - - function verifyScenarioWithChanges( - scenarioName: string, - change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals?: true - ) { - describe(scenarioName, () => { - it("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); - }); - - it("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); - }); - }); - } - - function verifyMainScenarioAndScriptInfoCollection(session: TestSession, host: TestServerHost) { - // Main scenario action - const { dependencyMap, documentPositionMapper } = verifyAllFnAction(session, host); - checkProject(session); - verifyInfos(session, host); - - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfos(session, host); - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { - // Main scenario action - verifyAllFnActionWithNoMap(session, host, dependencyTsOKInScenario); - - // Collecting at this point retains dependency.d.ts and map watcher - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWhenNoMapFile(session, host); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost, dependencyTsAndMapOk?: true) { - // Main scenario action - verifyAllFnActionWithNoDts(session, host, dependencyTsAndMapOk); - - // Collecting at this point retains dependency.d.ts and map watcher - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - verifyInfosWhenNoDtsFile(session, host); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyScenarioWhenFileNotPresent( - scenarioName: string, - fileLocation: string, - verifyScenarioAndScriptInfoCollection: (session: TestSession, host: TestServerHost, dependencyTsOk?: true) => void, - noDts?: true - ) { - describe(scenarioName, () => { - it(mainScenario, () => { - const { host, session } = openTsFile(host => host.deleteFile(fileLocation)); - checkProject(session, noDts); - - verifyScenarioAndScriptInfoCollection(session, host); - }); - - it("when file is created", () => { - let fileContents: string | undefined; - const { host, session } = openTsFile(host => { - fileContents = host.readFile(fileLocation); - host.deleteFile(fileLocation); - }); - firstAction(session); - - host.writeFile(fileLocation, fileContents!); - verifyMainScenarioAndScriptInfoCollection(session, host); - }); - - it("when file is deleted", () => { - const { host, session } = openTsFile(); - firstAction(session); - - // The dependency file is deleted when orphan files are collected - host.deleteFile(fileLocation); - verifyScenarioAndScriptInfoCollection(session, host, /*dependencyTsOk*/ true); - }); - }); - } - - it(mainScenario, () => { - const { host, session } = openTsFile(); - checkProject(session); - - verifyMainScenarioAndScriptInfoCollection(session, host); - }); - - // Edit - verifyScenarioWithChanges( - "when usage file changes, document position mapper doesnt change", - (_host, session) => openFiles.forEach( - (openFile, index) => session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { file: openFile.path, line: openFileLastLines[index], offset: 1, endLine: openFileLastLines[index], endOffset: 1, insertString: "const x = 10;" } - }) - ) - ); - - // Edit dts to add new fn - verifyScenarioWithChanges( - "when dependency .d.ts changes, document position mapper doesnt change", - host => host.writeFile( - dtsLocation, - host.readFile(dtsLocation)!.replace( - "//# sourceMappingURL=FnS.d.ts.map", - `export declare function fn6(): void; -//# sourceMappingURL=FnS.d.ts.map` - ) - ) - ); - - // Edit map file to represent added new line - verifyScenarioWithChanges( - "when dependency file's map changes", - host => host.writeFile( - dtsMapLocation, - `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` - ), - /*afterActionDocumentPositionMapperNotEquals*/ true - ); - - verifyScenarioWhenFileNotPresent( - "when map file is not present", - dtsMapLocation, - verifyMainScenarioAndScriptInfoCollectionWithNoMap - ); - - verifyScenarioWhenFileNotPresent( - "when .d.ts file is not present", - dtsLocation, - verifyMainScenarioAndScriptInfoCollectionWithNoDts, - /*noDts*/ true - ); - } - - const usageVerifier: DocumentPositionMapperVerifier = { - openFile: mainTs, - expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], - actionGetter: gotoDefintinionFromMainTs, - openFileLastLine: 14 - }; - describe("from project that uses dependency", () => { - const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "can go to definition correctly", - [usageVerifier], - closedInfos - ); - }); - - const definingVerifier: DocumentPositionMapperVerifier = { - openFile: dependencyTs, - expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path], - actionGetter: renameFromDependencyTs, - openFileLastLine: 6 - }; - describe("from defining project", () => { - const closedInfos = [libFile.path, dtsLocation, dtsMapLocation]; - verifyDocumentPositionMapperUpdates( - "rename locations from dependency", - [definingVerifier], - closedInfos - ); - }); - - describe("when opening depedency and usage project", () => { - const closedInfos = [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path]; - verifyDocumentPositionMapperUpdates( - "goto Definition in usage and rename locations from defining project", - [usageVerifier, { ...definingVerifier, actionGetter: renameFromDependencyTsWithBothProjectsOpen }], - closedInfos - ); - }); - }); - }); - - describe("tsserverProjectSystem duplicate packages", () => { - // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. - it("works with import fixes", () => { - const packageContent = "export const foo: number;"; - const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" }); - const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent }; - const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent }; - const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent }; - const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent }; - - const userContent = 'import("foo");\nfoo'; - const aUser: File = { path: "/a/user.ts", content: userContent }; - const bUser: File = { path: "/b/user.ts", content: userContent }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; - - const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); - const session = createSession(host); - - openFilesForSession([aUser, bUser], session); - - for (const user of [aUser, bUser]) { - const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { - file: user.path, - startLine: 2, - startOffset: 1, - endLine: 2, - endOffset: 4, - errorCodes: [Diagnostics.Cannot_find_name_0.code], - }); - assert.deepEqual | undefined>(response, [ - { - description: `Import 'foo' from module "foo"`, - fixName: "import", - fixId: "fixMissingImport", - fixAllDescription: "Add all missing imports", - changes: [{ - fileName: user.path, - textChanges: [{ - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: 'import { foo } from "foo";\n\n', - }], - }], - commands: undefined, - }, - ]); - } - }); - }); - - describe("tsserverProjectSystem Untitled files", () => { - it("Can convert positions to locations", () => { - const aTs: File = { path: "/proj/a.ts", content: "" }; - const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; - const session = createSession(createServerHost([aTs, tsconfig])); - - openFilesForSession([aTs], session); - - const untitledFile = "untitled:^Untitled-1"; - executeSessionRequestNoResponse(session, protocol.CommandTypes.Open, { - file: untitledFile, - fileContent: `/// \nlet foo = 1;\nfooo/**/`, - scriptKindName: "TS", - projectRootPath: "/proj", - }); - - const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { - file: untitledFile, - startLine: 3, - startOffset: 1, - endLine: 3, - endOffset: 5, - errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], - }); - assert.deepEqual | undefined>(response, [ - { - description: "Change spelling to 'foo'", - fixAllDescription: "Fix all detected spelling errors", - fixId: "fixSpelling", - fixName: "spelling", - changes: [{ - fileName: untitledFile, - textChanges: [{ - start: { line: 3, offset: 1 }, - end: { line: 3, offset: 5 }, - newText: "foo", - }], - }], - commands: undefined, - }, - ]); - }); - }); - - describe("tsserverProjectSystem with metadata in response", () => { - const metadata = "Extra Info"; - function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { - const output = host.getOutput().map(mapOutputToJson); - assert.deepEqual(output, [expectedResponse]); - host.clearOutput(); - } - - function verifyCommandWithMetadata(session: TestSession, host: TestServerHost, command: Partial, expectedResponseBody: U) { - command.seq = session.getSeq(); - command.type = "request"; - session.onMessage(JSON.stringify(command)); - verifyOutput(host, expectedResponseBody ? - { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } : - { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." } - ); - } - - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { plugins: [{ name: "myplugin" }] } - }) - }; - function createHostWithPlugin(files: ReadonlyArray) { - const host = createServerHost(files); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - const proxy = Harness.LanguageService.makeDefaultProxy(info); - proxy.getCompletionsAtPosition = (filename, position, options) => { - const result = info.languageService.getCompletionsAtPosition(filename, position, options); - if (result) { - result.metadata = metadata; - } - return result; - }; - return proxy; - } - }), - error: undefined - }; - }; - return host; - } - - describe("With completion requests", () => { - const completionRequestArgs: protocol.CompletionsRequestArgs = { - file: aTs.path, - line: 1, - offset: aTs.content.indexOf("this.") + 1 + "this.".length - }; - const expectedCompletionEntries: ReadonlyArray = [ - { name: "foo", kind: ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: "0" }, - { name: "prop", kind: ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: "0" } - ]; - - it("can pass through metadata when the command returns array", () => { - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata>(session, host, { - command: protocol.CommandTypes.Completions, - arguments: completionRequestArgs - }, expectedCompletionEntries); - }); - - it("can pass through metadata when the command returns object", () => { - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.CompletionInfo, - arguments: completionRequestArgs - }, { - isGlobalCompletion: false, - isMemberCompletion: true, - isNewIdentifierLocation: false, - entries: expectedCompletionEntries - }); - }); - - it("returns undefined correctly", () => { - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.Completions, - arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 } - }, /*expectedResponseBody*/ undefined); - }); - }); - }); - - function makeReferenceItem(file: File, isDefinition: boolean, text: string, lineText: string, options?: SpanFromSubstringOptions): protocol.ReferencesResponseItem { - return { - ...protocolFileSpanFromSubstring(file, text, options), - isDefinition, - isWriteAccess: isDefinition, - lineText, - }; - } - - function makeReferenceEntry(file: File, isDefinition: boolean, text: string, options?: SpanFromSubstringOptions): ReferenceEntry { - return { - ...documentSpanFromSubstring(file, text, options), - isDefinition, - isWriteAccess: isDefinition, - isInString: undefined, - }; - } - - function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: ReadonlyArray): void { - openFilesForSession([file], session); - const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); - const program = project.getCurrentProgram()!; - const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); - closeFilesForSession([file], session); - - Debug.assert(!output.emitSkipped); - assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); - } } From db4d9b3050fec94f4c2687fc223ada8e24b2ab5f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 20 Dec 2018 13:20:53 -0800 Subject: [PATCH 091/120] Add unittests:: on all unittests describe blocks for easy run --- src/testRunner/unittests/asserts.ts | 2 +- src/testRunner/unittests/base64.ts | 2 +- src/testRunner/unittests/builder.ts | 2 +- src/testRunner/unittests/compilerCore.ts | 2 +- .../unittests/config/commandLineParsing.ts | 4 +-- .../config/configurationExtension.ts | 2 +- .../config/convertCompilerOptionsFromJson.ts | 2 +- .../config/convertTypeAcquisitionFromJson.ts | 2 +- .../unittests/config/initializeTSConfig.ts | 2 +- src/testRunner/unittests/config/matchFiles.ts | 2 +- .../unittests/config/projectReferences.ts | 12 ++++---- src/testRunner/unittests/config/showConfig.ts | 2 +- .../unittests/config/tsconfigParsing.ts | 2 +- src/testRunner/unittests/convertToBase64.ts | 2 +- src/testRunner/unittests/customTransforms.ts | 4 +-- .../unittests/evaluation/asyncArrow.ts | 2 +- .../unittests/evaluation/asyncGenerator.ts | 2 +- .../unittests/evaluation/forAwaitOf.ts | 2 +- src/testRunner/unittests/factory.ts | 2 +- src/testRunner/unittests/incrementalParser.ts | 2 +- src/testRunner/unittests/jsDocParsing.ts | 2 +- src/testRunner/unittests/moduleResolution.ts | 14 ++++----- src/testRunner/unittests/parsePseudoBigInt.ts | 4 +-- src/testRunner/unittests/paths.ts | 4 +-- src/testRunner/unittests/printer.ts | 2 +- src/testRunner/unittests/programApi.ts | 4 +-- .../unittests/reuseProgramStructure.ts | 6 ++-- src/testRunner/unittests/semver.ts | 4 +-- .../cancellableLanguageServiceOperations.ts | 2 +- .../unittests/services/colorization.ts | 2 +- .../services/convertToAsyncFunction.ts | 2 +- .../unittests/services/documentRegistry.ts | 2 +- .../unittests/services/extract/constants.ts | 2 +- .../unittests/services/extract/functions.ts | 2 +- .../unittests/services/extract/ranges.ts | 2 +- .../services/extract/symbolWalker.ts | 2 +- .../unittests/services/hostNewLineSupport.ts | 2 +- .../unittests/services/languageService.ts | 2 +- .../unittests/services/organizeImports.ts | 2 +- .../unittests/services/patternMatcher.ts | 2 +- .../unittests/services/preProcessFile.ts | 2 +- .../unittests/services/textChanges.ts | 2 +- .../unittests/services/transpile.ts | 2 +- src/testRunner/unittests/transform.ts | 2 +- src/testRunner/unittests/tsbuild.ts | 30 +++++++++---------- src/testRunner/unittests/tsbuildWatchMode.ts | 2 +- src/testRunner/unittests/tscWatch/emit.ts | 8 ++--- .../unittests/tscWatch/resolutionCache.ts | 4 +-- src/testRunner/unittests/tscWatch/watchApi.ts | 2 +- .../unittests/tscWatch/watchEnvironment.ts | 2 +- .../tsserver/cachingFileSystemInformation.ts | 2 +- .../unittests/tsserver/cancellationToken.ts | 2 +- .../unittests/tsserver/compileOnSave.ts | 4 +-- .../unittests/tsserver/completions.ts | 2 +- .../unittests/tsserver/configFileSearch.ts | 2 +- .../unittests/tsserver/declarationFileMaps.ts | 2 +- .../unittests/tsserver/documentRegistry.ts | 2 +- .../unittests/tsserver/duplicatePackages.ts | 2 +- .../tsserver/events/largeFileReferenced.ts | 2 +- .../tsserver/events/projectLoading.ts | 2 +- .../events/projectUpdatedInBackground.ts | 2 +- .../unittests/tsserver/externalProjects.ts | 2 +- .../forceConsistentCasingInFileNames.ts | 2 +- .../unittests/tsserver/formatSettings.ts | 2 +- .../tsserver/getEditsForFileRename.ts | 2 +- .../unittests/tsserver/importHelpers.ts | 2 +- .../unittests/tsserver/inferredProjects.ts | 2 +- .../unittests/tsserver/languageService.ts | 2 +- .../tsserver/maxNodeModuleJsDepth.ts | 2 +- .../unittests/tsserver/metadataInResponse.ts | 2 +- src/testRunner/unittests/tsserver/navTo.ts | 2 +- .../unittests/tsserver/occurences.ts | 2 +- src/testRunner/unittests/tsserver/openFile.ts | 2 +- .../unittests/tsserver/projectErrors.ts | 12 ++++---- .../unittests/tsserver/projectReferences.ts | 2 +- .../unittests/tsserver/refactors.ts | 2 +- src/testRunner/unittests/tsserver/reload.ts | 2 +- src/testRunner/unittests/tsserver/rename.ts | 2 +- .../unittests/tsserver/resolutionCache.ts | 10 +++---- src/testRunner/unittests/tsserver/session.ts | 10 +++---- .../unittests/tsserver/skipLibCheck.ts | 2 +- src/testRunner/unittests/tsserver/symLinks.ts | 2 +- .../unittests/tsserver/syntaxOperations.ts | 2 +- .../unittests/tsserver/telemetry.ts | 2 +- .../unittests/tsserver/textStorage.ts | 2 +- .../unittests/tsserver/typeAquisition.ts | 4 +-- .../tsserver/typeReferenceDirectives.ts | 2 +- .../unittests/tsserver/typingsInstaller.ts | 20 ++++++------- .../unittests/tsserver/untitledFiles.ts | 2 +- .../unittests/tsserver/versionCache.ts | 6 ++-- .../unittests/tsserver/watchEnvironment.ts | 4 +-- 91 files changed, 155 insertions(+), 155 deletions(-) diff --git a/src/testRunner/unittests/asserts.ts b/src/testRunner/unittests/asserts.ts index 8d5138085a8..c54173e2f90 100644 --- a/src/testRunner/unittests/asserts.ts +++ b/src/testRunner/unittests/asserts.ts @@ -1,5 +1,5 @@ namespace ts { - describe("assert", () => { + describe("unittests:: assert", () => { it("deepEqual", () => { assert.throws(() => assert.deepEqual(createNodeArray([createIdentifier("A")]), createNodeArray([createIdentifier("B")]))); assert.throws(() => assert.deepEqual(createNodeArray([], /*hasTrailingComma*/ true), createNodeArray([], /*hasTrailingComma*/ false))); diff --git a/src/testRunner/unittests/base64.ts b/src/testRunner/unittests/base64.ts index 11c2623df65..1dc51a8fbf5 100644 --- a/src/testRunner/unittests/base64.ts +++ b/src/testRunner/unittests/base64.ts @@ -1,5 +1,5 @@ namespace ts { - describe("base64", () => { + describe("unittests:: base64", () => { describe("base64decode", () => { it("can decode input strings correctly without needing a host implementation", () => { const tests = [ diff --git a/src/testRunner/unittests/builder.ts b/src/testRunner/unittests/builder.ts index 4a163d6b56b..709222a4dff 100644 --- a/src/testRunner/unittests/builder.ts +++ b/src/testRunner/unittests/builder.ts @@ -1,5 +1,5 @@ namespace ts { - describe("builder", () => { + describe("unittests:: builder", () => { it("emits dependent files", () => { const files: NamedSourceText[] = [ { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, diff --git a/src/testRunner/unittests/compilerCore.ts b/src/testRunner/unittests/compilerCore.ts index 27f5cdc0887..4c3918c3149 100644 --- a/src/testRunner/unittests/compilerCore.ts +++ b/src/testRunner/unittests/compilerCore.ts @@ -1,5 +1,5 @@ namespace ts { - describe("compilerCore", () => { + describe("unittests:: compilerCore", () => { describe("equalOwnProperties", () => { it("correctly equates objects", () => { assert.isTrue(equalOwnProperties({}, {})); diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index be7f6dca3c5..25fa3a45050 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("config:: commandLineParsing:: parseCommandLine", () => { + describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => { function assertParseResult(commandLine: string[], expectedParsedCommandLine: ParsedCommandLine) { const parsed = parseCommandLine(commandLine); @@ -367,7 +367,7 @@ namespace ts { }); }); - describe("config:: commandLineParsing:: parseBuildOptions", () => { + describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => { function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) { const parsed = parseBuildCommand(commandLine); const parsedBuildOptions = JSON.stringify(parsed.buildOptions); diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index 0e7cd0821c4..25e975c0fb2 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -208,7 +208,7 @@ namespace ts { } } - describe("config:: configurationExtension", () => { + describe("unittests:: config:: configurationExtension", () => { forEach<[string, string, fakes.ParseConfigHost], void>([ ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] diff --git a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts index 4a3a42ce216..318bc0ceb26 100644 --- a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts +++ b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts @@ -1,5 +1,5 @@ namespace ts { - describe("config:: convertCompilerOptionsFromJson", () => { + describe("unittests:: config:: convertCompilerOptionsFromJson", () => { const formatDiagnosticHost: FormatDiagnosticsHost = { getCurrentDirectory: () => "/apath/", getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true), diff --git a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts index 0998b0b0d7a..890cad4ade6 100644 --- a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts +++ b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts @@ -1,6 +1,6 @@ namespace ts { interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; } - describe("config:: convertTypeAcquisitionFromJson", () => { + describe("unittests:: config:: convertTypeAcquisitionFromJson", () => { function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { assertTypeAcquisitionWithJson(json, configFileName, expectedResult); assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); diff --git a/src/testRunner/unittests/config/initializeTSConfig.ts b/src/testRunner/unittests/config/initializeTSConfig.ts index 09d80c7f5c2..f7fb7d2a439 100644 --- a/src/testRunner/unittests/config/initializeTSConfig.ts +++ b/src/testRunner/unittests/config/initializeTSConfig.ts @@ -1,5 +1,5 @@ namespace ts { - describe("config:: initTSConfig", () => { + describe("unittests:: config:: initTSConfig", () => { function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) { describe(name, () => { const commandLine = parseCommandLine(commandLinesArgs); diff --git a/src/testRunner/unittests/config/matchFiles.ts b/src/testRunner/unittests/config/matchFiles.ts index 9155cb26a1c..a30fa468443 100644 --- a/src/testRunner/unittests/config/matchFiles.ts +++ b/src/testRunner/unittests/config/matchFiles.ts @@ -143,7 +143,7 @@ namespace ts { return createFileDiagnostic(file, start, length, diagnosticMessage, arg0); } - describe("config:: matchFiles", () => { + describe("unittests:: config:: matchFiles", () => { it("with defaults", () => { const json = {}; const expected: ParsedCommandLine = { diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index be8e0be6206..266b016c681 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -96,7 +96,7 @@ namespace ts { checkResult(prog, host); } - describe("config:: project-references meta check", () => { + describe("unittests:: config:: project-references meta check", () => { it("default setup was created correctly", () => { const spec: TestSpecification = { "/primary": { @@ -118,7 +118,7 @@ namespace ts { /** * Validate that we enforce the basic settings constraints for referenced projects */ - describe("config:: project-references constraint checking for settings", () => { + describe("unittests:: config:: project-references constraint checking for settings", () => { it("errors when declaration = false", () => { const spec: TestSpecification = { "/primary": { @@ -248,7 +248,7 @@ namespace ts { /** * Path mapping behavior */ - describe("config:: project-references path mapping", () => { + describe("unittests:: config:: project-references path mapping", () => { it("redirects to the output .d.ts file", () => { const spec: TestSpecification = { "/alpha": { @@ -268,7 +268,7 @@ namespace ts { }); }); - describe("config:: project-references nice-behavior", () => { + describe("unittests:: config:: project-references nice-behavior", () => { it("issues a nice error when the input file is missing", () => { const spec: TestSpecification = { "/alpha": { @@ -289,7 +289,7 @@ namespace ts { /** * 'composite' behavior */ - describe("config:: project-references behavior changes under composite: true", () => { + describe("unittests:: config:: project-references behavior changes under composite: true", () => { it("doesn't infer the rootDir from source paths", () => { const spec: TestSpecification = { "/alpha": { @@ -308,7 +308,7 @@ namespace ts { }); }); - describe("config:: project-references errors when a file in a composite project occurs outside the root", () => { + describe("unittests:: config:: project-references errors when a file in a composite project occurs outside the root", () => { it("Errors when a file is outside the rootdir", () => { const spec: TestSpecification = { "/alpha": { diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index f86b37970f5..afe8f878d27 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -1,5 +1,5 @@ namespace ts { - describe("config:: showConfig", () => { + describe("unittests:: config:: showConfig", () => { function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) { describe(name, () => { const outputFileName = `showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`; diff --git a/src/testRunner/unittests/config/tsconfigParsing.ts b/src/testRunner/unittests/config/tsconfigParsing.ts index cc617a0d151..638a394a34e 100644 --- a/src/testRunner/unittests/config/tsconfigParsing.ts +++ b/src/testRunner/unittests/config/tsconfigParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("config:: tsconfigParsing:: parseConfigFileTextToJson", () => { + describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => { function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); diff --git a/src/testRunner/unittests/convertToBase64.ts b/src/testRunner/unittests/convertToBase64.ts index 56c95622977..37e38da8da7 100644 --- a/src/testRunner/unittests/convertToBase64.ts +++ b/src/testRunner/unittests/convertToBase64.ts @@ -1,5 +1,5 @@ namespace ts { - describe("convertToBase64", () => { + describe("unittests:: convertToBase64", () => { function runTest(input: string): void { const actual = convertToBase64(input); const expected = sys.base64encode!(input); diff --git a/src/testRunner/unittests/customTransforms.ts b/src/testRunner/unittests/customTransforms.ts index c4ff21d0a25..304c0a55c59 100644 --- a/src/testRunner/unittests/customTransforms.ts +++ b/src/testRunner/unittests/customTransforms.ts @@ -1,5 +1,5 @@ namespace ts { - describe("customTransforms", () => { + describe("unittests:: customTransforms", () => { function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) { it(name, () => { const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); @@ -97,4 +97,4 @@ namespace ts { experimentalDecorators: true }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/evaluation/asyncArrow.ts b/src/testRunner/unittests/evaluation/asyncArrow.ts index 01959ca150f..04a7af613fa 100644 --- a/src/testRunner/unittests/evaluation/asyncArrow.ts +++ b/src/testRunner/unittests/evaluation/asyncArrow.ts @@ -1,4 +1,4 @@ -describe("evaluation:: asyncArrowEvaluation", () => { +describe("unittests:: evaluation:: asyncArrowEvaluation", () => { // https://github.com/Microsoft/TypeScript/issues/24722 it("this capture (es5)", async () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/asyncGenerator.ts b/src/testRunner/unittests/evaluation/asyncGenerator.ts index cb630d28724..8ca531f0759 100644 --- a/src/testRunner/unittests/evaluation/asyncGenerator.ts +++ b/src/testRunner/unittests/evaluation/asyncGenerator.ts @@ -1,4 +1,4 @@ -describe("evaluation:: asyncGeneratorEvaluation", () => { +describe("unittests:: evaluation:: asyncGeneratorEvaluation", () => { it("return (es5)", async () => { const result = evaluator.evaluateTypeScript(` async function * g() { diff --git a/src/testRunner/unittests/evaluation/forAwaitOf.ts b/src/testRunner/unittests/evaluation/forAwaitOf.ts index 7e7ee41e2f1..c7be018cbc9 100644 --- a/src/testRunner/unittests/evaluation/forAwaitOf.ts +++ b/src/testRunner/unittests/evaluation/forAwaitOf.ts @@ -1,4 +1,4 @@ -describe("evaluation:: forAwaitOfEvaluation", () => { +describe("unittests:: evaluation:: forAwaitOfEvaluation", () => { it("sync (es5)", async () => { const result = evaluator.evaluateTypeScript(` let i = 0; diff --git a/src/testRunner/unittests/factory.ts b/src/testRunner/unittests/factory.ts index 402c399d728..cc760059c19 100644 --- a/src/testRunner/unittests/factory.ts +++ b/src/testRunner/unittests/factory.ts @@ -1,5 +1,5 @@ namespace ts { - describe("FactoryAPI", () => { + describe("unittests:: FactoryAPI", () => { function assertSyntaxKind(node: Node, expected: SyntaxKind) { assert.strictEqual(node.kind, expected, `Actual: ${Debug.showSyntaxKind(node)} Expected: ${(ts as any).SyntaxKind[expected]}`); } diff --git a/src/testRunner/unittests/incrementalParser.ts b/src/testRunner/unittests/incrementalParser.ts index fb3408b1fc3..8af44494803 100644 --- a/src/testRunner/unittests/incrementalParser.ts +++ b/src/testRunner/unittests/incrementalParser.ts @@ -120,7 +120,7 @@ namespace ts { } } - describe("Incremental", () => { + describe("unittests:: Incremental Parser", () => { it("Inserting into method", () => { const source = "class C {\r\n" + " public foo1() { }\r\n" + diff --git a/src/testRunner/unittests/jsDocParsing.ts b/src/testRunner/unittests/jsDocParsing.ts index 931785aa94d..4171a330f32 100644 --- a/src/testRunner/unittests/jsDocParsing.ts +++ b/src/testRunner/unittests/jsDocParsing.ts @@ -1,5 +1,5 @@ namespace ts { - describe("JSDocParsing", () => { + describe("unittests:: JSDocParsing", () => { describe("TypeExpressions", () => { function parsesCorrectly(name: string, content: string) { it(name, () => { diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index 28f25437149..69560e5449c 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -80,7 +80,7 @@ namespace ts { } } - describe("moduleResolution:: Node module resolution - relative paths", () => { + describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => { function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { for (const ext of supportedTSExtensions) { @@ -200,7 +200,7 @@ namespace ts { }); }); - describe("moduleResolution:: Node module resolution - non-relative paths", () => { + describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => { it("computes correct commonPrefix for moduleName cache", () => { const resolutionCache = createModuleResolutionCache("/", (f) => f); let cache = resolutionCache.getOrCreateCacheForModuleName("a"); @@ -457,7 +457,7 @@ namespace ts { }); }); - describe("moduleResolution:: Relative imports", () => { + describe("unittests:: moduleResolution:: Relative imports", () => { function test(files: Map, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { const options: CompilerOptions = { module: ModuleKind.CommonJS }; const host: CompilerHost = { @@ -530,7 +530,7 @@ export = C; }); }); - describe("moduleResolution:: Files with different casing", () => { + describe("unittests:: moduleResolution:: Files with different casing", () => { let library: SourceFile; function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -651,7 +651,7 @@ import b = require("./moduleB"); }); }); - describe("moduleResolution:: baseUrl augmented module resolution", () => { + describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => { it("module resolution without path mappings/rootDirs", () => { test(/*hasDirectoryExists*/ false); @@ -1098,7 +1098,7 @@ import b = require("./moduleB"); }); }); - describe("moduleResolution:: ModuleResolutionHost.directoryExists", () => { + describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => { it("No 'fileExists' calls if containing directory is missing", () => { const host: ModuleResolutionHost = { readFile: notImplemented, @@ -1111,7 +1111,7 @@ import b = require("./moduleB"); }); }); - describe("moduleResolution:: Type reference directive resolution: ", () => { + describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => { function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles)); const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host); diff --git a/src/testRunner/unittests/parsePseudoBigInt.ts b/src/testRunner/unittests/parsePseudoBigInt.ts index 0ffbee6345e..db1a841dc2b 100644 --- a/src/testRunner/unittests/parsePseudoBigInt.ts +++ b/src/testRunner/unittests/parsePseudoBigInt.ts @@ -1,5 +1,5 @@ namespace ts { - describe("BigInt literal base conversions", () => { + describe("unittests:: BigInt literal base conversions", () => { describe("parsePseudoBigInt", () => { const testNumbers: number[] = []; for (let i = 0; i < 1e3; i++) testNumbers.push(i); @@ -68,4 +68,4 @@ namespace ts { }); }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/paths.ts b/src/testRunner/unittests/paths.ts index 0b28bedc929..b1555ab2174 100644 --- a/src/testRunner/unittests/paths.ts +++ b/src/testRunner/unittests/paths.ts @@ -1,4 +1,4 @@ -describe("core paths", () => { +describe("unittests:: core paths", () => { it("normalizeSlashes", () => { assert.strictEqual(ts.normalizeSlashes("a"), "a"); assert.strictEqual(ts.normalizeSlashes("a/b"), "a/b"); @@ -289,4 +289,4 @@ describe("core paths", () => { assert.strictEqual(ts.getRelativePathFromDirectory("file:///a/b/c", "file:///a/b", /*ignoreCase*/ false), ".."); assert.strictEqual(ts.getRelativePathFromDirectory("file:///c:", "file:///d:", /*ignoreCase*/ false), "file:///d:/"); }); -}); \ No newline at end of file +}); diff --git a/src/testRunner/unittests/printer.ts b/src/testRunner/unittests/printer.ts index 2e69cf60876..8e217aa6a3a 100644 --- a/src/testRunner/unittests/printer.ts +++ b/src/testRunner/unittests/printer.ts @@ -1,5 +1,5 @@ namespace ts { - describe("PrinterAPI", () => { + describe("unittests:: PrinterAPI", () => { function makePrintsCorrectly(prefix: string) { return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) { it(name, () => { diff --git a/src/testRunner/unittests/programApi.ts b/src/testRunner/unittests/programApi.ts index 8fa107513fe..01ef6610dba 100644 --- a/src/testRunner/unittests/programApi.ts +++ b/src/testRunner/unittests/programApi.ts @@ -11,7 +11,7 @@ namespace ts { assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); } - describe("Program.getMissingFilePaths", () => { + describe("unittests:: Program.getMissingFilePaths", () => { const options: CompilerOptions = { noLib: true, @@ -127,7 +127,7 @@ namespace ts { }); }); - describe("Program.isSourceFileFromExternalLibrary", () => { + describe("unittests:: Program.isSourceFileFromExternalLibrary", () => { it("works on redirect files", () => { // In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'. const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";'); diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 63b670912d8..c9ea0e77fec 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -210,7 +210,7 @@ namespace ts { checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); } - describe("Reuse program structure:: General", () => { + describe("unittests:: Reuse program structure:: General", () => { const target = ScriptTarget.Latest; const files: NamedSourceText[] = [ { @@ -895,7 +895,7 @@ namespace ts { }); }); - describe("Reuse program structure:: host is optional", () => { + describe("unittests:: Reuse program structure:: host is optional", () => { it("should work if host is not provided", () => { createProgram([], {}); }); @@ -905,7 +905,7 @@ namespace ts { import createTestSystem = TestFSWithWatch.createWatchedSystem; import libFile = TestFSWithWatch.libFile; - describe("Reuse program structure:: isProgramUptoDate should return true when there is no change in compiler options and", () => { + describe("unittests:: Reuse program structure:: isProgramUptoDate should return true when there is no change in compiler options and", () => { function verifyProgramIsUptoDate( program: Program, newRootFileNames: string[], diff --git a/src/testRunner/unittests/semver.ts b/src/testRunner/unittests/semver.ts index 357a24307ca..079bdc3d512 100644 --- a/src/testRunner/unittests/semver.ts +++ b/src/testRunner/unittests/semver.ts @@ -1,6 +1,6 @@ namespace ts { import theory = utils.theory; - describe("semver", () => { + describe("unittests:: semver", () => { describe("Version", () => { function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { assert.strictEqual(version.major, major); @@ -225,4 +225,4 @@ namespace ts { ]); }); }); -} \ No newline at end of file +} diff --git a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts index 1d7e12d9825..9868d628d0c 100644 --- a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts +++ b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: cancellableLanguageServiceOperations", () => { + describe("unittests:: services:: cancellableLanguageServiceOperations", () => { const file = ` function foo(): void; function foo(x: T): T; diff --git a/src/testRunner/unittests/services/colorization.ts b/src/testRunner/unittests/services/colorization.ts index 54ea98dd563..71ba8bad394 100644 --- a/src/testRunner/unittests/services/colorization.ts +++ b/src/testRunner/unittests/services/colorization.ts @@ -6,7 +6,7 @@ interface ClassificationEntry { position?: number; } -describe("services:: Colorization", () => { +describe("unittests:: services:: Colorization", () => { // Use the shim adapter to ensure test coverage of the shim layer for the classifier const languageServiceAdapter = new Harness.LanguageService.ShimLanguageServiceAdapter(/*preprocessToResolve*/ false); const classifier = languageServiceAdapter.getClassifier(); diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index e8d857f2ee0..b89e684183b 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -343,7 +343,7 @@ interface Array {}` } } - describe("services:: convertToAsyncFunctions", () => { + describe("unittests:: services:: convertToAsyncFunctions", () => { _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); diff --git a/src/testRunner/unittests/services/documentRegistry.ts b/src/testRunner/unittests/services/documentRegistry.ts index 96f2aaff76b..e7c80486e97 100644 --- a/src/testRunner/unittests/services/documentRegistry.ts +++ b/src/testRunner/unittests/services/documentRegistry.ts @@ -1,4 +1,4 @@ -describe("services:: DocumentRegistry", () => { +describe("unittests:: services:: DocumentRegistry", () => { it("documents are shared between projects", () => { const documentRegistry = ts.createDocumentRegistry(); const defaultCompilerOptions = ts.getDefaultCompilerOptions(); diff --git a/src/testRunner/unittests/services/extract/constants.ts b/src/testRunner/unittests/services/extract/constants.ts index 1ca5a719a82..d9cd5010d5b 100644 --- a/src/testRunner/unittests/services/extract/constants.ts +++ b/src/testRunner/unittests/services/extract/constants.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: extract:: extractConstants", () => { + describe("unittests:: services:: extract:: extractConstants", () => { testExtractConstant("extractConstant_TopLevel", `let x = [#|1|];`); diff --git a/src/testRunner/unittests/services/extract/functions.ts b/src/testRunner/unittests/services/extract/functions.ts index 21c5e8e9001..d872ad81a63 100644 --- a/src/testRunner/unittests/services/extract/functions.ts +++ b/src/testRunner/unittests/services/extract/functions.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: extract:: extractFunctions", () => { + describe("unittests:: services:: extract:: extractFunctions", () => { testExtractFunction("extractFunction1", `namespace A { let x = 1; diff --git a/src/testRunner/unittests/services/extract/ranges.ts b/src/testRunner/unittests/services/extract/ranges.ts index dc62618e0c6..265dd33e60a 100644 --- a/src/testRunner/unittests/services/extract/ranges.ts +++ b/src/testRunner/unittests/services/extract/ranges.ts @@ -42,7 +42,7 @@ namespace ts { } } - describe("services:: extract:: extractRanges", () => { + describe("unittests:: services:: extract:: extractRanges", () => { it("get extract range from selection", () => { testExtractRange(` [#| diff --git a/src/testRunner/unittests/services/extract/symbolWalker.ts b/src/testRunner/unittests/services/extract/symbolWalker.ts index 9d323a7544a..58d9dcb577f 100644 --- a/src/testRunner/unittests/services/extract/symbolWalker.ts +++ b/src/testRunner/unittests/services/extract/symbolWalker.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: extract:: Symbol Walker", () => { + describe("unittests:: services:: extract:: Symbol Walker", () => { function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) { it(description, () => { const result = Harness.Compiler.compileFiles([{ diff --git a/src/testRunner/unittests/services/hostNewLineSupport.ts b/src/testRunner/unittests/services/hostNewLineSupport.ts index 2f00c7b08e5..cafe4813431 100644 --- a/src/testRunner/unittests/services/hostNewLineSupport.ts +++ b/src/testRunner/unittests/services/hostNewLineSupport.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: hostNewLineSupport", () => { + describe("unittests:: services:: hostNewLineSupport", () => { function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) { function snapFor(path: string): IScriptSnapshot | undefined { if (path === "lib.d.ts") { diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index 332d59ca68a..d646f32383f 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: languageService", () => { + describe("unittests:: services:: languageService", () => { const files: {[index: string]: string} = { "foo.ts": `import Vue from "./vue"; import Component from "./vue-class-component"; diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index a45c7548394..1421780a4b4 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: Organize imports", () => { + describe("unittests:: services:: Organize imports", () => { describe("Sort imports", () => { it("Sort - non-relative vs non-relative", () => { assertSortsBefore( diff --git a/src/testRunner/unittests/services/patternMatcher.ts b/src/testRunner/unittests/services/patternMatcher.ts index 304a9225e0d..9f3cbf4b6d7 100644 --- a/src/testRunner/unittests/services/patternMatcher.ts +++ b/src/testRunner/unittests/services/patternMatcher.ts @@ -1,4 +1,4 @@ -describe("services:: PatternMatcher", () => { +describe("unittests:: services:: PatternMatcher", () => { describe("BreakIntoCharacterSpans", () => { it("EmptyIdentifier", () => { verifyBreakIntoCharacterSpans(""); diff --git a/src/testRunner/unittests/services/preProcessFile.ts b/src/testRunner/unittests/services/preProcessFile.ts index caf412d6403..3e9d16672db 100644 --- a/src/testRunner/unittests/services/preProcessFile.ts +++ b/src/testRunner/unittests/services/preProcessFile.ts @@ -1,4 +1,4 @@ -describe("services:: PreProcessFile:", () => { +describe("unittests:: services:: PreProcessFile:", () => { function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void { const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports); diff --git a/src/testRunner/unittests/services/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts index fa5e723419a..de92efafbeb 100644 --- a/src/testRunner/unittests/services/textChanges.ts +++ b/src/testRunner/unittests/services/textChanges.ts @@ -2,7 +2,7 @@ // tslint:disable trim-trailing-whitespace namespace ts { - describe("services:: textChanges", () => { + describe("unittests:: services:: textChanges", () => { function findChild(name: string, n: Node) { return find(n)!; diff --git a/src/testRunner/unittests/services/transpile.ts b/src/testRunner/unittests/services/transpile.ts index 533f07884f4..d197d6db621 100644 --- a/src/testRunner/unittests/services/transpile.ts +++ b/src/testRunner/unittests/services/transpile.ts @@ -1,5 +1,5 @@ namespace ts { - describe("services:: Transpile", () => { + describe("unittests:: services:: Transpile", () => { interface TranspileTestSettings { options?: TranspileOptions; diff --git a/src/testRunner/unittests/transform.ts b/src/testRunner/unittests/transform.ts index f219fb6c683..98c29025d5f 100644 --- a/src/testRunner/unittests/transform.ts +++ b/src/testRunner/unittests/transform.ts @@ -1,5 +1,5 @@ namespace ts { - describe("TransformAPI", () => { + describe("unittests:: TransformAPI", () => { function replaceUndefinedWithVoid0(context: TransformationContext) { const previousOnSubstituteNode = context.onSubstituteNode; context.enableSubstitution(SyntaxKind.Identifier); diff --git a/src/testRunner/unittests/tsbuild.ts b/src/testRunner/unittests/tsbuild.ts index 72b1121e861..bf628e10598 100644 --- a/src/testRunner/unittests/tsbuild.ts +++ b/src/testRunner/unittests/tsbuild.ts @@ -8,7 +8,7 @@ namespace ts { "/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", "/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"]; - describe("tsbuild - sanity check of clean build of 'sample1' project", () => { + describe("unittests:: tsbuild - sanity check of clean build of 'sample1' project", () => { it("can build the sample project 'sample1' without error", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -61,7 +61,7 @@ namespace ts { }); }); - describe("tsbuild - dry builds", () => { + describe("unittests:: tsbuild - dry builds", () => { it("doesn't write any files in a dry build", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -90,7 +90,7 @@ namespace ts { }); }); - describe("tsbuild - clean builds", () => { + describe("unittests:: tsbuild - clean builds", () => { it("removes all files it built", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -111,7 +111,7 @@ namespace ts { }); }); - describe("tsbuild - force builds", () => { + describe("unittests:: tsbuild - force builds", () => { it("always builds under --force", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -137,7 +137,7 @@ namespace ts { }); }); - describe("tsbuild - can detect when and what to rebuild", () => { + describe("unittests:: tsbuild - can detect when and what to rebuild", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true }); @@ -200,7 +200,7 @@ namespace ts { }); }); - describe("tsbuild - downstream-blocked compilations", () => { + describe("unittests:: tsbuild - downstream-blocked compilations", () => { it("won't build downstream projects if upstream projects have errors", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -222,7 +222,7 @@ namespace ts { }); }); - describe("tsbuild - project invalidation", () => { + describe("unittests:: tsbuild - project invalidation", () => { it("invalidates projects correctly", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -270,7 +270,7 @@ export class cNew {}`); }); }); - describe("tsbuild - with resolveJsonModule option", () => { + describe("unittests:: tsbuild - with resolveJsonModule option", () => { const projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite"); const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"]; @@ -320,7 +320,7 @@ export default hello.hello`); }); }); - describe("tsbuild - lists files", () => { + describe("unittests:: tsbuild - lists files", () => { it("listFiles", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -369,7 +369,7 @@ export default hello.hello`); }); }); - describe("tsbuild - with rootDir of project reference in parentDirectory", () => { + describe("unittests:: tsbuild - with rootDir of project reference in parentDirectory", () => { const projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent"); const allExpectedOutputs = [ "/src/dist/other/other.js", "/src/dist/other/other.d.ts", @@ -388,7 +388,7 @@ export default hello.hello`); }); }); - describe("tsbuild - when project reference is referenced transitively", () => { + describe("unittests:: tsbuild - when project reference is referenced transitively", () => { const projFs = loadProjectFromDisk("tests/projects/transitiveReferences"); const allExpectedOutputs = [ "/src/a.js", "/src/a.d.ts", @@ -460,7 +460,7 @@ export const b = new A();`); export namespace OutFile { const outFileFs = loadProjectFromDisk("tests/projects/outfile-concat"); - describe("tsbuild - baseline sectioned sourcemaps", () => { + describe("unittests:: tsbuild - baseline sectioned sourcemaps", () => { let fs: vfs.FileSystem | undefined; before(() => { fs = outFileFs.shadow(); @@ -480,7 +480,7 @@ export const b = new A();`); }); }); - describe("tsbuild - downstream prepend projects always get rebuilt", () => { + describe("unittests:: tsbuild - downstream prepend projects always get rebuilt", () => { it("", () => { const fs = outFileFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -508,7 +508,7 @@ export const b = new A();`); "/src/core/index.d.ts.map", ]; - describe("tsbuild - empty files option in tsconfig", () => { + describe("unittests:: tsbuild - empty files option in tsconfig", () => { it("has empty files diagnostic when files is empty and no references are provided", () => { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); @@ -541,7 +541,7 @@ export const b = new A();`); }); } - describe("tsbuild - graph-ordering", () => { + describe("unittests:: tsbuild - graph-ordering", () => { let host: fakes.SolutionBuilderHost | undefined; const deps: [string, string][] = [ ["A", "B"], diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 9f45da51656..72da8dbf85e 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -14,7 +14,7 @@ namespace ts.tscWatch { return solutionBuilder; } - describe("tsbuild-watch program updates", () => { + describe("unittests:: tsbuild-watch program updates", () => { const project = "sample1"; const enum SubProject { core = "core", diff --git a/src/testRunner/unittests/tscWatch/emit.ts b/src/testRunner/unittests/tscWatch/emit.ts index 2f7a70bd2c6..06aec75f31f 100644 --- a/src/testRunner/unittests/tscWatch/emit.ts +++ b/src/testRunner/unittests/tscWatch/emit.ts @@ -38,7 +38,7 @@ namespace ts.tscWatch { checkOutputDoesNotContain(host, expectedNonAffectedFiles); } - describe("tsc-watch:: emit with outFile or out setting", () => { + describe("unittests:: tsc-watch:: emit with outFile or out setting", () => { function createWatchForOut(out?: string, outFile?: string) { const host = createWatchedSystem([]); const config: FileOrFolderEmit = { @@ -161,7 +161,7 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch:: emit for configured projects", () => { + describe("unittests:: tsc-watch:: emit for configured projects", () => { const file1Consumer1Path = "/a/b/file1Consumer1.ts"; const moduleFile1Path = "/a/b/moduleFile1.ts"; const configFilePath = "/a/b/tsconfig.json"; @@ -495,7 +495,7 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch:: emit file content", () => { + describe("unittests:: tsc-watch:: emit file content", () => { interface EmittedFile extends File { shouldBeWritten: boolean; } @@ -676,7 +676,7 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch:: emit with when module emit is specified as node", () => { + describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => { it("when instead of filechanged recursive directory watcher is invoked", () => { const configFile: File = { path: "/a/rootFolder/project/tsconfig.json", diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts index e7b546edcd9..b29fde85697 100644 --- a/src/testRunner/unittests/tscWatch/resolutionCache.ts +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -1,5 +1,5 @@ namespace ts.tscWatch { - describe("tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { + describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { it("works", () => { const root = { path: "/a/d/f0.ts", @@ -404,7 +404,7 @@ declare module "fs" { }); }); - describe("tsc-watch:: resolutionCache:: tsc-watch with modules linked to sibling folder", () => { + describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch with modules linked to sibling folder", () => { const projectRoot = "/user/username/projects/project"; const mainPackageRoot = `${projectRoot}/main`; const linkedPackageRoot = `${projectRoot}/linked-package`; diff --git a/src/testRunner/unittests/tscWatch/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts index 334b353f1d5..35527b73701 100644 --- a/src/testRunner/unittests/tscWatch/watchApi.ts +++ b/src/testRunner/unittests/tscWatch/watchApi.ts @@ -1,5 +1,5 @@ namespace ts.tscWatch { - describe("tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { + describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { const projectRoot = "/user/username/projects/project"; const configFileJson: any = { compilerOptions: { module: "commonjs", resolveJsonModule: true }, diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index 3d09e2a38f6..d30e5854652 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -1,6 +1,6 @@ namespace ts.tscWatch { import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - describe("tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { + describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { it("watchFile using dynamic priority polling", () => { const projectFolder = "/a/username/project"; const file1: File = { diff --git a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index b5132f57c0e..26b7857347a 100644 --- a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -3,7 +3,7 @@ namespace ts.projectSystem { return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); } - describe("tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { + describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { enum CalledMapsWithSingleArg { fileExists = "fileExists", directoryExists = "directoryExists", diff --git a/src/testRunner/unittests/tsserver/cancellationToken.ts b/src/testRunner/unittests/tsserver/cancellationToken.ts index 5d9cd5527e6..71fb4bb4b22 100644 --- a/src/testRunner/unittests/tsserver/cancellationToken.ts +++ b/src/testRunner/unittests/tsserver/cancellationToken.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: cancellationToken", () => { + describe("unittests:: tsserver:: cancellationToken", () => { // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test let oldPrepare: AnyFunction; before(() => { diff --git a/src/testRunner/unittests/tsserver/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts index 310a6e9e33c..667144f6a06 100644 --- a/src/testRunner/unittests/tsserver/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -6,7 +6,7 @@ namespace ts.projectSystem { return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); } - describe("tsserver:: compileOnSave:: affected list", () => { + describe("unittests:: tsserver:: compileOnSave:: affected list", () => { function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); @@ -552,7 +552,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: compileOnSave:: EmitFile test", () => { + describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { it("should respect line endings", () => { test("\n"); test("\r\n"); diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index ed020bae0ba..0a62f74dc7d 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: completions", () => { + describe("unittests:: tsserver:: completions", () => { it("works", () => { const aTs: File = { path: "/a.ts", diff --git a/src/testRunner/unittests/tsserver/configFileSearch.ts b/src/testRunner/unittests/tsserver/configFileSearch.ts index 4d0b0c0efdd..c9e02b8bd06 100644 --- a/src/testRunner/unittests/tsserver/configFileSearch.ts +++ b/src/testRunner/unittests/tsserver/configFileSearch.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: searching for config file", () => { + describe("unittests:: tsserver:: searching for config file", () => { it("should stop at projectRootPath if given", () => { const f1 = { path: "/a/file1.ts", diff --git a/src/testRunner/unittests/tsserver/declarationFileMaps.ts b/src/testRunner/unittests/tsserver/declarationFileMaps.ts index 334af82ca48..200d7af0240 100644 --- a/src/testRunner/unittests/tsserver/declarationFileMaps.ts +++ b/src/testRunner/unittests/tsserver/declarationFileMaps.ts @@ -40,7 +40,7 @@ namespace ts.projectSystem { assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); } - describe("tsserver:: with declaration file maps:: project references", () => { + describe("unittests:: tsserver:: with declaration file maps:: project references", () => { const aTs: File = { path: "/a/a.ts", content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index 0fd812a89d2..1761e413833 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: document registry in project service", () => { + describe("unittests:: tsserver:: document registry in project service", () => { const projectRootPath = "/user/username/projects/project"; const importModuleContent = `import {a} from "./module1"`; const file: File = { diff --git a/src/testRunner/unittests/tsserver/duplicatePackages.ts b/src/testRunner/unittests/tsserver/duplicatePackages.ts index f8a282ee7d2..ec85eb6a82f 100644 --- a/src/testRunner/unittests/tsserver/duplicatePackages.ts +++ b/src/testRunner/unittests/tsserver/duplicatePackages.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: duplicate packages", () => { + describe("unittests:: tsserver:: duplicate packages", () => { // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. it("works with import fixes", () => { const packageContent = "export const foo: number;"; diff --git a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index ca7f839e1b0..ff69a7e6568 100644 --- a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: LargeFileReferencedEvent with large file", () => { + describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => { const projectRoot = "/user/username/projects/project"; function getLargeFile(useLargeTsFile: boolean) { diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 2de3ab40db6..7a881ff1380 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { + describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { const projectRoot = "/user/username/projects"; const aTs: File = { path: `${projectRoot}/a/a.ts`, diff --git a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index c818f4d2a8d..05a43cb4a15 100644 --- a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: ProjectsUpdatedInBackground", () => { + describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); const seen = createMap(); diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 6c246a2e3bd..87615b24d12 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: ExternalProjects", () => { + describe("unittests:: tsserver:: ExternalProjects", () => { describe("correctly handling add/remove tsconfig - 1", () => { function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { const f1 = { diff --git a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts index d790a1aa25a..bb1be544a2c 100644 --- a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: forceConsistentCasingInFileNames", () => { + describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { it("works when extends is specified with a case insensitive file system", () => { const rootPath = "/Users/username/dev/project"; const file1: File = { diff --git a/src/testRunner/unittests/tsserver/formatSettings.ts b/src/testRunner/unittests/tsserver/formatSettings.ts index 4e3e2ebd418..2d09ed8a8a0 100644 --- a/src/testRunner/unittests/tsserver/formatSettings.ts +++ b/src/testRunner/unittests/tsserver/formatSettings.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: format settings", () => { + describe("unittests:: tsserver:: format settings", () => { it("can be set globally", () => { const f1 = { path: "/a/b/app.ts", diff --git a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts index 3fadf99c3f8..2b28de7b121 100644 --- a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts +++ b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: getEditsForFileRename", () => { + describe("unittests:: tsserver:: getEditsForFileRename", () => { it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { const userTs: File = { path: "/user.ts", diff --git a/src/testRunner/unittests/tsserver/importHelpers.ts b/src/testRunner/unittests/tsserver/importHelpers.ts index 42e38aed122..b1ec5955eaa 100644 --- a/src/testRunner/unittests/tsserver/importHelpers.ts +++ b/src/testRunner/unittests/tsserver/importHelpers.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: import helpers", () => { + describe("unittests:: tsserver:: import helpers", () => { it("should not crash in tsserver", () => { const f1 = { path: "/a/app.ts", diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index b22413c6ab3..47e871efb82 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: Inferred projects", () => { + describe("unittests:: tsserver:: Inferred projects", () => { it("should support files without extensions", () => { const f = { path: "/a/compile", diff --git a/src/testRunner/unittests/tsserver/languageService.ts b/src/testRunner/unittests/tsserver/languageService.ts index 6ee68e74c15..86d426664df 100644 --- a/src/testRunner/unittests/tsserver/languageService.ts +++ b/src/testRunner/unittests/tsserver/languageService.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: Language service", () => { + describe("unittests:: tsserver:: Language service", () => { it("should work correctly on case-sensitive file systems", () => { const lib = { path: "/a/Lib/lib.d.ts", diff --git a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts index dd0b954824d..eb254789d67 100644 --- a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts +++ b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: maxNodeModuleJsDepth for inferred projects", () => { + describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () => { it("should be set to 2 if the project has js root files", () => { const file1: File = { path: "/a/b/file1.js", diff --git a/src/testRunner/unittests/tsserver/metadataInResponse.ts b/src/testRunner/unittests/tsserver/metadataInResponse.ts index 783fce4dd16..6ec8a565d87 100644 --- a/src/testRunner/unittests/tsserver/metadataInResponse.ts +++ b/src/testRunner/unittests/tsserver/metadataInResponse.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: with metadata in response", () => { + describe("unittests:: tsserver:: with metadata in response", () => { const metadata = "Extra Info"; function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { const output = host.getOutput().map(mapOutputToJson); diff --git a/src/testRunner/unittests/tsserver/navTo.ts b/src/testRunner/unittests/tsserver/navTo.ts index 5d45e537ede..b3aebef1043 100644 --- a/src/testRunner/unittests/tsserver/navTo.ts +++ b/src/testRunner/unittests/tsserver/navTo.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: navigate-to for javascript project", () => { + describe("unittests:: tsserver:: navigate-to for javascript project", () => { function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined; } diff --git a/src/testRunner/unittests/tsserver/occurences.ts b/src/testRunner/unittests/tsserver/occurences.ts index bcdadd3872c..cf9c83e2e72 100644 --- a/src/testRunner/unittests/tsserver/occurences.ts +++ b/src/testRunner/unittests/tsserver/occurences.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: occurence highlight on string", () => { + describe("unittests:: tsserver:: occurence highlight on string", () => { it("should be marked if only on string values", () => { const file1: File = { path: "/a/b/file1.ts", diff --git a/src/testRunner/unittests/tsserver/openFile.ts b/src/testRunner/unittests/tsserver/openFile.ts index 187b5043fcc..d2a995c68f8 100644 --- a/src/testRunner/unittests/tsserver/openFile.ts +++ b/src/testRunner/unittests/tsserver/openFile.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: Open-file", () => { + describe("unittests:: tsserver:: Open-file", () => { it("can be reloaded with empty content", () => { const f = { path: "/a/b/app.ts", diff --git a/src/testRunner/unittests/tsserver/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts index cb82a9eeca6..9e24771f3ae 100644 --- a/src/testRunner/unittests/tsserver/projectErrors.ts +++ b/src/testRunner/unittests/tsserver/projectErrors.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: Project Errors", () => { + describe("unittests:: tsserver:: Project Errors", () => { function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: ReadonlyArray): void { assert.isTrue(projectFiles !== undefined, "missing project files"); checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); @@ -200,7 +200,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: Project Errors are reported as appropriate", () => { + describe("unittests:: tsserver:: Project Errors are reported as appropriate", () => { function createErrorLogger() { let hasError = false; const errorLogger: server.Logger = { @@ -481,7 +481,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: Project Errors for Configure file diagnostics events", () => { + describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => { function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic { const d = Diagnostics.Unknown_compiler_option_0; const start = configFile.content.indexOf(prop) - 1; // start at "prop" @@ -671,7 +671,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: Project Errors dont include overwrite emit error", () => { + describe("unittests:: tsserver:: Project Errors dont include overwrite emit error", () => { it("for inferred project", () => { const f1 = { path: "/a/b/f1.js", @@ -754,7 +754,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { + describe("unittests:: tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { it("when options change", () => { const file = { path: "/a/b/app.ts", @@ -818,7 +818,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: Project Errors with config file change", () => { + describe("unittests:: tsserver:: Project Errors with config file change", () => { it("Updates diagnostics when '--noUnusedLabels' changes", () => { const aTs: File = { path: "/a.ts", content: "label: while (1) {}" }; const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index c7a42d6b813..e39c510216d 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: with project references and tsbuild", () => { + describe("unittests:: tsserver:: with project references and tsbuild", () => { function createHost(files: ReadonlyArray, rootNames: ReadonlyArray) { const host = createServerHost(files); diff --git a/src/testRunner/unittests/tsserver/refactors.ts b/src/testRunner/unittests/tsserver/refactors.ts index 2e916f50f9b..2f4531f61a5 100644 --- a/src/testRunner/unittests/tsserver/refactors.ts +++ b/src/testRunner/unittests/tsserver/refactors.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: refactors", () => { + describe("unittests:: tsserver:: refactors", () => { it("use formatting options", () => { const file = { path: "/a.ts", diff --git a/src/testRunner/unittests/tsserver/reload.ts b/src/testRunner/unittests/tsserver/reload.ts index 80f3e56e437..e9a32a5e1a1 100644 --- a/src/testRunner/unittests/tsserver/reload.ts +++ b/src/testRunner/unittests/tsserver/reload.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: reload", () => { + describe("unittests:: tsserver:: reload", () => { it("should work with temp file", () => { const f1 = { path: "/a/b/app.ts", diff --git a/src/testRunner/unittests/tsserver/rename.ts b/src/testRunner/unittests/tsserver/rename.ts index 3b4d77263e6..75c08bb8bb0 100644 --- a/src/testRunner/unittests/tsserver/rename.ts +++ b/src/testRunner/unittests/tsserver/rename.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: rename", () => { + describe("unittests:: tsserver:: rename", () => { it("works with fileToRename", () => { const aTs: File = { path: "/a.ts", content: "export const a = 0;" }; const bTs: File = { path: "/b.ts", content: 'import { a } from "./a";' }; diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 9f75455e226..33b59e2faae 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -5,7 +5,7 @@ namespace ts.projectSystem { return resolutionTrace; } - describe("tsserver:: resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => { + describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem extra resolution pass in lshost", () => { it("can load typings that are proper modules", () => { const file1 = { path: "/a/b/app.js", @@ -46,7 +46,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: resolutionCache:: tsserverProjectSystem watching @types", () => { + describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem watching @types", () => { it("works correctly when typings are added or removed", () => { const f1 = { path: "/a/b/app.ts", @@ -92,7 +92,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { + describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { it("should remove the `module not found` error", () => { const moduleFile = { path: "/a/b/moduleFile.ts", @@ -358,7 +358,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { + describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { it("should restore the states for inferred projects", () => { const moduleFile = { path: "/a/b/moduleFile.ts", @@ -493,7 +493,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => { + describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => { const projectLocation = "/user/username/projects/myproject"; const configFile: File = { path: `${projectLocation}/tsconfig.json`, diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 927b22fb475..bf21228405c 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -35,7 +35,7 @@ namespace ts.server { } } - describe("tsserver:: Session:: General functionality", () => { + describe("unittests:: tsserver:: Session:: General functionality", () => { let session: TestSession; let lastSent: protocol.Message; @@ -418,7 +418,7 @@ namespace ts.server { }); }); - describe("tsserver:: Session:: exceptions", () => { + describe("unittests:: tsserver:: Session:: exceptions", () => { // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test let oldPrepare: AnyFunction; @@ -489,7 +489,7 @@ namespace ts.server { }); }); - describe("tsserver:: Session:: how Session is extendable via subclassing", () => { + describe("unittests:: tsserver:: Session:: how Session is extendable via subclassing", () => { class TestSession extends Session { lastSent: protocol.Message | undefined; customHandler = "testhandler"; @@ -558,7 +558,7 @@ namespace ts.server { }); }); - describe("tsserver:: Session:: an example of using the Session API to create an in-process server", () => { + describe("unittests:: tsserver:: Session:: an example of using the Session API to create an in-process server", () => { class InProcSession extends Session { private queue: protocol.Request[] = []; constructor(private client: InProcClient) { @@ -710,7 +710,7 @@ namespace ts.server { }); }); - describe("tsserver:: Session:: helpers", () => { + describe("unittests:: tsserver:: Session:: helpers", () => { it(getLocationInNewDocument.name, () => { const text = `// blank line\nconst x = 0;`; const renameLocationInOldText = text.indexOf("0"); diff --git a/src/testRunner/unittests/tsserver/skipLibCheck.ts b/src/testRunner/unittests/tsserver/skipLibCheck.ts index 463ea439396..d154154f083 100644 --- a/src/testRunner/unittests/tsserver/skipLibCheck.ts +++ b/src/testRunner/unittests/tsserver/skipLibCheck.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: with skipLibCheck", () => { + describe("unittests:: tsserver:: with skipLibCheck", () => { it("should be turned on for js-only inferred projects", () => { const file1 = { path: "/a/b/file1.js", diff --git a/src/testRunner/unittests/tsserver/symLinks.ts b/src/testRunner/unittests/tsserver/symLinks.ts index d6fa9dca894..e331c94ca1a 100644 --- a/src/testRunner/unittests/tsserver/symLinks.ts +++ b/src/testRunner/unittests/tsserver/symLinks.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: symLinks", () => { + describe("unittests:: tsserver:: symLinks", () => { it("rename in common file renames all project", () => { const projects = "/users/username/projects"; const folderA = `${projects}/a`; diff --git a/src/testRunner/unittests/tsserver/syntaxOperations.ts b/src/testRunner/unittests/tsserver/syntaxOperations.ts index c2ec877137a..66a962d3625 100644 --- a/src/testRunner/unittests/tsserver/syntaxOperations.ts +++ b/src/testRunner/unittests/tsserver/syntaxOperations.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: syntax operations", () => { + describe("unittests:: tsserver:: syntax operations", () => { function navBarFull(session: TestSession, file: File) { return JSON.stringify(session.executeCommandSeq({ command: protocol.CommandTypes.NavBarFull, diff --git a/src/testRunner/unittests/tsserver/telemetry.ts b/src/testRunner/unittests/tsserver/telemetry.ts index 4f4d8ba8b19..cdf78adbf54 100644 --- a/src/testRunner/unittests/tsserver/telemetry.ts +++ b/src/testRunner/unittests/tsserver/telemetry.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: project telemetry", () => { + describe("unittests:: tsserver:: project telemetry", () => { it("does nothing for inferred project", () => { const file = makeFile("/a.js"); const et = new TestServerEventManager([file]); diff --git a/src/testRunner/unittests/tsserver/textStorage.ts b/src/testRunner/unittests/tsserver/textStorage.ts index 157734da931..40424b5c9c3 100644 --- a/src/testRunner/unittests/tsserver/textStorage.ts +++ b/src/testRunner/unittests/tsserver/textStorage.ts @@ -1,5 +1,5 @@ namespace ts.textStorage { - describe("tsserver:: Text storage", () => { + describe("unittests:: tsserver:: Text storage", () => { const f = { path: "/a/app.ts", content: ` diff --git a/src/testRunner/unittests/tsserver/typeAquisition.ts b/src/testRunner/unittests/tsserver/typeAquisition.ts index 9f487896e03..ac9d3e3cfc0 100644 --- a/src/testRunner/unittests/tsserver/typeAquisition.ts +++ b/src/testRunner/unittests/tsserver/typeAquisition.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: autoDiscovery", () => { + describe("unittests:: tsserver:: autoDiscovery", () => { it("does not depend on extension", () => { const file1 = { path: "/a/b/app.html", @@ -22,7 +22,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: prefer typings to js", () => { + describe("unittests:: tsserver:: prefer typings to js", () => { it("during second resolution pass", () => { const typingsCacheLocation = "/a/typings"; const f1 = { diff --git a/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts b/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts index 8318f402a1a..e3ad121e427 100644 --- a/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts +++ b/src/testRunner/unittests/tsserver/typeReferenceDirectives.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: typeReferenceDirectives", () => { + describe("unittests:: tsserver:: typeReferenceDirectives", () => { it("when typeReferenceDirective contains UpperCasePackage", () => { const projectLocation = "/user/username/projects/myproject"; const libProjectLocation = `${projectLocation}/lib`; diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index 18117a5f961..76df9934682 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -47,7 +47,7 @@ namespace ts.projectSystem { import typingsName = TI.typingsName; - describe("tsserver:: typingsInstaller:: local module", () => { + describe("unittests:: tsserver:: typingsInstaller:: local module", () => { it("should not be picked up", () => { const f1 = { path: "/a/app.js", @@ -86,7 +86,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: General functionality", () => { + describe("unittests:: tsserver:: typingsInstaller:: General functionality", () => { it("configured projects (typings installed) 1", () => { const file1 = { path: "/a/b/app.js", @@ -1249,7 +1249,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: Validate package name:", () => { + describe("unittests:: tsserver:: typingsInstaller:: Validate package name:", () => { it("name cannot be too long", () => { let packageName = "a"; for (let i = 0; i < 8; i++) { @@ -1273,7 +1273,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: Invalid package names", () => { + describe("unittests:: tsserver:: typingsInstaller:: Invalid package names", () => { it("should not be installed", () => { const f1 = { path: "/a/b/app.js", @@ -1305,7 +1305,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: discover typings", () => { + describe("unittests:: tsserver:: typingsInstaller:: discover typings", () => { const emptySafeList = emptyMap; it("should use mappings from safe list", () => { @@ -1517,7 +1517,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: telemetry events", () => { + describe("unittests:: tsserver:: typingsInstaller:: telemetry events", () => { it("should be received", () => { const f1 = { path: "/a/app.js", @@ -1567,7 +1567,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: progress notifications", () => { + describe("unittests:: tsserver:: typingsInstaller:: progress notifications", () => { it("should be sent for success", () => { const f1 = { path: "/a/app.js", @@ -1676,7 +1676,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: npm installation command", () => { + describe("unittests:: tsserver:: typingsInstaller:: npm installation command", () => { const npmPath = "npm", tsVersion = "2.9.0-dev.20180410"; const packageNames = ["@types/graphql@ts2.8", "@types/highlight.js@ts2.8", "@types/jest@ts2.8", "@types/mini-css-extract-plugin@ts2.8", "@types/mongoose@ts2.8", "@types/pg@ts2.8", "@types/webpack-bundle-analyzer@ts2.8", "@types/enhanced-resolve@ts2.8", "@types/eslint-plugin-prettier@ts2.8", "@types/friendly-errors-webpack-plugin@ts2.8", "@types/hammerjs@ts2.8", "@types/history@ts2.8", "@types/image-size@ts2.8", "@types/js-cookie@ts2.8", "@types/koa-compress@ts2.8", "@types/less@ts2.8", "@types/material-ui@ts2.8", "@types/mysql@ts2.8", "@types/nodemailer@ts2.8", "@types/prettier@ts2.8", "@types/query-string@ts2.8", "@types/react-places-autocomplete@ts2.8", "@types/react-router@ts2.8", "@types/react-router-config@ts2.8", "@types/react-select@ts2.8", "@types/react-transition-group@ts2.8", "@types/redux-form@ts2.8", "@types/abbrev@ts2.8", "@types/accepts@ts2.8", "@types/acorn@ts2.8", "@types/ansi-regex@ts2.8", "@types/ansi-styles@ts2.8", "@types/anymatch@ts2.8", "@types/apollo-codegen@ts2.8", "@types/are-we-there-yet@ts2.8", "@types/argparse@ts2.8", "@types/arr-union@ts2.8", "@types/array-find-index@ts2.8", "@types/array-uniq@ts2.8", "@types/array-unique@ts2.8", "@types/arrify@ts2.8", "@types/assert-plus@ts2.8", "@types/async@ts2.8", "@types/autoprefixer@ts2.8", "@types/aws4@ts2.8", "@types/babel-code-frame@ts2.8", "@types/babel-generator@ts2.8", "@types/babel-plugin-syntax-jsx@ts2.8", "@types/babel-template@ts2.8", "@types/babel-traverse@ts2.8", "@types/babel-types@ts2.8", "@types/babylon@ts2.8", "@types/base64-js@ts2.8", "@types/basic-auth@ts2.8", "@types/big.js@ts2.8", "@types/bl@ts2.8", "@types/bluebird@ts2.8", "@types/body-parser@ts2.8", "@types/bonjour@ts2.8", "@types/boom@ts2.8", "@types/brace-expansion@ts2.8", "@types/braces@ts2.8", "@types/brorand@ts2.8", "@types/browser-resolve@ts2.8", "@types/bson@ts2.8", "@types/buffer-equal@ts2.8", "@types/builtin-modules@ts2.8", "@types/bytes@ts2.8", "@types/callsites@ts2.8", "@types/camelcase@ts2.8", "@types/camelcase-keys@ts2.8", "@types/caseless@ts2.8", "@types/change-emitter@ts2.8", "@types/check-types@ts2.8", "@types/cheerio@ts2.8", "@types/chokidar@ts2.8", "@types/chownr@ts2.8", "@types/circular-json@ts2.8", "@types/classnames@ts2.8", "@types/clean-css@ts2.8", "@types/clone@ts2.8", "@types/co-body@ts2.8", "@types/color@ts2.8", "@types/color-convert@ts2.8", "@types/color-name@ts2.8", "@types/color-string@ts2.8", "@types/colors@ts2.8", "@types/combined-stream@ts2.8", "@types/common-tags@ts2.8", "@types/component-emitter@ts2.8", "@types/compressible@ts2.8", "@types/compression@ts2.8", "@types/concat-stream@ts2.8", "@types/connect-history-api-fallback@ts2.8", "@types/content-disposition@ts2.8", "@types/content-type@ts2.8", "@types/convert-source-map@ts2.8", "@types/cookie@ts2.8", "@types/cookie-signature@ts2.8", "@types/cookies@ts2.8", "@types/core-js@ts2.8", "@types/cosmiconfig@ts2.8", "@types/create-react-class@ts2.8", "@types/cross-spawn@ts2.8", "@types/cryptiles@ts2.8", "@types/css-modules-require-hook@ts2.8", "@types/dargs@ts2.8", "@types/dateformat@ts2.8", "@types/debug@ts2.8", "@types/decamelize@ts2.8", "@types/decompress@ts2.8", "@types/decompress-response@ts2.8", "@types/deep-equal@ts2.8", "@types/deep-extend@ts2.8", "@types/deepmerge@ts2.8", "@types/defined@ts2.8", "@types/del@ts2.8", "@types/depd@ts2.8", "@types/destroy@ts2.8", "@types/detect-indent@ts2.8", "@types/detect-newline@ts2.8", "@types/diff@ts2.8", "@types/doctrine@ts2.8", "@types/download@ts2.8", "@types/draft-js@ts2.8", "@types/duplexer2@ts2.8", "@types/duplexer3@ts2.8", "@types/duplexify@ts2.8", "@types/ejs@ts2.8", "@types/end-of-stream@ts2.8", "@types/entities@ts2.8", "@types/escape-html@ts2.8", "@types/escape-string-regexp@ts2.8", "@types/escodegen@ts2.8", "@types/eslint-scope@ts2.8", "@types/eslint-visitor-keys@ts2.8", "@types/esprima@ts2.8", "@types/estraverse@ts2.8", "@types/etag@ts2.8", "@types/events@ts2.8", "@types/execa@ts2.8", "@types/exenv@ts2.8", "@types/exit@ts2.8", "@types/exit-hook@ts2.8", "@types/expect@ts2.8", "@types/express@ts2.8", "@types/express-graphql@ts2.8", "@types/extend@ts2.8", "@types/extract-zip@ts2.8", "@types/fancy-log@ts2.8", "@types/fast-diff@ts2.8", "@types/fast-levenshtein@ts2.8", "@types/figures@ts2.8", "@types/file-type@ts2.8", "@types/filenamify@ts2.8", "@types/filesize@ts2.8", "@types/finalhandler@ts2.8", "@types/find-root@ts2.8", "@types/find-up@ts2.8", "@types/findup-sync@ts2.8", "@types/forever-agent@ts2.8", "@types/form-data@ts2.8", "@types/forwarded@ts2.8", "@types/fresh@ts2.8", "@types/from2@ts2.8", "@types/fs-extra@ts2.8", "@types/get-caller-file@ts2.8", "@types/get-stdin@ts2.8", "@types/get-stream@ts2.8", "@types/get-value@ts2.8", "@types/glob-base@ts2.8", "@types/glob-parent@ts2.8", "@types/glob-stream@ts2.8", "@types/globby@ts2.8", "@types/globule@ts2.8", "@types/got@ts2.8", "@types/graceful-fs@ts2.8", "@types/gulp-rename@ts2.8", "@types/gulp-sourcemaps@ts2.8", "@types/gulp-util@ts2.8", "@types/gzip-size@ts2.8", "@types/handlebars@ts2.8", "@types/has-ansi@ts2.8", "@types/hasha@ts2.8", "@types/he@ts2.8", "@types/hoek@ts2.8", "@types/html-entities@ts2.8", "@types/html-minifier@ts2.8", "@types/htmlparser2@ts2.8", "@types/http-assert@ts2.8", "@types/http-errors@ts2.8", "@types/http-proxy@ts2.8", "@types/http-proxy-middleware@ts2.8", "@types/indent-string@ts2.8", "@types/inflected@ts2.8", "@types/inherits@ts2.8", "@types/ini@ts2.8", "@types/inline-style-prefixer@ts2.8", "@types/inquirer@ts2.8", "@types/internal-ip@ts2.8", "@types/into-stream@ts2.8", "@types/invariant@ts2.8", "@types/ip@ts2.8", "@types/ip-regex@ts2.8", "@types/is-absolute-url@ts2.8", "@types/is-binary-path@ts2.8", "@types/is-finite@ts2.8", "@types/is-glob@ts2.8", "@types/is-my-json-valid@ts2.8", "@types/is-number@ts2.8", "@types/is-object@ts2.8", "@types/is-path-cwd@ts2.8", "@types/is-path-in-cwd@ts2.8", "@types/is-promise@ts2.8", "@types/is-scoped@ts2.8", "@types/is-stream@ts2.8", "@types/is-svg@ts2.8", "@types/is-url@ts2.8", "@types/is-windows@ts2.8", "@types/istanbul-lib-coverage@ts2.8", "@types/istanbul-lib-hook@ts2.8", "@types/istanbul-lib-instrument@ts2.8", "@types/istanbul-lib-report@ts2.8", "@types/istanbul-lib-source-maps@ts2.8", "@types/istanbul-reports@ts2.8", "@types/jest-diff@ts2.8", "@types/jest-docblock@ts2.8", "@types/jest-get-type@ts2.8", "@types/jest-matcher-utils@ts2.8", "@types/jest-validate@ts2.8", "@types/jpeg-js@ts2.8", "@types/js-base64@ts2.8", "@types/js-string-escape@ts2.8", "@types/js-yaml@ts2.8", "@types/jsbn@ts2.8", "@types/jsdom@ts2.8", "@types/jsesc@ts2.8", "@types/json-parse-better-errors@ts2.8", "@types/json-schema@ts2.8", "@types/json-stable-stringify@ts2.8", "@types/json-stringify-safe@ts2.8", "@types/json5@ts2.8", "@types/jsonfile@ts2.8", "@types/jsontoxml@ts2.8", "@types/jss@ts2.8", "@types/keygrip@ts2.8", "@types/keymirror@ts2.8", "@types/keyv@ts2.8", "@types/klaw@ts2.8", "@types/koa-send@ts2.8", "@types/leven@ts2.8", "@types/listr@ts2.8", "@types/load-json-file@ts2.8", "@types/loader-runner@ts2.8", "@types/loader-utils@ts2.8", "@types/locate-path@ts2.8", "@types/lodash-es@ts2.8", "@types/lodash.assign@ts2.8", "@types/lodash.camelcase@ts2.8", "@types/lodash.clonedeep@ts2.8", "@types/lodash.debounce@ts2.8", "@types/lodash.escape@ts2.8", "@types/lodash.flowright@ts2.8", "@types/lodash.get@ts2.8", "@types/lodash.isarguments@ts2.8", "@types/lodash.isarray@ts2.8", "@types/lodash.isequal@ts2.8", "@types/lodash.isobject@ts2.8", "@types/lodash.isstring@ts2.8", "@types/lodash.keys@ts2.8", "@types/lodash.memoize@ts2.8", "@types/lodash.merge@ts2.8", "@types/lodash.mergewith@ts2.8", "@types/lodash.pick@ts2.8", "@types/lodash.sortby@ts2.8", "@types/lodash.tail@ts2.8", "@types/lodash.template@ts2.8", "@types/lodash.throttle@ts2.8", "@types/lodash.unescape@ts2.8", "@types/lodash.uniq@ts2.8", "@types/log-symbols@ts2.8", "@types/log-update@ts2.8", "@types/loglevel@ts2.8", "@types/loud-rejection@ts2.8", "@types/lru-cache@ts2.8", "@types/make-dir@ts2.8", "@types/map-obj@ts2.8", "@types/media-typer@ts2.8", "@types/mem@ts2.8", "@types/mem-fs@ts2.8", "@types/memory-fs@ts2.8", "@types/meow@ts2.8", "@types/merge-descriptors@ts2.8", "@types/merge-stream@ts2.8", "@types/methods@ts2.8", "@types/micromatch@ts2.8", "@types/mime@ts2.8", "@types/mime-db@ts2.8", "@types/mime-types@ts2.8", "@types/minimatch@ts2.8", "@types/minimist@ts2.8", "@types/minipass@ts2.8", "@types/mkdirp@ts2.8", "@types/mongodb@ts2.8", "@types/morgan@ts2.8", "@types/move-concurrently@ts2.8", "@types/ms@ts2.8", "@types/msgpack-lite@ts2.8", "@types/multimatch@ts2.8", "@types/mz@ts2.8", "@types/negotiator@ts2.8", "@types/node-dir@ts2.8", "@types/node-fetch@ts2.8", "@types/node-forge@ts2.8", "@types/node-int64@ts2.8", "@types/node-ipc@ts2.8", "@types/node-notifier@ts2.8", "@types/nomnom@ts2.8", "@types/nopt@ts2.8", "@types/normalize-package-data@ts2.8", "@types/normalize-url@ts2.8", "@types/number-is-nan@ts2.8", "@types/object-assign@ts2.8", "@types/on-finished@ts2.8", "@types/on-headers@ts2.8", "@types/once@ts2.8", "@types/onetime@ts2.8", "@types/opener@ts2.8", "@types/opn@ts2.8", "@types/optimist@ts2.8", "@types/ora@ts2.8", "@types/os-homedir@ts2.8", "@types/os-locale@ts2.8", "@types/os-tmpdir@ts2.8", "@types/p-cancelable@ts2.8", "@types/p-each-series@ts2.8", "@types/p-event@ts2.8", "@types/p-lazy@ts2.8", "@types/p-limit@ts2.8", "@types/p-locate@ts2.8", "@types/p-map@ts2.8", "@types/p-map-series@ts2.8", "@types/p-reduce@ts2.8", "@types/p-timeout@ts2.8", "@types/p-try@ts2.8", "@types/pako@ts2.8", "@types/parse-glob@ts2.8", "@types/parse-json@ts2.8", "@types/parseurl@ts2.8", "@types/path-exists@ts2.8", "@types/path-is-absolute@ts2.8", "@types/path-parse@ts2.8", "@types/pg-pool@ts2.8", "@types/pg-types@ts2.8", "@types/pify@ts2.8", "@types/pixelmatch@ts2.8", "@types/pkg-dir@ts2.8", "@types/pluralize@ts2.8", "@types/pngjs@ts2.8", "@types/prelude-ls@ts2.8", "@types/pretty-bytes@ts2.8", "@types/pretty-format@ts2.8", "@types/progress@ts2.8", "@types/promise-retry@ts2.8", "@types/proxy-addr@ts2.8", "@types/pump@ts2.8", "@types/q@ts2.8", "@types/qs@ts2.8", "@types/range-parser@ts2.8", "@types/rc@ts2.8", "@types/rc-select@ts2.8", "@types/rc-slider@ts2.8", "@types/rc-tooltip@ts2.8", "@types/rc-tree@ts2.8", "@types/react-event-listener@ts2.8", "@types/react-side-effect@ts2.8", "@types/react-slick@ts2.8", "@types/read-chunk@ts2.8", "@types/read-pkg@ts2.8", "@types/read-pkg-up@ts2.8", "@types/recompose@ts2.8", "@types/recursive-readdir@ts2.8", "@types/relateurl@ts2.8", "@types/replace-ext@ts2.8", "@types/request@ts2.8", "@types/request-promise-native@ts2.8", "@types/require-directory@ts2.8", "@types/require-from-string@ts2.8", "@types/require-relative@ts2.8", "@types/resolve@ts2.8", "@types/resolve-from@ts2.8", "@types/retry@ts2.8", "@types/rx@ts2.8", "@types/rx-lite@ts2.8", "@types/rx-lite-aggregates@ts2.8", "@types/safe-regex@ts2.8", "@types/sane@ts2.8", "@types/sass-graph@ts2.8", "@types/sax@ts2.8", "@types/scriptjs@ts2.8", "@types/semver@ts2.8", "@types/send@ts2.8", "@types/serialize-javascript@ts2.8", "@types/serve-index@ts2.8", "@types/serve-static@ts2.8", "@types/set-value@ts2.8", "@types/shallowequal@ts2.8", "@types/shelljs@ts2.8", "@types/sockjs@ts2.8", "@types/sockjs-client@ts2.8", "@types/source-list-map@ts2.8", "@types/source-map-support@ts2.8", "@types/spdx-correct@ts2.8", "@types/spdy@ts2.8", "@types/split@ts2.8", "@types/sprintf@ts2.8", "@types/sprintf-js@ts2.8", "@types/sqlstring@ts2.8", "@types/sshpk@ts2.8", "@types/stack-utils@ts2.8", "@types/stat-mode@ts2.8", "@types/statuses@ts2.8", "@types/strict-uri-encode@ts2.8", "@types/string-template@ts2.8", "@types/strip-ansi@ts2.8", "@types/strip-bom@ts2.8", "@types/strip-json-comments@ts2.8", "@types/supports-color@ts2.8", "@types/svg2png@ts2.8", "@types/svgo@ts2.8", "@types/table@ts2.8", "@types/tapable@ts2.8", "@types/tar@ts2.8", "@types/temp@ts2.8", "@types/tempfile@ts2.8", "@types/through@ts2.8", "@types/through2@ts2.8", "@types/tinycolor2@ts2.8", "@types/tmp@ts2.8", "@types/to-absolute-glob@ts2.8", "@types/tough-cookie@ts2.8", "@types/trim@ts2.8", "@types/tryer@ts2.8", "@types/type-check@ts2.8", "@types/type-is@ts2.8", "@types/ua-parser-js@ts2.8", "@types/uglify-js@ts2.8", "@types/uglifyjs-webpack-plugin@ts2.8", "@types/underscore@ts2.8", "@types/uniq@ts2.8", "@types/uniqid@ts2.8", "@types/untildify@ts2.8", "@types/urijs@ts2.8", "@types/url-join@ts2.8", "@types/url-parse@ts2.8", "@types/url-regex@ts2.8", "@types/user-home@ts2.8", "@types/util-deprecate@ts2.8", "@types/util.promisify@ts2.8", "@types/utils-merge@ts2.8", "@types/uuid@ts2.8", "@types/vali-date@ts2.8", "@types/vary@ts2.8", "@types/verror@ts2.8", "@types/vinyl@ts2.8", "@types/vinyl-fs@ts2.8", "@types/warning@ts2.8", "@types/watch@ts2.8", "@types/watchpack@ts2.8", "@types/webpack-dev-middleware@ts2.8", "@types/webpack-sources@ts2.8", "@types/which@ts2.8", "@types/window-size@ts2.8", "@types/wrap-ansi@ts2.8", "@types/write-file-atomic@ts2.8", "@types/ws@ts2.8", "@types/xml2js@ts2.8", "@types/xmlbuilder@ts2.8", "@types/xtend@ts2.8", "@types/yallist@ts2.8", "@types/yargs@ts2.8", "@types/yauzl@ts2.8", "@types/yeoman-generator@ts2.8", "@types/zen-observable@ts2.8", "@types/react-content-loader@ts2.8"]; const expectedCommands = [ @@ -1704,7 +1704,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: recomputing resolutions of unresolved imports", () => { + describe("unittests:: tsserver:: typingsInstaller:: recomputing resolutions of unresolved imports", () => { const globalTypingsCacheLocation = "/tmp"; const appPath = "/a/b/app.js" as Path; const foooPath = "/a/b/node_modules/fooo/index.d.ts"; @@ -1775,7 +1775,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => { + describe("unittests:: tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => { it("when projectRootPath is provided", () => { const projects = "/users/username/projects"; const projectRootPath = `${projects}/san2`; diff --git a/src/testRunner/unittests/tsserver/untitledFiles.ts b/src/testRunner/unittests/tsserver/untitledFiles.ts index 2c429707a94..845a53eb218 100644 --- a/src/testRunner/unittests/tsserver/untitledFiles.ts +++ b/src/testRunner/unittests/tsserver/untitledFiles.ts @@ -1,5 +1,5 @@ namespace ts.projectSystem { - describe("tsserver:: Untitled files", () => { + describe("unittests:: tsserver:: Untitled files", () => { it("Can convert positions to locations", () => { const aTs: File = { path: "/proj/a.ts", content: "" }; const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; diff --git a/src/testRunner/unittests/tsserver/versionCache.ts b/src/testRunner/unittests/tsserver/versionCache.ts index bd75b2bbaf4..60eaa6ca42e 100644 --- a/src/testRunner/unittests/tsserver/versionCache.ts +++ b/src/testRunner/unittests/tsserver/versionCache.ts @@ -15,7 +15,7 @@ namespace ts { assert.equal(editedText, checkText); } - describe(`tsserver:: VersionCache TS code`, () => { + describe(`unittests:: tsserver:: VersionCache TS code`, () => { let validateEditAtLineCharIndex: (line: number, char: number, deleteLength: number, insertString: string) => void; before(() => { @@ -77,7 +77,7 @@ var q:Point=p;`; }); }); - describe(`tsserver:: VersionCache simple text`, () => { + describe(`unittests:: tsserver:: VersionCache simple text`, () => { let validateEditAtPosition: (position: number, deleteLength: number, insertString: string) => void; let testContent: string; let lines: string[]; @@ -181,7 +181,7 @@ and grew 1cm per day`; }); }); - describe(`tsserver:: VersionCache stress test`, () => { + describe(`unittests:: tsserver:: VersionCache stress test`, () => { let rsa: number[] = []; let la: number[] = []; let las: number[] = []; diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 5ca4d6f4570..2c195e0c7d2 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -1,6 +1,6 @@ namespace ts.projectSystem { import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - describe("tsserver:: watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { + describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { const projectFolder = "/a/username/project"; const projectSrcFolder = `${projectFolder}/src`; @@ -83,7 +83,7 @@ namespace ts.projectSystem { }); }); - describe("tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { + describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; const configFile: File = { From f117184562901c18fd642abc15647b1eb1645c0d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 20 Dec 2018 14:00:45 -0800 Subject: [PATCH 092/120] More tsserver tests refactoring --- src/testRunner/tsconfig.json | 6 +- .../unittests/tsserver/compileOnSave.ts | 95 + .../unittests/tsserver/configuredProjects.ts | 1010 +++++ .../events/projectLanguageServiceState.ts | 51 + .../unittests/tsserver/events/surveyReady.ts | 111 + .../unittests/tsserver/externalProjects.ts | 464 +++ .../tsserver/getApplicableRefactors.ts | 12 + .../unittests/tsserver/inferredProjects.ts | 120 + src/testRunner/unittests/tsserver/projects.ts | 1426 +++++++ .../unittests/tsserverProjectSystem.ts | 3281 ----------------- 10 files changed, 3294 insertions(+), 3282 deletions(-) create mode 100644 src/testRunner/unittests/tsserver/configuredProjects.ts create mode 100644 src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts create mode 100644 src/testRunner/unittests/tsserver/events/surveyReady.ts create mode 100644 src/testRunner/unittests/tsserver/getApplicableRefactors.ts create mode 100644 src/testRunner/unittests/tsserver/projects.ts delete mode 100644 src/testRunner/unittests/tsserverProjectSystem.ts diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 4d200c4b8f9..9b5f74cb18c 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -61,7 +61,6 @@ "unittests/tsbuild.ts", "unittests/tsbuildWatchMode.ts", "unittests/tscWatchMode.ts", - "unittests/tsserverProjectSystem.ts", "unittests/config/commandLineParsing.ts", "unittests/config/configurationExtension.ts", "unittests/config/convertCompilerOptionsFromJson.ts", @@ -98,15 +97,19 @@ "unittests/tsserver/compileOnSave.ts", "unittests/tsserver/completions.ts", "unittests/tsserver/configFileSearch.ts", + "unittests/tsserver/configuredProjects.ts", "unittests/tsserver/declarationFileMaps.ts", "unittests/tsserver/documentRegistry.ts", "unittests/tsserver/duplicatePackages.ts", "unittests/tsserver/events/largeFileReferenced.ts", + "unittests/tsserver/events/projectLanguageServiceState.ts", "unittests/tsserver/events/projectLoading.ts", "unittests/tsserver/events/projectUpdatedInBackground.ts", + "unittests/tsserver/events/surveyReady.ts", "unittests/tsserver/externalProjects.ts", "unittests/tsserver/forceConsistentCasingInFileNames.ts", "unittests/tsserver/formatSettings.ts", + "unittests/tsserver/getApplicableRefactors.ts", "unittests/tsserver/getEditsForFileRename.ts", "unittests/tsserver/importHelpers.ts", "unittests/tsserver/inferredProjects.ts", @@ -118,6 +121,7 @@ "unittests/tsserver/openFile.ts", "unittests/tsserver/projectErrors.ts", "unittests/tsserver/projectReferences.ts", + "unittests/tsserver/projects.ts", "unittests/tsserver/refactors.ts", "unittests/tsserver/reload.ts", "unittests/tsserver/rename.ts", diff --git a/src/testRunner/unittests/tsserver/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts index 667144f6a06..7dc9ea0497c 100644 --- a/src/testRunner/unittests/tsserver/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -695,4 +695,99 @@ namespace ts.projectSystem { }); }); + describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { + const projectRoot = "/user/username/projects/myproject"; + const core: File = { + path: `${projectRoot}/core/core.ts`, + content: "let z = 10;" + }; + const app1: File = { + path: `${projectRoot}/app1/app.ts`, + content: "let x = 10;" + }; + const app2: File = { + path: `${projectRoot}/app2/app.ts`, + content: "let y = 10;" + }; + const app1Config: File = { + path: `${projectRoot}/app1/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const app2Config: File = { + path: `${projectRoot}/app2/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const files = [libFile, core, app1, app2, app1Config, app2Config]; + + function insertString(session: TestSession, file: File) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: file.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: "let k = 1" + } + }); + } + + function getSession() { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([app1, app2, core], session); + const service = session.getProjectService(); + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + const project1 = service.configuredProjects.get(app1Config.path)!; + const project2 = service.configuredProjects.get(app2Config.path)!; + checkProjectActualFiles(project1, [libFile.path, app1.path, core.path, app1Config.path]); + checkProjectActualFiles(project2, [libFile.path, app2.path, core.path, app2Config.path]); + insertString(session, app1); + insertString(session, app2); + assert.equal(project1.dirty, true); + assert.equal(project2.dirty, true); + return session; + } + + it("when projectFile is specified", () => { + const session = getSession(); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { + file: core.path, + projectFileName: app1Config.path + } + }).response; + assert.deepEqual(response, [ + { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true } + ]); + assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); + assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, true); + }); + + it("when projectFile is not specified", () => { + const session = getSession(); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { + file: core.path + } + }).response; + assert.deepEqual(response, [ + { projectFileName: app1Config.path, fileNames: [core.path, app1.path], projectUsesOutFile: true }, + { projectFileName: app2Config.path, fileNames: [core.path, app2.path], projectUsesOutFile: true } + ]); + assert.equal(session.getProjectService().configuredProjects.get(app1Config.path)!.dirty, false); + assert.equal(session.getProjectService().configuredProjects.get(app2Config.path)!.dirty, false); + }); + }); } diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts new file mode 100644 index 00000000000..d1525f8dbeb --- /dev/null +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -0,0 +1,1010 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: ConfiguredProjects", () => { + it("create configured project without file list", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: ` + { + "compilerOptions": {}, + "exclude": [ + "e" + ] + }` + }; + const file1: File = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: File = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; + + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); + // watching all files except one that was open + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + const configFileDirectory = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true); + }); + + it("create configured project with the file list", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: ` + { + "compilerOptions": {}, + "include": ["*.ts"] + }` + }; + const file1: File = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const file3: File = { + path: "/a/b/c/f3.ts", + content: "let z = 1" + }; + + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); + // watching all files except one that was open + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false); + }); + + it("add and then remove a config file in a folder with loose files", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "files": ["commonFile1.ts"] + }` + }; + const filesWithoutConfig = [libFile, commonFile1, commonFile2]; + const host = createServerHost(filesWithoutConfig); + + const filesWithConfig = [libFile, commonFile1, commonFile2, configFile]; + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); + + projectService.checkNumberOfProjects({ inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + + const configFileLocations = ["/", "/a/", "/a/b/"]; + const watchedFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]).concat(libFile.path); + checkWatchedFiles(host, watchedFiles); + + // Add a tsconfig file + host.reloadFS(filesWithConfig); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + + projectService.checkNumberOfProjects({ inferredProjects: 2, configuredProjects: 1 }); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + checkProjectActualFiles(projectService.configuredProjects.get(configFile.path)!, [libFile.path, commonFile1.path, configFile.path]); + + checkWatchedFiles(host, watchedFiles); + + // remove the tsconfig file + host.reloadFS(filesWithoutConfig); + + projectService.checkNumberOfProjects({ inferredProjects: 2 }); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + + host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects + + projectService.checkNumberOfProjects({ inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [commonFile1.path, libFile.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [commonFile2.path, libFile.path]); + checkWatchedFiles(host, watchedFiles); + }); + + it("add new files to a configured project without file list", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, libFile, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + const configFileDir = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path]); + + // add a new ts file + host.reloadFS([commonFile1, commonFile2, libFile, configFile]); + host.checkTimeoutQueueLengthAndRun(2); + // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + }); + + it("should ignore non-existing files specified in the config file", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "files": [ + "commonFile1.ts", + "commonFile3.ts" + ] + }` + }; + const host = createServerHost([commonFile1, commonFile2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); + + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path]); + checkNumberOfInferredProjects(projectService, 1); + }); + + it("handle recreated files correctly", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, commonFile2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + + // delete commonFile2 + host.reloadFS([commonFile1, configFile]); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(project, [commonFile1.path]); + + // re-add commonFile2 + host.reloadFS([commonFile1, commonFile2, configFile]); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + }); + + it("files explicitly excluded in config file", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "exclude": ["/a/c"] + }` + }; + const excludedFile1: File = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; + + const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(commonFile1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + projectService.openClientFile(excludedFile1.path); + checkNumberOfInferredProjects(projectService, 1); + }); + + it("should properly handle module resolution changes in config file", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: File = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: File = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const randomFile: File = { + path: "/a/file1.ts", + content: `export interface T {}` + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "moduleResolution": "node" + }, + "files": ["${file1.path}"] + }` + }; + const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.openClientFile(nodeModuleFile.path); + projectService.openClientFile(classicModuleFile.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + const inferredProject0 = projectService.inferredProjects[0]; + checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); + checkProjectActualFiles(projectService.inferredProjects[0], [classicModuleFile.path]); + + configFile.content = `{ + "compilerOptions": { + "moduleResolution": "classic" + }, + "files": ["${file1.path}"] + }`; + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(2); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); // will not remove project 1 + checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + const inferredProject1 = projectService.inferredProjects[1]; + checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); + + // Open random file and it will reuse first inferred project + projectService.openClientFile(randomFile.path); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + checkProjectActualFiles(projectService.inferredProjects[0], [randomFile.path]); // Reuses first inferred project + assert.strictEqual(projectService.inferredProjects[1], inferredProject1); + checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); + }); + + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: File = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: File = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts" ] + }` + }; + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + }); + + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: File = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: File = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts" ] + }` + }; + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + }); + + it("should tolerate config file errors and still try to build a project", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6", + "allowAnything": true + }, + "someOtherProperty": {} + }` + }; + const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); + }); + + it("should reuse same project if file is opened from the configured project that has no open files", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/main2.ts", + content: "let y =1;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts", "main2.ts" ] + }` + }; + const host = createServerHost([file1, file2, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(project.hasOpenRef()); // file1 + + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No open files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isTrue(project.hasOpenRef()); // file2 + assert.isFalse(project.isClosed()); + }); + + it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts" ] + }` + }; + const host = createServerHost([file1, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(project.hasOpenRef()); // file1 + + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(libFile.path); + checkNumberOfConfiguredProjects(projectService, 0); + assert.isFalse(project.hasOpenRef()); // No files + project closed + assert.isTrue(project.isClosed()); + }); + + it("open file become a part of configured project if it is referenced from root file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "export let x = 5" + }; + const file2 = { + path: "/a/c/f2.ts", + content: `import {x} from "../b/f1"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: "export let y = 1" + }; + const configFile = { + path: "/a/c/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + host.reloadFS([file1, file2, file3, configFile]); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.isTrue(projectService.inferredProjects[1].isOrphan()); + }); + + it("can correctly update configured project when set of root files has changed (new file on disk)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + host.reloadFS([file1, file2, configFile]); + + host.checkTimeoutQueueLengthAndRun(2); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); + + it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + const modifiedConfigFile = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + host.reloadFS([file1, file2, modifiedConfigFile]); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); + + it("can update configured project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); + + const modifiedConfigFile = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] }) + }; + + host.reloadFS([file1, file2, modifiedConfigFile]); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); + + it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { + const file1: File = { + path: "/a/b/src/file1.ts", + content: "let x = 1;" + }; + const file2: File = { + path: "/a/b/src/file2.ts", + content: "let y = 1;" + }; + const file3: File = { + path: "/a/b/file3.ts", + content: "let z = 1;" + }; + const file4: File = { + path: "/a/file4.ts", + content: "let z = 1;" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) + }; + + const files = [file1, file2, file3, file4]; + const host = createServerHost(files.concat(configFile)); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + projectService.openClientFile(file4.path); + + const infos = files.map(file => projectService.getScriptInfoForPath(file.path as Path)!); + checkOpenFiles(projectService, files); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + const configProject1 = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 + checkProjectActualFiles(configProject1, [file1.path, file3.path, configFile.path]); + const inferredProject1 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject1, [file2.path]); + const inferredProject2 = projectService.inferredProjects[1]; + checkProjectActualFiles(inferredProject2, [file4.path]); + + configFile.content = "{}"; + host.reloadFS(files.concat(configFile)); + host.runQueuedTimeoutCallbacks(); + + verifyScriptInfos(); + checkOpenFiles(projectService, files); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file1, file2, file3 + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + const inferredProject3 = projectService.inferredProjects[1]; + checkProjectActualFiles(inferredProject3, [file4.path]); + assert.strictEqual(inferredProject3, inferredProject2); + + projectService.closeClientFile(file1.path); + projectService.closeClientFile(file2.path); + projectService.closeClientFile(file4.path); + + verifyScriptInfos(); + checkOpenFiles(projectService, [file3]); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 2); // file3 + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.isTrue(projectService.inferredProjects[1].isOrphan()); + + projectService.openClientFile(file4.path); + verifyScriptInfos(); + checkOpenFiles(projectService, [file3, file4]); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ true, 1); // file3 + const inferredProject4 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject4, [file4.path]); + + projectService.closeClientFile(file3.path); + verifyScriptInfos(); + checkOpenFiles(projectService, [file4]); + verifyConfiguredProjectStateAfterUpdate(/*hasOpenRef*/ false, 1); // No open files + const inferredProject5 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject4, [file4.path]); + assert.strictEqual(inferredProject5, inferredProject4); + + const file5: File = { + path: "/file5.ts", + content: "let zz = 1;" + }; + host.reloadFS(files.concat(configFile, file5)); + projectService.openClientFile(file5.path); + verifyScriptInfosAreUndefined([file1, file2, file3]); + assert.strictEqual(projectService.getScriptInfoForPath(file4.path as Path), find(infos, info => info.path === file4.path)); + assert.isDefined(projectService.getScriptInfoForPath(file5.path as Path)); + checkOpenFiles(projectService, [file4, file5]); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file5.path]); + + function verifyScriptInfos() { + infos.forEach(info => assert.strictEqual(projectService.getScriptInfoForPath(info.path), info)); + } + + function verifyScriptInfosAreUndefined(files: File[]) { + for (const file of files) { + assert.isUndefined(projectService.getScriptInfoForPath(file.path as Path)); + } + } + + function verifyConfiguredProjectStateAfterUpdate(hasOpenRef: boolean, inferredProjects: number) { + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects }); + const configProject2 = projectService.configuredProjects.get(configFile.path)!; + assert.strictEqual(configProject2, configProject1); + checkProjectActualFiles(configProject2, [file1.path, file2.path, file3.path, configFile.path]); + assert.equal(configProject2.hasOpenRef(), hasOpenRef); + } + }); + + it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { + const file1: File = { + path: "/a/b/src/file1.ts", + content: "let x = 1;" + }; + const file2: File = { + path: "/a/b/src/file2.ts", + content: "let y = 1;" + }; + const file3: File = { + path: "/a/b/file3.ts", + content: "let z = 1;" + }; + const file4: File = { + path: "/a/file4.ts", + content: "let z = 1;" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) + }; + + const files = [file1, file2, file3]; + const hostFiles = files.concat(file4, configFile); + const host = createServerHost(hostFiles); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + const configuredProject = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 + checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); + const inferredProject1 = projectService.inferredProjects[0]; + checkProjectActualFiles(inferredProject1, [file2.path]); + + projectService.closeClientFile(file1.path); + projectService.closeClientFile(file3.path); + assert.isFalse(configuredProject.hasOpenRef()); // No files + + configFile.content = "{}"; + host.reloadFS(files.concat(configFile)); + // Time out is not yet run so there is project update pending + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project + + projectService.openClientFile(file4.path); + + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); + const inferredProject2 = projectService.inferredProjects[1]; + checkProjectActualFiles(inferredProject2, [file4.path]); + + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // file2 + checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); + assert.isTrue(inferredProject1.isOrphan()); + assert.strictEqual(projectService.inferredProjects[1], inferredProject2); + checkProjectActualFiles(inferredProject2, [file4.path]); + }); + + it("files are properly detached when language service is disabled", () => { + const f1 = { + path: "/a/app.js", + content: "var x = 1" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const f3 = { + path: "/a/lib.js", + content: "var x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = createServerHost([f1, f2, f3, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const projectService = createProjectService(host); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f1 + assert.isFalse(project.isClosed()); + + projectService.closeClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + for (const f of [f1, f2, f3]) { + // All the script infos should be present and contain the project since it is still alive. + const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))!; + assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); + assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); + } + + const f4 = { + path: "/aa.js", + content: "var x = 1" + }; + host.reloadFS([f1, f2, f3, config, f4]); + projectService.openClientFile(f4.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isFalse(project.hasOpenRef()); // No files + assert.isTrue(project.isClosed()); + + for (const f of [f1, f2, f3]) { + // All the script infos should not be present since the project is closed and orphan script infos are collected + assert.isUndefined(projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))); + } + }); + + it("syntactic features work even if language service is disabled", () => { + const f1 = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const host = createServerHost([f1, f2, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); + session.executeCommand({ + seq: 0, + type: "request", + command: "open", + arguments: { file: f1.path } + }); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + assert.isFalse(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 1, "should receive event"); + assert.equal(events[0].data.project, project, "project name"); + assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); + + const options = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); + const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options); + assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]); + }); + }); + + describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { + it("should be tolerated without crashing the server", () => { + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "include": ["app/*", "test/**/*", "something"] + }` + }; + const file1 = { + path: "/a/b/file1.ts", + content: "let t = 10;" + }; + + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + host.runQueuedTimeoutCallbacks(); + // Since there is no file open from configFile it would be closed + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); + + const inferredProject = projectService.inferredProjects[0]; + assert.isTrue(inferredProject.containsFile(file1.path)); + }); + + it("should be able to handle @types if input file list is empty", () => { + const f = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compiler: {}, + files: [] + }) + }; + const t1 = { + path: "/a/node_modules/@types/typings/index.d.ts", + content: `export * from "./lib"` + }; + const t2 = { + path: "/a/node_modules/@types/typings/lib.d.ts", + content: `export const x: number` + }; + const host = createServerHost([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); + 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 }); + }); + + 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 }); + }); + + it("Changed module resolution reflected when specifying files list", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: 'import classc from "file2"' + }; + const file2a: File = { + path: "/a/file2.ts", + content: "export classc { method2a() { return 10; } }" + }; + const file2: File = { + path: "/a/b/file2.ts", + content: "export classc { method2() { return 10; } }" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) + }; + const files = [file1, file2a, configFile, libFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + checkProjectActualFiles(project, map(files, file => file.path)); + checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); + checkWatchedDirectoriesDetailed(host, ["/a/b"], 1, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + + files.push(file2); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); + checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + + // On next file open the files file2a should be closed and not watched any more + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, ["/a/b/node_modules/@types"], 1, /*recursive*/ true); + }); + + it("Failed lookup locations uses parent most node_modules directory", () => { + const root = "/user/username/rootfolder"; + const file1: File = { + path: "/a/b/src/file1.ts", + content: 'import { classc } from "module1"' + }; + const module1: File = { + path: "/a/b/node_modules/module1/index.d.ts", + content: `import { class2 } from "module2"; + export classc { method2a(): class2; }` + }; + const module2: File = { + path: "/a/b/node_modules/module2/index.d.ts", + content: "export class2 { method2() { return 10; } }" + }; + const module3: File = { + path: "/a/b/node_modules/module/node_modules/module3/index.d.ts", + content: "export class3 { method2() { return 10; } }" + }; + const configFile: File = { + path: "/a/b/src/tsconfig.json", + content: JSON.stringify({ files: ["file1.ts"] }) + }; + const nonLibFiles = [file1, module1, module2, module3, configFile]; + nonLibFiles.forEach(f => f.path = root + f.path); + const files = nonLibFiles.concat(libFile); + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + checkProjectActualFiles(project, [file1.path, libFile.path, module1.path, module2.path, configFile.path]); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + const watchedRecursiveDirectories = getTypeRootsFromLocation(root + "/a/b/src"); + watchedRecursiveDirectories.push(`${root}/a/b/src/node_modules`, `${root}/a/b/node_modules`); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts new file mode 100644 index 00000000000..231c46c350b --- /dev/null +++ b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts @@ -0,0 +1,51 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => { + it("language service disabled events are triggered", () => { + const f1 = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const configWithExclude = { + path: config.path, + content: JSON.stringify({ exclude: ["largefile.js"] }) + }; + const host = createServerHost([f1, f2, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); + session.executeCommand({ + seq: 0, + type: "request", + command: "open", + arguments: { file: f1.path } + }); + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + assert.isFalse(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 1, "should receive event"); + assert.equal(events[0].data.project, project, "project name"); + assert.equal(events[0].data.project.getProjectName(), config.path, "config path"); + assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); + + host.reloadFS([f1, f2, configWithExclude]); + host.checkTimeoutQueueLengthAndRun(2); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isTrue(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 2, "should receive event"); + assert.equal(events[1].data.project, project, "project"); + assert.equal(events[1].data.project.getProjectName(), config.path, "config path"); + assert.isTrue(events[1].data.languageServiceEnabled, "Language service state"); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/events/surveyReady.ts b/src/testRunner/unittests/tsserver/events/surveyReady.ts new file mode 100644 index 00000000000..b04746800d0 --- /dev/null +++ b/src/testRunner/unittests/tsserver/events/surveyReady.ts @@ -0,0 +1,111 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: events:: SurveyReady", () => { + function createSessionWithEventHandler(host: TestServerHost) { + const { session, events: surveyEvents } = createSessionWithEventTracking(host, server.SurveyReady); + + return { session, verifySurveyReadyEvent }; + + function verifySurveyReadyEvent(numberOfEvents: number) { + assert.equal(surveyEvents.length, numberOfEvents); + const expectedEvents = numberOfEvents === 0 ? [] : [{ + eventName: server.SurveyReady, + data: { surveyId: "checkJs" } + }]; + assert.deepEqual(surveyEvents, expectedEvents); + } + } + + it("doesn't log an event when checkJs isn't set", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + const service = session.getProjectService(); + openFilesForSession([file], session); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = service.configuredProjects.get(tsconfig.path)!; + checkProjectActualFiles(project, [file.path, tsconfig.path]); + + verifySurveyReadyEvent(0); + }); + + it("logs an event when checkJs is set", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { checkJs: true } }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); + }); + + it("logs an event when checkJs is set, only the first time", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const rando: File = { + path: `/rando/calrissian.ts`, + content: "export function f() { }" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { checkJs: true } }), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); + + closeFilesForSession([file], session); + openFilesForSession([rando], session); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); + }); + + it("logs an event when checkJs is set after closing and reopening", () => { + const projectRoot = "/user/username/projects/project"; + const file: File = { + path: `${projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const rando: File = { + path: `/rando/calrissian.ts`, + content: "export function f() { }" + }; + const tsconfig: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({}), + }; + const host = createServerHost([file, tsconfig]); + const { session, verifySurveyReadyEvent } = createSessionWithEventHandler(host); + openFilesForSession([file], session); + + verifySurveyReadyEvent(0); + + closeFilesForSession([file], session); + openFilesForSession([rando], session); + host.writeFile(tsconfig.path, JSON.stringify({ compilerOptions: { checkJs: true } })); + openFilesForSession([file], session); + + verifySurveyReadyEvent(1); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 87615b24d12..12a97151f4e 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -1,5 +1,469 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: ExternalProjects", () => { + describe("can handle tsconfig file name with difference casing", () => { + function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + include: [] + }) + }; + + const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); + const service = createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); + service.openExternalProject({ + projectFileName: "/a/b/project.csproj", + rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + if (lazyConfiguredProjectsFromExternalProject) { + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + checkProjectActualFiles(project, emptyArray); + } + else { + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + checkProjectActualFiles(project, [upperCaseConfigFilePath]); + } + + service.openClientFile(f1.path); + service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + + assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated + checkProjectActualFiles(project, [upperCaseConfigFilePath]); + checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); + + it("remove not-listed external projects", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const f2 = { + path: "/b/app.ts", + content: "let x = 1" + }; + const f3 = { + path: "/c/app.ts", + content: "let x = 1" + }; + const makeProject = (f: File) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} }); + const p1 = makeProject(f1); + const p2 = makeProject(f2); + const p3 = makeProject(f3); + + const host = createServerHost([f1, f2, f3]); + const session = createSession(host); + + session.executeCommand({ + seq: 1, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p2] } + }); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); + + session.executeCommand({ + seq: 2, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p3] } + }); + checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [] } + }); + checkNumberOfProjects(projectService, { externalProjects: 0 }); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p2] } + }); + assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); + }); + + it("should not close external project with no open files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y =1;" + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, file2.path]), + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + // close client file - external project should still exists + projectService.closeClientFile(file1.path); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + projectService.closeExternalProject(externalProjectName); + checkNumberOfExternalProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 0); + }); + + it("external project for dynamic file", () => { + const externalProjectName = "^ScriptDocument1 file1.ts"; + const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]); + const host = createServerHost([]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: externalFiles, + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + + externalFiles[0].content = "let x =1;"; + projectService.applyChangesInOpenFiles(externalFiles, [], []); + }); + + it("external project that included config files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + const file2 = { + path: "/a/c/f2.ts", + content: "let y =1;" + }; + const config2 = { + path: "/a/c/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f2.ts"] + } + ) + }; + const file3 = { + path: "/a/d/f3.ts", + content: "let z =1;" + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2, file3, config1, config2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + const proj1 = projectService.configuredProjects.get(config1.path); + const proj2 = projectService.configuredProjects.get(config2.path); + assert.isDefined(proj1); + assert.isDefined(proj2); + + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + + projectService.openClientFile(file3.path, file3.content); + checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + + projectService.closeExternalProject(externalProjectName); + // open file 'file1' from configured project keeps project alive + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + + projectService.closeClientFile(file3.path); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.openClientFile(file2.path, file2.content); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config1.path)); + assert.isDefined(projectService.configuredProjects.get(config2.path)); + }); + + it("external project with included config file opened after configured project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + projectService.openExternalProject({ + rootFiles: toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + projectService.closeClientFile(file1.path); + // configured project is alive since it is opened as part of external project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + projectService.closeExternalProject(externalProjectName); + checkNumberOfProjects(projectService, { configuredProjects: 0 }); + }); + + it("external project with included config file opened after configured project and then closed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/f2.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2, libFile, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); + + projectService.openExternalProject({ + rootFiles: toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + + projectService.closeExternalProject(externalProjectName); + // configured project is alive since file is still open + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(configFile.path)); + }); + + it("can correctly update external project when set of root files has changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + }); + + it("can update external project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "m"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let y = 1" + }; + const file3 = { + path: "/a/m.ts", + content: "export let y = 1" + }; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); + }); + + it("language service disabled state is updated in external projects", () => { + const f1 = { + path: "/a/app.js", + content: "var x = 1" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const host = createServerHost([f1, f2]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const service = createProjectService(host); + const projectFileName = "/a/proj.csproj"; + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path]), + options: {} + }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); + + service.openExternalProject({ + projectFileName, + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {} + }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); + }); + + describe("deleting config file opened from the external project works", () => { + function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) { + const site = { + path: "/user/someuser/project/js/site.js", + content: "" + }; + const configFile = { + path: "/user/someuser/project/tsconfig.json", + content: "{}" + }; + const projectFileName = "/user/someuser/project/WebApplication6.csproj"; + const host = createServerHost([libFile, site, configFile]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + const externalProject: protocol.ExternalProject = { + projectFileName, + rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], + options: { allowJs: false }, + typeAcquisition: { include: [] } + }; + + projectService.openExternalProjects([externalProject]); + + let knownProjects = projectService.synchronizeProjectList([]); + checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); + + const configProject = configuredProjectAt(projectService, 0); + checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ? + emptyArray : // Since no files opened from this project, its not loaded + [configFile.path]); + + host.reloadFS([libFile, site]); + host.checkTimeoutQueueLengthAndRun(1); + + knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); + + externalProject.rootFiles.length = 1; + projectService.openExternalProjects([externalProject]); + + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); + checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true); + }); + }); + describe("correctly handling add/remove tsconfig - 1", () => { function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { const f1 = { diff --git a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts new file mode 100644 index 00000000000..1f9576e1d63 --- /dev/null +++ b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts @@ -0,0 +1,12 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: getApplicableRefactors", () => { + it("works when taking position", () => { + const aTs: File = { path: "/a.ts", content: "" }; + const session = createSession(createServerHost([aTs])); + openFilesForSession([aTs], session); + const response = executeSessionRequest( + session, protocol.CommandTypes.GetApplicableRefactors, { file: aTs.path, line: 1, offset: 1 }); + assert.deepEqual | undefined>(response, []); + }); + }); +} diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index 47e871efb82..9d7ccaa1224 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -1,5 +1,125 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: Inferred projects", () => { + it("create inferred project", () => { + const appFile: File = { + path: "/a/b/c/app.ts", + content: ` + import {f} from "./module" + console.log(f) + ` + }; + + const moduleFile: File = { + path: "/a/b/c/module.d.ts", + content: `export let x: number` + }; + const host = createServerHost([appFile, moduleFile, libFile]); + const projectService = createProjectService(host); + const { configFileName } = projectService.openClientFile(appFile.path); + + assert(!configFileName, `should not find config, got: '${configFileName}`); + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); + + const project = projectService.inferredProjects[0]; + + checkArray("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); + const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; + const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); + checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); + checkWatchedDirectories(host, ["/a/b/c"], /*recursive*/ false); + checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); + }); + + it("should use only one inferred project if 'useOneInferredProject' is set", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts" ] + }` + }; + const file2 = { + path: "/a/c/main.ts", + content: "let x =1;" + }; + + const file3 = { + path: "/a/d/main.ts", + content: "let x =1;" + }; + + const host = createServerHost([file1, file2, file3, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]); + + + host.reloadFS([file1, configFile, file2, file3, libFile]); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); + }); + + it("disable inferred project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + + const host = createServerHost([file1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + + projectService.openClientFile(file1.path, file1.content); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isFalse(proj.languageServiceEnabled); + }); + + it("project settings for inferred projects", () => { + const file1 = { + path: "/a/b/app.ts", + content: `import {x} from "mod"` + }; + const modFile = { + path: "/a/mod.ts", + content: "export let x: number" + }; + const host = createServerHost([file1, modFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(modFile.path); + + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + const inferredProjects = projectService.inferredProjects.slice(); + checkProjectActualFiles(inferredProjects[0], [file1.path]); + checkProjectActualFiles(inferredProjects[1], [modFile.path]); + + projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic }); + host.checkTimeoutQueueLengthAndRun(3); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + checkProjectActualFiles(inferredProjects[0], [file1.path, modFile.path]); + assert.isTrue(inferredProjects[1].isOrphan()); + }); + it("should support files without extensions", () => { const f = { path: "/a/compile", diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts new file mode 100644 index 00000000000..00941a1d3c9 --- /dev/null +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -0,0 +1,1426 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: Projects", () => { + it("handles the missing files - that were added to program because they were added with /// { + const file1: File = { + path: "/a/b/commonFile1.ts", + content: `/// + let x = y` + }; + const host = createServerHost([file1, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + checkNumberOfInferredProjects(projectService, 1); + const project = projectService.inferredProjects[0]; + checkProjectRootFiles(project, [file1.path]); + checkProjectActualFiles(project, [file1.path, libFile.path]); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + + // Two errors: CommonFile2 not found and cannot find name y + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] }, + { diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] } + ]); + + host.reloadFS([file1, commonFile2, libFile]); + host.runQueuedTimeoutCallbacks(); + checkNumberOfInferredProjects(projectService, 1); + assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); + checkProjectRootFiles(project, [file1.path]); + checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); + + it("should create new inferred projects for files excluded from a configured project", () => { + const configFile: File = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "files": ["${commonFile1.path}", "${commonFile2.path}"] + }` + }; + const files = [commonFile1, commonFile2, configFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + configFile.content = `{ + "compilerOptions": {}, + "files": ["${commonFile1.path}"] + }`; + host.reloadFS(files); + + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path]); + + projectService.openClientFile(commonFile2.path); + checkNumberOfInferredProjects(projectService, 1); + }); + + it("should disable features when the files are too large", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 10 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + const file3 = { + path: "/a/b/f3.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + + const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); + const proj1 = projectService.findProject(proj1name)!; + assert.isTrue(proj1.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); + const proj2 = projectService.findProject(proj2name)!; + assert.isTrue(proj2.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); + const proj3 = projectService.findProject(proj3name)!; + assert.isFalse(proj3.languageServiceEnabled); + }); + + describe("ignoreConfigFiles", () => { + it("external project including config file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + + const externalProjectName = "externalproject"; + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, config1.path]), + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const proj = projectService.externalProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); + }); + + it("loose file included in config file (openClientFile)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + projectService.openClientFile(file1.path, file1.content); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); + }); + + it("loose file included in config file (applyCodeChanges)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + projectService.applyChangesInOpenFiles([{ fileName: file1.path, content: file1.content }], [], []); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); + }); + }); + + it("reload regular file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: "x." + }; + const f2 = { + path: "/a/b/lib.ts", + content: "let x: number;" + }; + + const host = createServerHost([f1, f2, libFile]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); + + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let x: string"); + + service.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); + + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; + // should contain completions for string + assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); + assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); + + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; + // should contain completions for string + assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); + assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); + }); + + it("clear mixed content file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: " " + }; + const f2 = { + path: "/a/b/lib.html", + content: "" + }; + + const host = createServerHost([f1, f2, libFile]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); + + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let somelongname: string"); + + service.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); + + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; + assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); + + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; + assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); + const sf2 = service.externalProjects[0].getLanguageService().getProgram()!.getSourceFile(f2.path)!; + assert.equal(sf2.text, ""); + }); + + it("changes in closed files are reflected in project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const inferredProject0 = projectService.inferredProjects[0]; + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); + const inferredProject1 = projectService.inferredProjects[1]; + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + const modifiedFile2 = { + path: file2.path, + content: `export * from "../c/f3"` // now inferred project should inclule file3 + }; + + host.reloadFS([file1, modifiedFile2, file3]); + host.checkTimeoutQueueLengthAndRun(2); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]); + assert.strictEqual(projectService.inferredProjects[1], inferredProject1); + assert.isTrue(inferredProject1.isOrphan()); + }); + + it("deleted files affect project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + + host.reloadFS([file1, file3]); + host.checkTimeoutQueueLengthAndRun(2); + + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + }); + + it("ignores files excluded by a custom safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const office = { + path: "/lib/duckquack-3.min.js", + content: "whoa do @@ not parse me ok thanks!!!" + }; + const host = createServerHost([file1, office, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); + } finally { + projectService.resetSafeList(); + } + }); + + it("file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: `export let x = 5; import { s } from "s"` + }; + const constructorFile = { + path: "/a/b/constructor.js", + content: "const x = 10;" + }; + const bliss = { + path: "/a/b/bliss.js", + content: "export function is() { return true; }" + }; + const host = createServerHost([file1, libFile, constructorFile, bliss, customTypesMap]); + let request: string | undefined; + const cachePath = "/a/data"; + const typingsInstaller: server.ITypingsInstaller = { + isKnownTypesPackageName: returnFalse, + installPackage: notImplemented, + inspectValue: notImplemented, + enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => { + assert.isUndefined(request); + request = JSON.stringify(server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || server.emptyArray, cachePath)); + }, + attach: noop, + onProjectClosed: noop, + globalTypingsCacheLocation: cachePath + }; + + const projectName = "project"; + const projectService = createProjectService(host, { typingsInstaller }); + projectService.openExternalProject({ projectFileName: projectName, options: {}, rootFiles: toExternalFiles([file1.path, constructorFile.path, bliss.path]) }); + assert.equal(request, JSON.stringify({ + projectName, + fileNames: [libFile.path, file1.path, constructorFile.path, bliss.path], + compilerOptions: { allowNonTsExtensions: true, noEmitForJsFiles: true }, + typeAcquisition: { include: ["blissfuljs"], exclude: [], enable: true }, + unresolvedImports: ["s"], + projectRootPath: "/", + cachePath, + kind: "discover" + })); + const response = JSON.parse(request!); + request = undefined; + projectService.updateTypingsForProject({ + kind: "action::set", + projectName: response.projectName, + typeAcquisition: response.typeAcquisition, + compilerOptions: response.compilerOptions, + typings: emptyArray, + unresolvedImports: response.unresolvedImports, + }); + + host.checkTimeoutQueueLengthAndRun(2); + assert.isUndefined(request); + }); + + it("ignores files excluded by the default type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const minFile = { + path: "/c/moment.min.js", + content: "unspecified" + }; + const kendoFile1 = { + path: "/q/lib/kendo/kendo.all.min.js", + content: "unspecified" + }; + const kendoFile2 = { + path: "/q/lib/kendo/kendo.ui.min.js", + content: "unspecified" + }; + const kendoFile3 = { + path: "/q/lib/kendo-ui/kendo.all.js", + content: "unspecified" + }; + const officeFile1 = { + path: "/scripts/Office/1/excel-15.debug.js", + content: "unspecified" + }; + const officeFile2 = { + path: "/scripts/Office/1/powerpoint.js", + content: "unspecified" + }; + const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; + const host = createServerHost(files); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); + } finally { + projectService.resetSafeList(); + } + }); + + it("removes version numbers correctly", () => { + const testData: [string, string][] = [ + ["jquery-max", "jquery-max"], + ["jquery.min", "jquery"], + ["jquery-min.4.2.3", "jquery"], + ["jquery.min.4.2.1", "jquery"], + ["minimum", "minimum"], + ["min", "min"], + ["min.3.2", "min"], + ["jquery", "jquery"] + ]; + for (const t of testData) { + assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); + } + }); + + it("ignores files excluded by a legacy safe type list", () => { + const file1 = { + path: "/a/b/bliss.js", + content: "let x = 5" + }; + const file2 = { + path: "/a/b/foo.js", + content: "" + }; + const file3 = { + path: "/a/b/Bacon.js", + content: "let y = 5" + }; + const host = createServerHost([file1, file2, file3, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(), [file2.path]); + } finally { + projectService.resetSafeList(); + } + }); + + it("correctly migrate files between projects", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` + export * from "../c/f2"; + export * from "../d/f3";` + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + let inferredProjects = projectService.inferredProjects.slice(); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[1]); + checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 3 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.closeClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 3 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + assert.strictEqual(projectService.inferredProjects[2], inferredProjects[2]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + assert.isTrue(projectService.inferredProjects[2].isOrphan()); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[2]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + checkProjectActualFiles(projectService.inferredProjects[0], [file3.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); + + it("regression test for crash in acquireOrUpdateDocument", () => { + const tsFile = { + fileName: "/a/b/file1.ts", + path: "/a/b/file1.ts", + content: "" + }; + const jsFile = { + path: "/a/b/file1.js", + content: "var x = 10;", + fileName: "/a/b/file1.js", + scriptKind: "JS" as "JS" + }; + + const host = createServerHost([]); + const projectService = createProjectService(host); + projectService.applyChangesInOpenFiles([tsFile], [], []); + const projs = projectService.synchronizeProjectList([]); + projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); + projectService.synchronizeProjectList([projs[0].info!]); + projectService.applyChangesInOpenFiles([jsFile], [], []); + }); + + it("config file is deleted", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = createServerHost([file1, file2, config]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + host.reloadFS([file1, file2]); + host.checkTimeoutQueueLengthAndRun(1); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); + + it("loading files with correct priority", () => { + const f1 = { + path: "/a/main.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/main.js", + content: "var y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { allowJs: true } + }) + }; + const host = createServerHost([f1, f2, config]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ + extraFileExtensions: [ + { extension: ".js", isMixedContent: false }, + { extension: ".html", isMixedContent: true } + ] + }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); + + // Should close configured project with next file open + projectService.closeClientFile(f1.path); + + projectService.openClientFile(f2.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config.path)); + checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); + }); + + it("tsconfig script block support", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = createServerHost([file1, file2, config]); + const session = createSession(host); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + // HTML file will not be included in any projects yet + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const configuredProj = configuredProjectAt(projectService, 0); + checkProjectActualFiles(configuredProj, [file1.path, config.path]); + + // Specify .html extension as mixed content + const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); + + // The configured project should now be updated to include html file + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Open HTML file + projectService.applyChangesInOpenFiles( + /*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }], + /*changedFiles*/ undefined, + /*closedFiles*/ undefined); + + // Now HTML file is included in the project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Check identifiers defined in HTML content are available in .ts file + const project = configuredProjectAt(projectService, 0); + let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions); + assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`); + + // Close HTML file + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + /*changedFiles*/ undefined, + /*closedFiles*/[file2.path]); + + // HTML file is still included in project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Check identifiers defined in HTML content are not available in .ts file + completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, emptyOptions); + assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); + }); + + it("no tsconfig script block diagnostic errors", () => { + + // #1. Ensure no diagnostic errors when allowJs is true + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + + let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + let session = createSession(host); + + // Specify .html extension as mixed content in a configure host request + const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); + + openFilesForSession([file1], session); + let projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + + // #2. Ensure no errors when allowJs is false + const config2 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: false } }) + }; + + host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); + + session.executeCommand(configureHostRequest); + + openFilesForSession([file1], session); + projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + + // #3. Ensure no errors when compiler options aren't specified + const config3 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; + + host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); + + session.executeCommand(configureHostRequest); + + openFilesForSession([file1], session); + projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + + // #4. Ensure no errors when files are explicitly specified in tsconfig + const config4 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) + }; + + host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); + + session.executeCommand(configureHostRequest); + + openFilesForSession([file1], session); + projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + + // #4. Ensure no errors when files are explicitly excluded in tsconfig + const config5 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] }) + }; + + host = createServerHost([file1, file2, config5, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); + + session.executeCommand(configureHostRequest); + + openFilesForSession([file1], session); + projectService = session.getProjectService(); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); + + it("project structure update is deferred if files are not added\removed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `import {x} from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let x = 1" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + /*changedFiles*/[{ fileName: file1.path, changes: [{ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }] }], + /*closedFiles*/ undefined); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.ensureInferredProjectsUpToDate_TestOnly(); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + }); + + it("files with mixed content are handled correctly", () => { + const file1 = { + path: "/a/b/f1.html", + content: `