Adding test for #16456 to verify watched directories in case-sensitive and non sensitive file system and fixing caching
This commit is contained in:
parent
7173da2134
commit
e500be28cd
|
@ -2076,8 +2076,8 @@ namespace ts {
|
|||
}
|
||||
|
||||
export interface FileSystemEntries {
|
||||
files: ReadonlyArray<string>;
|
||||
directories: ReadonlyArray<string>;
|
||||
readonly files: ReadonlyArray<string>;
|
||||
readonly directories: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface FileMatcherPatterns {
|
||||
|
@ -2644,8 +2644,13 @@ namespace ts {
|
|||
export interface CachedPartialSystem extends PartialSystem, CachedHost {
|
||||
}
|
||||
|
||||
interface MutableFileSystemEntries {
|
||||
readonly files: string[];
|
||||
readonly directories: string[];
|
||||
}
|
||||
|
||||
export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem {
|
||||
const cachedReadDirectoryResult = createMap<FileSystemEntries>();
|
||||
const cachedReadDirectoryResult = createMap<MutableFileSystemEntries>();
|
||||
const getCurrentDirectory = memoize(() => host.getCurrentDirectory());
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
return {
|
||||
|
@ -2665,11 +2670,11 @@ namespace ts {
|
|||
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
|
||||
}
|
||||
|
||||
function getCachedFileSystemEntries(rootDirPath: Path): FileSystemEntries | undefined {
|
||||
function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined {
|
||||
return cachedReadDirectoryResult.get(rootDirPath);
|
||||
}
|
||||
|
||||
function getCachedFileSystemEntriesForBaseDir(path: Path): FileSystemEntries | undefined {
|
||||
function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined {
|
||||
return getCachedFileSystemEntries(getDirectoryPath(path));
|
||||
}
|
||||
|
||||
|
@ -2678,8 +2683,8 @@ namespace ts {
|
|||
}
|
||||
|
||||
function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) {
|
||||
const resultFromHost: FileSystemEntries = {
|
||||
files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [],
|
||||
const resultFromHost: MutableFileSystemEntries = {
|
||||
files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [],
|
||||
directories: host.getDirectories(rootDir) || []
|
||||
};
|
||||
|
||||
|
@ -2692,7 +2697,7 @@ namespace ts {
|
|||
* Otherwise gets result from host and caches it.
|
||||
* The host request is done under try catch block to avoid caching incorrect result
|
||||
*/
|
||||
function tryReadDirectory(rootDir: string, rootDirPath: Path): FileSystemEntries | undefined {
|
||||
function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined {
|
||||
const cachedResult = getCachedFileSystemEntries(rootDirPath);
|
||||
if (cachedResult) {
|
||||
return cachedResult;
|
||||
|
@ -2716,16 +2721,15 @@ namespace ts {
|
|||
return some(entries, file => fileNameEqual(file, name));
|
||||
}
|
||||
|
||||
function updateFileSystemEntry(entries: ReadonlyArray<string>, baseName: string, isValid: boolean) {
|
||||
function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) {
|
||||
if (hasEntry(entries, baseName)) {
|
||||
if (!isValid) {
|
||||
return filter(entries, entry => !fileNameEqual(entry, baseName));
|
||||
return filterMutate(entries, entry => !fileNameEqual(entry, baseName));
|
||||
}
|
||||
}
|
||||
else if (isValid) {
|
||||
return entries.concat(baseName);
|
||||
return entries.push(baseName);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
|
||||
|
@ -2754,7 +2758,7 @@ namespace ts {
|
|||
const result = getCachedFileSystemEntriesForBaseDir(path);
|
||||
const baseFileName = getBaseNameOfFileName(dirPath);
|
||||
if (result) {
|
||||
result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
|
||||
updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true);
|
||||
}
|
||||
host.createDirectory(dirPath);
|
||||
}
|
||||
|
@ -2801,7 +2805,7 @@ namespace ts {
|
|||
const baseName = getBaseNameOfFileName(fileOrFolder);
|
||||
if (parentResult) {
|
||||
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath));
|
||||
parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath));
|
||||
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2818,8 +2822,8 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function updateFilesOfFileSystemEntry(parentResult: FileSystemEntries, baseName: string, fileExists: boolean) {
|
||||
parentResult.files = updateFileSystemEntry(parentResult.files, baseName, fileExists);
|
||||
function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) {
|
||||
updateFileSystemEntry(parentResult.files, baseName, fileExists);
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace ts {
|
|||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
|
||||
invalidateResolutionOfFile(filePath: Path): void;
|
||||
invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void;
|
||||
invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void;
|
||||
|
||||
createHasInvalidatedResolution(): HasInvalidatedResolution;
|
||||
|
||||
|
@ -300,12 +300,12 @@ namespace ts {
|
|||
}
|
||||
|
||||
function invalidateResolutionCacheOfChangedFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
|
||||
failedLookupLocation: string,
|
||||
failedLookupLocationPath: Path,
|
||||
cache: Map<Map<T>>) {
|
||||
cache.forEach((value, containingFile) => {
|
||||
if (value) {
|
||||
value.forEach(resolution => {
|
||||
if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocation)) {
|
||||
if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) {
|
||||
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
|
||||
resolution.isInvalidated = true;
|
||||
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFile, true);
|
||||
|
@ -320,9 +320,9 @@ namespace ts {
|
|||
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
|
||||
}
|
||||
|
||||
function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string) {
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedModuleNames);
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedTypeReferenceDirectives);
|
||||
function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) {
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames);
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -564,7 +564,7 @@ namespace ts {
|
|||
function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) {
|
||||
writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`);
|
||||
updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind);
|
||||
resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
|
||||
resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
|
@ -633,7 +633,7 @@ namespace ts {
|
|||
// Reload is pending, do the reload
|
||||
if (!needsReload) {
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host);
|
||||
if (!configFileSpecs.filesSpecs) {
|
||||
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
|
||||
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
|
||||
}
|
||||
rootFileNames = result.fileNames;
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace ts {
|
|||
typingsInstaller: undefined
|
||||
};
|
||||
const projectService = new server.ProjectService(svcOpts);
|
||||
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
|
||||
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, serverHost);
|
||||
|
||||
const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo);
|
||||
project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } );
|
||||
|
|
|
@ -130,7 +130,7 @@ namespace ts.tscWatch {
|
|||
const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false });
|
||||
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
|
||||
const watch = createWatchModeWithConfigFile(upperCaseConfigFilePath, host);
|
||||
checkProgramActualFiles(watch(), [f1.path]);
|
||||
checkProgramActualFiles(watch(), [combinePaths(getDirectoryPath(upperCaseConfigFilePath), getBaseFileName(f1.path))]);
|
||||
});
|
||||
|
||||
it("create configured project without file list", () => {
|
||||
|
@ -722,12 +722,13 @@ namespace ts.tscWatch {
|
|||
const host = createWatchedSystem([moduleFile, file1, configFile, libFile]);
|
||||
createWatchModeWithConfigFile(configFile.path, host);
|
||||
|
||||
const error = `error TS6053: File '${moduleFile.path}' not found.${host.newLine}`;
|
||||
const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n";
|
||||
checkOutputDoesNotContain(host, [error]);
|
||||
|
||||
const moduleFileOldPath = moduleFile.path;
|
||||
const moduleFileNewPath = "/a/b/moduleFile1.ts";
|
||||
moduleFile.path = moduleFileNewPath;
|
||||
host.clearOutput();
|
||||
host.reloadFS([moduleFile, file1, configFile, libFile]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkOutputContains(host, [error]);
|
||||
|
|
|
@ -3998,33 +3998,206 @@ namespace ts.projectSystem {
|
|||
});
|
||||
|
||||
describe("CachingFileSystemInformation", () => {
|
||||
function getFunctionWithCalledMapForSingleArgumentCb<T>(cb: (f: string) => T) {
|
||||
const calledMap = createMultiMap<true>();
|
||||
type CalledMaps = {
|
||||
fileExists: MultiMap<true>;
|
||||
directoryExists: MultiMap<true>;
|
||||
getDirectories: MultiMap<true>;
|
||||
readFile: MultiMap<true>;
|
||||
readDirectory: MultiMap<[ReadonlyArray<string>, ReadonlyArray<string>, ReadonlyArray<string>, number]>;
|
||||
};
|
||||
|
||||
function createCallsTrackingHost(host: TestServerHost) {
|
||||
const keys: Array<keyof CalledMaps> = ["fileExists", "directoryExists", "getDirectories", "readFile", "readDirectory"];
|
||||
const calledMaps = getCallsTrackingMap();
|
||||
return {
|
||||
cb: (f: string) => {
|
||||
verifyNoCall,
|
||||
verifyCalledOnEachEntryOnce,
|
||||
verifyCalledOnEachEntry,
|
||||
verifyNoHostCalls,
|
||||
verifyNoHostCallsExceptFileExistsOnce,
|
||||
clear
|
||||
};
|
||||
|
||||
function getCallsTrackingMap() {
|
||||
const calledMaps: { [s: string]: Map<any> } = {};
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
setCallsTrackingWithSingleArgFn(keys[i]);
|
||||
}
|
||||
setCallsTrackingWithFiveArgFn(keys[keys.length - 1]);
|
||||
return calledMaps as CalledMaps;
|
||||
|
||||
function setCallsTrackingWithSingleArgFn(prop: keyof CalledMaps) {
|
||||
const calledMap = createMultiMap<true>();
|
||||
const cb = (<any>host)[prop].bind(host);
|
||||
(<any>host)[prop] = (f: string) => {
|
||||
calledMap.add(f, /*value*/ true);
|
||||
return cb(f);
|
||||
},
|
||||
calledMap
|
||||
};
|
||||
calledMaps[prop] = calledMap;
|
||||
}
|
||||
|
||||
function getFunctionWithCalledMapForFiveArgumentCb<T, U, V, W, X>(cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => T) {
|
||||
function setCallsTrackingWithFiveArgFn<U, V, W, X>(prop: keyof CalledMaps) {
|
||||
const calledMap = createMultiMap<[U, V, W, X]>();
|
||||
return {
|
||||
cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => {
|
||||
const cb = (<any>host)[prop].bind(host);
|
||||
(<any>host)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => {
|
||||
calledMap.add(f, [arg1, arg2, arg3, arg4]);
|
||||
return cb(f, arg1, arg2, arg3, arg4);
|
||||
},
|
||||
calledMap
|
||||
};
|
||||
calledMaps[prop] = calledMap;
|
||||
}
|
||||
}
|
||||
|
||||
function checkMultiMapKeysForSingleEntry<T>(caption: string, multiMap: MultiMap<T>, expectedKeys: string[]) {
|
||||
assert.equal(multiMap.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(multiMap.keys())} Expected: ${expectedKeys}`);
|
||||
for (const name of expectedKeys) {
|
||||
assert.isTrue(multiMap.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(multiMap.keys())}`);
|
||||
assert.equal(multiMap.get(name).length, 1, `${caption} is expected to have just one entry for key ${name}, actual entry: ${multiMap.get(name)}`);
|
||||
function verifyNoCall(callback: keyof CalledMaps) {
|
||||
const calledMap = calledMaps[callback];
|
||||
assert.equal(calledMap.size, 0, `${callback} shouldnt be called: ${arrayFrom(calledMap.keys())}`);
|
||||
}
|
||||
|
||||
function verifyCalledOnEachEntry(callback: keyof CalledMaps, expectedKeys: Map<number>) {
|
||||
const calledMap = calledMaps[callback];
|
||||
assert.equal(calledMap.size, expectedKeys.size, `${callback}: incorrect size of map: Actual keys: ${arrayFrom(calledMap.keys())} Expected: ${arrayFrom(expectedKeys.keys())}`);
|
||||
expectedKeys.forEach((called, name) => {
|
||||
assert.isTrue(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`);
|
||||
assert.equal(calledMap.get(name).length, called, `${callback} is expected to be called ${called} times with ${name}. Actual entry: ${calledMap.get(name)}`);
|
||||
});
|
||||
}
|
||||
|
||||
function verifyCalledOnEachEntryOnce(callback: keyof CalledMaps, expectedKeys: string[]) {
|
||||
return verifyCalledOnEachEntry(callback, zipToMap(expectedKeys, expectedKeys.map(() => 1)));
|
||||
}
|
||||
|
||||
function verifyNoHostCalls() {
|
||||
for (const key of keys) {
|
||||
verifyNoCall(key);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: string[]) {
|
||||
verifyCalledOnEachEntryOnce("fileExists", expectedKeys);
|
||||
verifyNoCall("directoryExists");
|
||||
verifyNoCall("getDirectories");
|
||||
verifyNoCall("readFile");
|
||||
verifyNoCall("readDirectory");
|
||||
}
|
||||
|
||||
function clear() {
|
||||
for (const key of keys) {
|
||||
calledMaps[key].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) {
|
||||
const frontendDir = "/Users/someuser/work/applications/frontend";
|
||||
const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase();
|
||||
const file1: FileOrFolder = {
|
||||
path: `${frontendDir}/src/app/utils/Analytic.ts`,
|
||||
content: "export class SomeClass { };"
|
||||
};
|
||||
const file2: FileOrFolder = {
|
||||
path: `${frontendDir}/src/app/redux/configureStore.ts`,
|
||||
content: "export class configureStore { }"
|
||||
};
|
||||
const file3: FileOrFolder = {
|
||||
path: `${frontendDir}/src/app/utils/Cookie.ts`,
|
||||
content: "export class Cookie { }"
|
||||
};
|
||||
const es2016LibFile: FileOrFolder = {
|
||||
path: "/a/lib/lib.es2016.full.d.ts",
|
||||
content: libFile.content
|
||||
};
|
||||
const tsconfigFile: FileOrFolder = {
|
||||
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": [
|
||||
"node",
|
||||
"jest"
|
||||
],
|
||||
"noUnusedLocals": true,
|
||||
"outDir": "./compiled",
|
||||
"typeRoots": [
|
||||
"types",
|
||||
"node_modules/@types"
|
||||
],
|
||||
"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 = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase();
|
||||
const { configFileName } = projectService.openClientFile(file1.path);
|
||||
assert.equal(configFileName, tsconfigFile.path, `should find config`);
|
||||
checkNumberOfConfiguredProjects(projectService, 1);
|
||||
|
||||
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();
|
||||
callsTrackingHost.verifyCalledOnEachEntryOnce("fileExists", [canonicalFile3Path]);
|
||||
|
||||
// Called for type root resolution
|
||||
const directoryExistsCalled = createMap<number>();
|
||||
for (let dir = frontendDir; dir !== "/"; dir = getDirectoryPath(dir)) {
|
||||
directoryExistsCalled.set(`${dir}/node_modules`, 2);
|
||||
}
|
||||
directoryExistsCalled.set(`/node_modules`, 2);
|
||||
directoryExistsCalled.set(`${frontendDir}/types`, 2);
|
||||
directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2);
|
||||
directoryExistsCalled.set(canonicalFile3Path, 1);
|
||||
callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled);
|
||||
|
||||
callsTrackingHost.verifyNoCall("getDirectories");
|
||||
callsTrackingHost.verifyCalledOnEachEntryOnce("readFile", [file3.path]);
|
||||
callsTrackingHost.verifyNoCall("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 verifyProjectAndWatchedDirectories() {
|
||||
checkProjectActualFiles(project, map(projectFiles, f => f.path));
|
||||
checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true);
|
||||
checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4069,7 +4242,6 @@ namespace ts.projectSystem {
|
|||
]
|
||||
})
|
||||
};
|
||||
|
||||
const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile];
|
||||
const host = createServerHost(projectFiles);
|
||||
const session = createSession(host);
|
||||
|
@ -4082,17 +4254,7 @@ namespace ts.projectSystem {
|
|||
const project = projectService.configuredProjects.get(tsconfigFile.path);
|
||||
checkProjectActualFiles(project, map(projectFiles, f => f.path));
|
||||
|
||||
const fileExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb<boolean>(host.fileExists.bind(host));
|
||||
host.fileExists = fileExistsCalledOn.cb;
|
||||
const directoryExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb<boolean>(host.directoryExists.bind(host));
|
||||
host.directoryExists = directoryExistsCalledOn.cb;
|
||||
const getDirectoriesCalledOn = getFunctionWithCalledMapForSingleArgumentCb<string[]>(host.getDirectories.bind(host));
|
||||
host.getDirectories = getDirectoriesCalledOn.cb;
|
||||
const readFileCalledOn = getFunctionWithCalledMapForSingleArgumentCb<string>(host.readFile.bind(host));
|
||||
host.readFile = readFileCalledOn.cb;
|
||||
const readDirectoryCalledOn = getFunctionWithCalledMapForFiveArgumentCb<string[], ReadonlyArray<string>, ReadonlyArray<string>, ReadonlyArray<string>, number>(host.readDirectory.bind(host));
|
||||
host.readDirectory = readDirectoryCalledOn.cb;
|
||||
|
||||
const callsTrackingHost = createCallsTrackingHost(host);
|
||||
|
||||
// Get definitions shouldnt make host requests
|
||||
const getDefinitionRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(protocol.CommandTypes.Definition, {
|
||||
|
@ -4103,23 +4265,23 @@ namespace ts.projectSystem {
|
|||
});
|
||||
const { response } = session.executeCommand(getDefinitionRequest);
|
||||
assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response));
|
||||
assert.equal(fileExistsCalledOn.calledMap.size, 0, `fileExists shouldnt be called`);
|
||||
assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`);
|
||||
assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`);
|
||||
assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`);
|
||||
assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`);
|
||||
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);
|
||||
checkMultiMapKeysForSingleEntry("fileExists", fileExistsCalledOn.calledMap, ["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]);
|
||||
assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`);
|
||||
assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`);
|
||||
assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`);
|
||||
assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`);
|
||||
callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]);
|
||||
|
||||
checkNumberOfConfiguredProjects(projectService, 1);
|
||||
assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project);
|
||||
});
|
||||
|
||||
it("WatchDirectories for config file With non case sensitive file system", () => {
|
||||
verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false);
|
||||
});
|
||||
|
||||
it("WatchDirectories for config file With case sensitive file system", () => {
|
||||
verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -197,9 +197,12 @@ namespace ts.TestFSWithWatch {
|
|||
this.reloadFS(fileOrFolderList);
|
||||
}
|
||||
|
||||
toNormalizedAbsolutePath(s: string) {
|
||||
return getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||
}
|
||||
|
||||
toFullPath(s: string) {
|
||||
const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory);
|
||||
return this.toPath(fullPath);
|
||||
return this.toPath(this.toNormalizedAbsolutePath(s));
|
||||
}
|
||||
|
||||
reloadFS(fileOrFolderList: FileOrFolder[]) {
|
||||
|
@ -413,7 +416,7 @@ namespace ts.TestFSWithWatch {
|
|||
}
|
||||
|
||||
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
|
||||
return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => {
|
||||
return ts.matchFiles(this.toNormalizedAbsolutePath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => {
|
||||
const directories: string[] = [];
|
||||
const files: string[] = [];
|
||||
const dirEntry = this.fs.get(this.toPath(dir));
|
||||
|
|
|
@ -1429,7 +1429,7 @@ namespace ts.server {
|
|||
else {
|
||||
const scriptKind = propertyReader.getScriptKind(f);
|
||||
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
||||
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent);
|
||||
scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.lsHost.host);
|
||||
path = scriptInfo.path;
|
||||
// If this script info is not already a root add it
|
||||
if (!project.isRoot(scriptInfo)) {
|
||||
|
@ -1583,9 +1583,12 @@ namespace ts.server {
|
|||
* @param uncheckedFileName is absolute pathname
|
||||
* @param fileContent is a known version of the file content that is more up to date than the one on disk
|
||||
*/
|
||||
|
||||
getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
|
||||
return this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName), openedByClient, fileContent, scriptKind);
|
||||
/*@internal*/
|
||||
getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) {
|
||||
return this.getOrCreateScriptInfoForNormalizedPath(
|
||||
toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined,
|
||||
/*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn
|
||||
);
|
||||
}
|
||||
|
||||
getScriptInfo(uncheckedFileName: string) {
|
||||
|
@ -1610,11 +1613,11 @@ namespace ts.server {
|
|||
}
|
||||
}
|
||||
|
||||
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
|
||||
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) {
|
||||
const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName);
|
||||
let info = this.getScriptInfoForPath(path);
|
||||
if (!info) {
|
||||
if (openedByClient || this.host.fileExists(fileName)) {
|
||||
if (openedByClient || (hostToQueryFileExistsOn || this.host).fileExists(fileName)) {
|
||||
info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path);
|
||||
|
||||
this.filenameToScriptInfo.set(info.path, info);
|
||||
|
|
|
@ -240,7 +240,7 @@ namespace ts.server {
|
|||
if (this.projectKind === ProjectKind.Configured) {
|
||||
(this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind);
|
||||
}
|
||||
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
|
||||
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
|
||||
this.markAsDirty();
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
});
|
||||
|
@ -701,7 +701,7 @@ namespace ts.server {
|
|||
// by the LSHost for files in the program when the program is retrieved above but
|
||||
// the program doesn't contain external files so this must be done explicitly.
|
||||
inserted => {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false);
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.lsHost.host);
|
||||
scriptInfo.attachToProject(this);
|
||||
},
|
||||
removed => {
|
||||
|
@ -744,7 +744,7 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
getScriptInfoLSHost(fileName: string) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.lsHost.host);
|
||||
if (scriptInfo) {
|
||||
const existingValue = this.rootFilesMap.get(scriptInfo.path);
|
||||
if (existingValue !== undefined && existingValue !== scriptInfo) {
|
||||
|
@ -758,7 +758,10 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(
|
||||
fileName, /*openedByClient*/ false, /*fileContent*/ undefined,
|
||||
/*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.lsHost.host
|
||||
);
|
||||
if (scriptInfo && !scriptInfo.isAttached(this)) {
|
||||
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue