Skip to content

Commit fe40873

Browse files
authored
Merge pull request microsoft#19786 from Microsoft/directoryRename
Handle the watch when folders are added/removed/renamed in wild card folder
2 parents 57f247e + 3f34525 commit fe40873

7 files changed

Lines changed: 162 additions & 39 deletions

File tree

src/compiler/core.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,10 +3097,9 @@ namespace ts {
30973097
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
30983098
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
30993099
if (existingResult) {
3100-
// This was a folder already present, remove it if this doesnt exist any more
3101-
if (!host.directoryExists(fileOrDirectory)) {
3102-
cachedReadDirectoryResult.delete(fileOrDirectoryPath);
3103-
}
3100+
// Just clear the cache for now
3101+
// For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated
3102+
clearCache();
31043103
}
31053104
else {
31063105
// This was earlier a file (hence not in cached directory contents)
@@ -3113,8 +3112,14 @@ namespace ts {
31133112
fileExists: host.fileExists(fileOrDirectoryPath),
31143113
directoryExists: host.directoryExists(fileOrDirectoryPath)
31153114
};
3116-
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
3117-
updateFileSystemEntry(parentResult.directories, baseName, fsQueryResult.directoryExists);
3115+
if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) {
3116+
// Folder added or removed, clear the cache instead of updating the folder and its structure
3117+
clearCache();
3118+
}
3119+
else {
3120+
// No need to update the directory structure, just files
3121+
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
3122+
}
31183123
return fsQueryResult;
31193124
}
31203125
}

