Skip to content

Commit 12790e8

Browse files
authored
Merge pull request #23910 from Microsoft/moduleUpdates
Support invalidating resolutions that are path mapped into sibling folder of root
2 parents 40e0ab7 + 1a2fda0 commit 12790e8

9 files changed

+682
-420
lines changed

src/compiler/resolutionCache.ts

+40-10
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,15 @@ namespace ts {
5959
watcher: FileWatcher;
6060
/** ref count keeping this directory watch alive */
6161
refCount: number;
62+
/** map of refcount for the subDirectory */
63+
subDirectoryMap?: Map<number>;
6264
}
6365

6466
interface DirectoryOfFailedLookupWatch {
6567
dir: string;
6668
dirPath: Path;
6769
ignore?: true;
70+
subDirectory?: Path;
6871
}
6972

7073
export const maxNumberOfFilesToIterateForInvalidation = 256;
@@ -393,18 +396,20 @@ namespace ts {
393396
}
394397

395398
// Use some ancestor of the root directory
399+
let subDirectory: Path | undefined;
396400
if (rootPath !== undefined) {
397401
while (!isInDirectoryPath(dirPath, rootPath)) {
398402
const parentPath = getDirectoryPath(dirPath);
399403
if (parentPath === dirPath) {
400404
break;
401405
}
406+
subDirectory = dirPath.slice(parentPath.length + directorySeparator.length) as Path;
402407
dirPath = parentPath;
403408
dir = getDirectoryPath(dir);
404409
}
405410
}
406411

407-
return filterFSRootDirectoriesToWatch({ dir, dirPath }, dirPath);
412+
return filterFSRootDirectoriesToWatch({ dir, dirPath, subDirectory }, dirPath);
408413
}
409414

