Merge pull request #19786 from Microsoft/directoryRename

Handle the watch when folders are added/removed/renamed in wild card folder
This commit is contained in:
Sheetal Nandi authored and GitHub committed 2017-11-07 11:13:47 -08:00
commit fe40873664
7 files changed
+162 -39

No files matched your search

+11 -6
View File
@@ -3097,10 +3097,9 @@ namespace ts {
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
if (existingResult) {
// This was a folder already present, remove it if this doesnt exist any more
if (!host.directoryExists(fileOrDirectory)) {
cachedReadDirectoryResult.delete(fileOrDirectoryPath);
}
// Just clear the cache for now
// For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated
clearCache();
}
else {
// This was earlier a file (hence not in cached directory contents)
@@ -3113,8 +3112,14 @@ namespace ts {
fileExists: host.fileExists(fileOrDirectoryPath),
directoryExists: host.directoryExists(fileOrDirectoryPath)
};
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
updateFileSystemEntry(parentResult.directories, baseName, fsQueryResult.directoryExists);
if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) {
// Folder added or removed, clear the cache instead of updating the folder and its structure
clearCache();
}
else {
// No need to update the directory structure, just files
updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists);
}
return fsQueryResult;
}
}
+23 -14
View File
@@ -230,7 +230,7 @@ namespace ts {
function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike<WatchDirectoryFlags>, optionsToExtendForConfigFile?: CompilerOptions) {
let program: Program;
let needsReload: boolean; // true if the config file changed and needs to reload it from the disk
let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
let missingFilesMap: Map<FileWatcher>; // Map of file watchers for the missing files
let watchedWildcardDirectories: Map<WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
let timerToUpdateProgram: any; // timer callback to recompile the program
@@ -488,7 +488,7 @@ namespace ts {
function scheduleProgramReload() {
Debug.assert(!!configFileName);
needsReload = true;
reloadLevel = ConfigFileProgramReloadLevel.Full;
scheduleProgramUpdate();
}
@@ -496,17 +496,30 @@ namespace ts {
timerToUpdateProgram = undefined;
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
if (needsReload) {
reloadConfigFile();
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
return reloadFileNamesFromConfigFile();
case ConfigFileProgramReloadLevel.Full:
return reloadConfigFile();
default:
return synchronizeProgram();
}
else {
synchronizeProgram();
}
function reloadFileNamesFromConfigFile() {
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost);
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
rootFileNames = result.fileNames;
// Update the program
synchronizeProgram();
}
function reloadConfigFile() {
writeLog(`Reloading config file: ${configFileName}`);
needsReload = false;
reloadLevel = ConfigFileProgramReloadLevel.None;
const cachedHost = directoryStructureHost as CachedDirectoryStructureHost;
cachedHost.clearCache();
@@ -611,18 +624,14 @@ namespace ts {
// If the the added or created file or directory is not supported file name, ignore the file
// But when watched directory is added/removed, we need to reload the file list
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return;
}
// Reload is pending, do the reload
if (!needsReload) {
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost);
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
rootFileNames = result.fileNames;
if (reloadLevel !== ConfigFileProgramReloadLevel.Full) {
reloadLevel = ConfigFileProgramReloadLevel.Partial;
// Schedule Update the program
scheduleProgramUpdate();
+8
View File
@@ -2,6 +2,14 @@
/* @internal */
namespace ts {
export enum ConfigFileProgramReloadLevel {
None,
/** Update the file name list from the disk */
Partial,
/** Reload completely by re-reading contents of config file from disk and updating program */
Full
}
/**
* Updates the existing missing file watches with the new set of missing files after new program is created
*/
+56 -3
View File
@@ -2766,6 +2766,7 @@ namespace ts.projectSystem {
watchedRecursiveDirectories.push(`${root}/a/b/src`, `${root}/a/b/node_modules`);
checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true);
});
});
describe("Proper errors", () => {
@@ -2868,6 +2869,58 @@ namespace ts.projectSystem {
verifyNonExistentFile(/*useProjectRoot*/ false);
});
});
it("folder rename updates project structure and reports no errors", () => {
const projectDir = "/a/b/projects/myproject";
const app: FileOrFolder = {
path: `${projectDir}/bar/app.ts`,
content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }"
};
const foo: FileOrFolder = {
path: `${projectDir}/foo/foo.ts`,
content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }"
};
const configFile: FileOrFolder = {
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<protocol.OpenRequest>({
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() {
host.clearOutput();
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrRequest>({
command: server.CommandNames.Geterr,
arguments: {
delay: 0,
files: [app.path]
}
});
host.checkTimeoutQueueLengthAndRun(1);
checkErrorMessage(host, "syntaxDiag", { file: app.path, diagnostics: [] });
host.clearOutput();
host.runQueuedImmediateCallbacks();
checkErrorMessage(host, "semanticDiag", { file: app.path, diagnostics: [] });
checkCompleteEvent(host, 2, expectedSequenceId);
host.clearOutput();
}
});
});
describe("autoDiscovery", () => {
@@ -5656,8 +5709,8 @@ namespace ts.projectSystem {
{ 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 didnt add any supported extension file, there wont be any timeout scheduled
verifyAfterPartialOrCompleteNpmInstall(0);
// Since we added/removed folder, scheduled project update
verifyAfterPartialOrCompleteNpmInstall(2);
// Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594"
filesAndFoldersToAdd.length--;
@@ -5678,7 +5731,7 @@ namespace ts.projectSystem {
{ 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 Line truncated
].map(getRootedFileOrFolder));
verifyAfterPartialOrCompleteNpmInstall(0);
verifyAfterPartialOrCompleteNpmInstall(2);
// remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041
filesAndFoldersToAdd.length--;
+35 -2
View File
@@ -346,6 +346,39 @@ interface Array<T> {}`
}
}
renameFolder(folderName: string, newFolderName: string) {
const fullPath = getNormalizedAbsolutePath(folderName, this.currentDirectory);
const path = this.toPath(fullPath);
const folder = this.fs.get(path) as Folder;
Debug.assert(!!folder);
// Only remove the folder
this.removeFileOrFolder(folder, returnFalse, /*isRenaming*/ true);
// Add updated folder with new folder name
const newFullPath = getNormalizedAbsolutePath(newFolderName, this.currentDirectory);
const newFolder = this.toFolder(newFullPath);
const newPath = newFolder.path;
const basePath = getDirectoryPath(path);
Debug.assert(basePath !== path);
Debug.assert(basePath === getDirectoryPath(newPath));
const baseFolder = this.fs.get(basePath) as Folder;
this.addFileOrFolderInFolder(baseFolder, newFolder);
// Invoke watches for files in the folder as deleted (from old path)
for (const entry of folder.entries) {
Debug.assert(isFile(entry));
this.fs.delete(entry.path);
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Deleted);
entry.fullPath = combinePaths(newFullPath, getBaseFileName(entry.fullPath));
entry.path = this.toPath(entry.fullPath);
newFolder.entries.push(entry);
this.fs.set(entry.path, entry);
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Created);
}
}
ensureFileOrFolder(fileOrDirectory: FileOrFolder, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean) {
if (isString(fileOrDirectory.content)) {
const file = this.toFile(fileOrDirectory);
@@ -393,7 +426,7 @@ interface Array<T> {}`
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) {
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
const basePath = getDirectoryPath(fileOrDirectory.path);
const baseFolder = this.fs.get(basePath) as Folder;
if (basePath !== fileOrDirectory.path) {
@@ -406,7 +439,7 @@ interface Array<T> {}`
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
}
else {
Debug.assert(fileOrDirectory.entries.length === 0);
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
// Invoke directory and recursive directory watcher for the folder
// Here we arent invoking recursive directory watchers for the base folders
+18 -8
View File
@@ -801,17 +801,14 @@ namespace ts.server {
// If the the added or created file or directory is not supported file name, ignore the file
// But when watched directory is added/removed, we need to reload the file list
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) {
if (fileOrDirectoryPath !== directory && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) {
this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
return;
}
// Reload is pending, do the reload
if (!project.pendingReload) {
const configFileSpecs = project.configFileSpecs;
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(result.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader);
if (project.pendingReload !== ConfigFileProgramReloadLevel.Full) {
project.pendingReload = ConfigFileProgramReloadLevel.Partial;
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
}
},
@@ -844,7 +841,7 @@ namespace ts.server {
}
else {
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
project.pendingReload = true;
project.pendingReload = ConfigFileProgramReloadLevel.Full;
this.delayUpdateProjectGraph(project);
// As we scheduled the update on configured project graph,
// we would need to schedule the project reload for only the root of inferred projects
@@ -1592,6 +1589,19 @@ namespace ts.server {
this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, newTypeAcquisition);
}
/**
* Reload the file names from config file specs and update the project graph
*/
/*@internal*/
reloadFileNamesOfConfiguredProject(project: ConfiguredProject): boolean {
const configFileSpecs = project.configFileSpecs;
const configFileName = project.getConfigFilePath();
const fileNamesResult = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(fileNamesResult.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames, fileNamePropertyReader);
return project.updateGraph();
}
/**
* Read the config file of the project again and update the project
*/
@@ -1886,7 +1896,7 @@ namespace ts.server {
}
else if (!updatedProjects.has(configFileName)) {
if (delayReload) {
project.pendingReload = true;
project.pendingReload = ConfigFileProgramReloadLevel.Full;
this.delayUpdateProjectGraph(project);
}
else {
+11 -6
View File
@@ -1122,7 +1122,7 @@ namespace ts.server {
readonly canonicalConfigFilePath: NormalizedPath;
/* @internal */
pendingReload: boolean;
pendingReload: ConfigFileProgramReloadLevel;
/*@internal*/
configFileSpecs: ConfigFileSpecs;
@@ -1162,12 +1162,17 @@ namespace ts.server {
* @returns: true if set of files in the project stays the same and false - otherwise.
*/
updateGraph(): boolean {
if (this.pendingReload) {
this.pendingReload = false;
this.projectService.reloadConfiguredProject(this);
return true;
const reloadLevel = this.pendingReload;
this.pendingReload = ConfigFileProgramReloadLevel.None;
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
return this.projectService.reloadFileNamesOfConfiguredProject(this);
case ConfigFileProgramReloadLevel.Full:
this.projectService.reloadConfiguredProject(this);
return true;
default:
return super.updateGraph();
}
return super.updateGraph();
}
/*@internal*/