src/compiler/watch.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ namespace ts {
230230

231231
function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) {
232232
let program: Program;
233-
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk
233+
let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
234234
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
235235
let watchedWildcardDirectories: Map<WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
236236
let timerToUpdateProgram: any; // timer callback to recompile the program
@@ -488,25 +488,38 @@ namespace ts {
488488

489489
function scheduleProgramReload() {
490490
Debug.assert(!!configFileName);
491-
needsReload = true;
491+
reloadLevel = ConfigFileProgramReloadLevel.Full;
492492
scheduleProgramUpdate();
493493
}
494494

495495
function updateProgram() {
496496
timerToUpdateProgram = undefined;
497497
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
498498

499-
if (needsReload) {
500-
reloadConfigFile();
499+
switch (reloadLevel) {
500+
case ConfigFileProgramReloadLevel.Partial:
501+
return reloadFileNamesFromConfigFile();
502+
case ConfigFileProgramReloadLevel.Full:
503+
return reloadConfigFile();
504+
default:
505+
return synchronizeProgram();
501506
}
502-
else {
503-
synchronizeProgram();
507+
}
508+
509+
function reloadFileNamesFromConfigFile() {
510+
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost);
511+
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
512+
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
504513
}
514+
rootFileNames = result.fileNames;
515+
516+
// Update the program
517+
synchronizeProgram();
505518
}
506519

507520
function reloadConfigFile() {
508521
writeLog(`Reloading config file: ${configFileName}`);
509-
needsReload = false;
522+
reloadLevel = ConfigFileProgramReloadLevel.None;
510523

511524
const cachedHost = directoryStructureHost as CachedDirectoryStructureHost;
512525
cachedHost.clearCache();
@@ -611,18 +624,14 @@ namespace ts {
611624

612625
// If the the added or created file or directory is not supported file name, ignore the file
613626
// But when watched directory is added/removed, we need to reload the file list
614-
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
627+
if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
615628
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
616629
return;
617630
}
618631

619632
// Reload is pending, do the reload
620-
if (!needsReload) {
621-
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost);
622-
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
623-
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
624-
}
625-
rootFileNames = result.fileNames;
633+
if (reloadLevel !== ConfigFileProgramReloadLevel.Full) {
634+
reloadLevel = ConfigFileProgramReloadLevel.Partial;
626635

627636
// Schedule Update the program
628637
scheduleProgramUpdate();

src/compiler/watchUtilities.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
/* @internal */
44
namespace ts {
5+
export enum ConfigFileProgramReloadLevel {
6+
None,
7+
/** Update the file name list from the disk */
8+
Partial,
9+
/** Reload completely by re-reading contents of config file from disk and updating program */
10+
Full
11+
}
12+
513
/**
614
* Updates the existing missing file watches with the new set of missing files after new program is created
715
*/

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2766,6 +2766,7 @@ namespace ts.projectSystem {
27662766
watchedRecursiveDirectories.push(`${root}/a/b/src`, `${root}/a/b/node_modules`);
27672767
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
27682768
});
2769+
27692770
});
27702771

27712772
describe("Proper errors", () => {
@@ -2868,6 +2869,58 @@ namespace ts.projectSystem {
28682869
verifyNonExistentFile(/*useProjectRoot*/ false);
28692870
});
28702871
});
2872+
2873+
it("folder rename updates project structure and reports no errors", () => {
2874+
const projectDir = "/a/b/projects/myproject";
2875+
const app: FileOrFolder = {
2876+
path: `${projectDir}/bar/app.ts`,
2877+
content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }"
2878+
};
2879+
const foo: FileOrFolder = {
2880+
path: `${projectDir}/foo/foo.ts`,
2881+
content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }"
2882+
};
2883+
const configFile: FileOrFolder = {
2884+
path: `${projectDir}/tsconfig.json`,
2885+
content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] })
2886+
};
2887+
const host = createServerHost([app, foo, configFile]);
2888+
const session = createSession(host, { canUseEvents: true, });
2889+
const projectService = session.getProjectService();
2890+
2891+
session.executeCommandSeq<protocol.OpenRequest>({
2892+
command: server.CommandNames.Open,
2893+
arguments: { file: app.path, }
2894+
});
2895+
checkNumberOfProjects(projectService, { configuredProjects: 1 });
2896+
assert.isDefined(projectService.configuredProjects.get(configFile.path));
2897+
verifyErrorsInApp();
2898+
2899+
host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`);
2900+
host.runQueuedTimeoutCallbacks();
2901+
host.runQueuedTimeoutCallbacks();
2902+
verifyErrorsInApp();
2903+
2904+
function verifyErrorsInApp() {
2905+
host.clearOutput();
2906+
const expectedSequenceId = session.getNextSeq();
2907+
session.executeCommandSeq<protocol.GeterrRequest>({
2908+
command: server.CommandNames.Geterr,
2909+
arguments: {
2910+
delay: 0,
2911+
files: [app.path]
2912+
}
2913+
});
2914+
host.checkTimeoutQueueLengthAndRun(1);
2915+
checkErrorMessage(host, "syntaxDiag", { file: app.path, diagnostics: [] });
2916+
host.clearOutput();
2917+
2918+
host.runQueuedImmediateCallbacks();
2919+
checkErrorMessage(host, "semanticDiag", { file: app.path, diagnostics: [] });
2920+
checkCompleteEvent(host, 2, expectedSequenceId);
2921+
host.clearOutput();
2922+
}
2923+
});
28712924
});
28722925

28732926
describe("autoDiscovery", () => {
@@ -5656,8 +5709,8 @@ namespace ts.projectSystem {
56565709
{ path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" },
56575710
{ path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" }
56585711
].map(getRootedFileOrFolder));
5659-
// Since we didnt add any supported extension file, there wont be any timeout scheduled
5660-
verifyAfterPartialOrCompleteNpmInstall(0);
5712+
// Since we added/removed folder, scheduled project update
5713+
verifyAfterPartialOrCompleteNpmInstall(2);
56615714

56625715
// Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594"
56635716
filesAndFoldersToAdd.length--;
@@ -5678,7 +5731,7 @@ namespace ts.projectSystem {
56785731
{ path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" },
56795732
{ 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" }
56805733
].map(getRootedFileOrFolder));
5681-
verifyAfterPartialOrCompleteNpmInstall(0);
5734+
verifyAfterPartialOrCompleteNpmInstall(2);
56825735

56835736
// remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041
56845737
filesAndFoldersToAdd.length--;

0 commit comments

Comments
 (0)