410415
function isPathWithDefaultFailedLookupExtension(path: Path) {
@@ -427,7 +432,7 @@ namespace ts {
427432
let setAtRoot = false;
428433
for (const failedLookupLocation of failedLookupLocations) {
429434
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
430-
const { dir, dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
435+
const { dir, dirPath, ignore , subDirectory } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
431436
if (!ignore) {
432437
// If the failed lookup location path is not one of the supported extensions,
433438
// store it in the custom path
@@ -439,7 +444,7 @@ namespace ts {
439444
setAtRoot = true;
440445
}
441446
else {
442-
setDirectoryWatcher(dir, dirPath);
447+
setDirectoryWatcher(dir, dirPath, subDirectory);
443448
}
444449
}
445450
}
@@ -449,13 +454,20 @@ namespace ts {
449454
}
450455
}
451456

452-
function setDirectoryWatcher(dir: string, dirPath: Path) {
453-
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
457+
function setDirectoryWatcher(dir: string, dirPath: Path, subDirectory?: Path) {
458+
let dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
454459
if (dirWatcher) {
455460
dirWatcher.refCount++;
456461
}
457462
else {
458-
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
463+
dirWatcher = { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 };
464+
directoryWatchesOfFailedLookups.set(dirPath, dirWatcher);
465+
}
466+
467+
if (subDirectory) {
468+
const subDirectoryMap = dirWatcher.subDirectoryMap || (dirWatcher.subDirectoryMap = createMap());
469+
const existing = subDirectoryMap.get(subDirectory) || 0;
470+
subDirectoryMap.set(subDirectory, existing + 1);
459471
}
460472
}
461473

@@ -473,7 +485,7 @@ namespace ts {
473485
let removeAtRoot = false;
474486
for (const failedLookupLocation of failedLookupLocations) {
475487
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
476-
const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
488+
const { dirPath, ignore, subDirectory } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
477489
if (!ignore) {
478490
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
479491
if (refCount) {
@@ -490,7 +502,7 @@ namespace ts {
490502
removeAtRoot = true;
491503
}
492504
else {
493-
removeDirectoryWatcher(dirPath);
505+
removeDirectoryWatcher(dirPath, subDirectory);
494506
}
495507
}
496508
}
@@ -499,12 +511,30 @@ namespace ts {
499511
}
500512
}
501513

502-
function removeDirectoryWatcher(dirPath: string) {
514+
function removeDirectoryWatcher(dirPath: string, subDirectory?: Path) {
503515
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
516+
if (subDirectory) {
517+
const existing = dirWatcher.subDirectoryMap.get(subDirectory);
518+
if (existing === 1) {
519+
dirWatcher.subDirectoryMap.delete(subDirectory);
520+
}
521+
else {
522+
dirWatcher.subDirectoryMap.set(subDirectory, existing - 1);
523+
}
524+
}
504525
// Do not close the watcher yet since it might be needed by other failed lookup locations.
505526
dirWatcher.refCount--;
506527
}
507528

529+
function inWatchedSubdirectory(dirPath: Path, fileOrDirectoryPath: Path) {
530+
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
531+
if (!dirWatcher || !dirWatcher.subDirectoryMap) return false;
532+
return forEachKey(dirWatcher.subDirectoryMap, subDirectory => {
533+
const fullSubDirectory = `${dirPath}/${subDirectory}` as Path;
534+
return fullSubDirectory === fileOrDirectoryPath || isInDirectoryPath(fullSubDirectory, fileOrDirectoryPath);
535+
});
536+
}
537+
508538
function createDirectoryWatcher(directory: string, dirPath: Path) {
509539
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
510540
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
@@ -516,7 +546,7 @@ namespace ts {
516546
// If the files are added to project root or node_modules directory, always run through the invalidation process
517547
// Otherwise run through invalidation only if adding to the immediate directory
518548
if (!allFilesHaveInvalidatedResolution &&
519-
dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath) {
549+
(dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath || inWatchedSubdirectory(dirPath, fileOrDirectoryPath))) {
520550
if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
521551
resolutionHost.onInvalidatedResolution();
522552
}

src/harness/unittests/compileOnSave.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace ts.projectSystem {
1111
}
1212

1313
describe("CompileOnSave affected list", () => {
14-
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) {
14+
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) {
1515
const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[];
1616
const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName));
1717
expectedFileList = expectedFileList.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName));
@@ -47,12 +47,12 @@ namespace ts.projectSystem {
4747
}
4848

4949
describe("for configured projects", () => {
50-
let moduleFile1: FileOrFolder;
51-
let file1Consumer1: FileOrFolder;
52-
let file1Consumer2: FileOrFolder;
53-
let moduleFile2: FileOrFolder;
54-
let globalFile3: FileOrFolder;
55-
let configFile: FileOrFolder;
50+
let moduleFile1: File;
51+
let file1Consumer1: File;
52+
let file1Consumer2: File;
53+
let moduleFile2: File;
54+
let globalFile3: File;
55+
let configFile: File;
5656
let changeModuleFile1ShapeRequest1: server.protocol.Request;
5757
let changeModuleFile1InternalRequest1: server.protocol.Request;
5858
// A compile on save affected file request using file1
@@ -225,7 +225,7 @@ namespace ts.projectSystem {
225225
openFilesForSession([moduleFile1], session);
226226
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
227227

228-
const file1Consumer3: FileOrFolder = {
228+
const file1Consumer3: File = {
229229
path: "/a/b/file1Consumer3.ts",
230230
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
231231
};
@@ -330,7 +330,7 @@ namespace ts.projectSystem {
330330
}`
331331
};
332332

333-
const configFile2: FileOrFolder = {
333+
const configFile2: File = {
334334
path: "/a/tsconfig.json",
335335
content: `{
336336
"compileOnSave": true
@@ -403,7 +403,7 @@ namespace ts.projectSystem {
403403
});
404404

405405
it("should return cascaded affected file list", () => {
406-
const file1Consumer1Consumer1: FileOrFolder = {
406+
const file1Consumer1Consumer1: File = {
407407
path: "/a/b/file1Consumer1Consumer1.ts",
408408
content: `import {y} from "./file1Consumer1";`
409409
};
@@ -428,13 +428,13 @@ namespace ts.projectSystem {
428428
});
429429

430430
it("should work fine for files with circular references", () => {
431-
const file1: FileOrFolder = {
431+
const file1: File = {
432432
path: "/a/b/file1.ts",
433433
content: `
434434
/// <reference path="./file2.ts" />
435435
export var t1 = 10;`
436436
};
437-
const file2: FileOrFolder = {
437+
const file2: File = {
438438
path: "/a/b/file2.ts",
439439
content: `
440440
/// <reference path="./file1.ts" />
@@ -450,11 +450,11 @@ namespace ts.projectSystem {
450450
});
451451

452452
it("should return results for all projects if not specifying projectFileName", () => {
453-
const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" };
454-
const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
455-
const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
456-
const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
457-
const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
453+
const file1: File = { path: "/a/b/file1.ts", content: "export var t = 10;" };
454+
const file2: File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
455+
const file3: File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
456+
const configFile1: File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
457+
const configFile2: File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
458458

459459
const host = createServerHost([file1, file2, file3, configFile1, configFile2]);
460460
const session = createSession(host);
@@ -469,7 +469,7 @@ namespace ts.projectSystem {
469469
});
470470

471471
it("should detect removed code file", () => {
472-
const referenceFile1: FileOrFolder = {
472+
const referenceFile1: File = {
473473
path: "/a/b/referenceFile1.ts",
474474
content: `
475475
/// <reference path="./moduleFile1.ts" />
@@ -490,7 +490,7 @@ namespace ts.projectSystem {
490490
});
491491

492492
it("should detect non-existing code file", () => {
493-
const referenceFile1: FileOrFolder = {
493+
const referenceFile1: File = {
494494
path: "/a/b/referenceFile1.ts",
495495
content: `
496496
/// <reference path="./moduleFile2.ts" />

src/harness/unittests/organizeImports.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -446,11 +446,11 @@ import { React, Other } from "react";
446446
},
447447
reactLibFile);
448448

449-
function testOrganizeImports(testName: string, testFile: TestFSWithWatch.FileOrFolder, ...otherFiles: TestFSWithWatch.FileOrFolder[]) {
449+
function testOrganizeImports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
450450
it(testName, () => runBaseline(`organizeImports/${testName}.ts`, testFile, ...otherFiles));
451451
}
452452

453-
function runBaseline(baselinePath: string, testFile: TestFSWithWatch.FileOrFolder, ...otherFiles: TestFSWithWatch.FileOrFolder[]) {
453+
function runBaseline(baselinePath: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
454454
const { path: testPath, content: testContent } = testFile;
455455
const languageService = makeLanguageService(testFile, ...otherFiles);
456456
const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatOptions, defaultPreferences);
@@ -468,7 +468,7 @@ import { React, Other } from "react";
468468
});
469469
}
470470

471-
function makeLanguageService(...files: TestFSWithWatch.FileOrFolder[]) {
471+
function makeLanguageService(...files: TestFSWithWatch.File[]) {
472472
const host = projectSystem.createServerHost(files);
473473
const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true });
474474
projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None });
@@ -555,4 +555,4 @@ import { React, Other } from "react";
555555
}
556556
}
557557
});
558-
}
558+
}

0 commit comments

Comments
 (0)