mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Use watch invoked with node_modules/.staging as watch for refreshing complete node_modules, so that npm install is reflected correctly (#36039)
* Add test that demonstrates npm install watch behaviour some times * Use watch invoked with `node_modules/.staging` as watch for refreshing complete node_modules, so that npm install is reflected correctly Fixes #35966
This commit is contained in:
parent
0c3019e3b3
commit
76ee0214f9
@ -73,8 +73,15 @@ namespace ts {
|
||||
nonRecursive?: boolean;
|
||||
}
|
||||
|
||||
export function isPathIgnored(path: Path) {
|
||||
return some(ignoredPaths, searchPath => stringContains(path, searchPath));
|
||||
export function removeIgnoredPath(path: Path): Path | undefined {
|
||||
// Consider whole staging folder as if node_modules changed.
|
||||
if (endsWith(path, "/node_modules/.staging")) {
|
||||
return removeSuffix(path, "/.staging") as Path;
|
||||
}
|
||||
|
||||
return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ?
|
||||
undefined :
|
||||
path;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -722,7 +729,9 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// If something to do with folder/file starting with "." in node_modules folder, skip it
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return false;
|
||||
const updatedPath = removeIgnoredPath(fileOrDirectoryPath);
|
||||
if (!updatedPath) return false;
|
||||
fileOrDirectoryPath = updatedPath;
|
||||
|
||||
// prevent saving an open file from over-eagerly triggering invalidation
|
||||
if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) {
|
||||
|
||||
@ -687,7 +687,7 @@ namespace ts {
|
||||
fileOrDirectory => {
|
||||
Debug.assert(!!configFileName);
|
||||
|
||||
const fileOrDirectoryPath = toPath(fileOrDirectory);
|
||||
let fileOrDirectoryPath: Path | undefined = toPath(fileOrDirectory);
|
||||
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
if (cachedDirectoryStructureHost) {
|
||||
@ -695,7 +695,8 @@ namespace ts {
|
||||
}
|
||||
nextSourceFileVersion(fileOrDirectoryPath);
|
||||
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return;
|
||||
fileOrDirectoryPath = removeIgnoredPath(fileOrDirectoryPath);
|
||||
if (!fileOrDirectoryPath) return;
|
||||
|
||||
// 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
|
||||
|
||||
@ -611,28 +611,29 @@ interface Array<T> { length: number; [n: number]: T; }`
|
||||
}
|
||||
}
|
||||
|
||||
ensureFileOrFolder(fileOrDirectoryOrSymLink: FileOrFolderOrSymLink, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean) {
|
||||
ensureFileOrFolder(fileOrDirectoryOrSymLink: FileOrFolderOrSymLink, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean, ignoreParentWatch?: boolean) {
|
||||
if (isFile(fileOrDirectoryOrSymLink)) {
|
||||
const file = this.toFsFile(fileOrDirectoryOrSymLink);
|
||||
// file may already exist when updating existing type declaration file
|
||||
if (!this.fs.get(file.path)) {
|
||||
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
|
||||
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath), ignoreParentWatch);
|
||||
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
|
||||
}
|
||||
}
|
||||
else if (isSymLink(fileOrDirectoryOrSymLink)) {
|
||||
const symLink = this.toFsSymLink(fileOrDirectoryOrSymLink);
|
||||
Debug.assert(!this.fs.get(symLink.path));
|
||||
const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath));
|
||||
const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath), ignoreParentWatch);
|
||||
this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate);
|
||||
}
|
||||
else {
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrDirectoryOrSymLink.path, this.currentDirectory);
|
||||
this.ensureFolder(fullPath);
|
||||
this.ensureFolder(getDirectoryPath(fullPath), ignoreParentWatch);
|
||||
this.ensureFolder(fullPath, ignoreWatchInvokedWithTriggerAsFileCreate);
|
||||
}
|
||||
}
|
||||
|
||||
private ensureFolder(fullPath: string): FsFolder {
|
||||
private ensureFolder(fullPath: string, ignoreWatch: boolean | undefined): FsFolder {
|
||||
const path = this.toPath(fullPath);
|
||||
let folder = this.fs.get(path) as FsFolder;
|
||||
if (!folder) {
|
||||
@ -640,8 +641,8 @@ interface Array<T> { length: number; [n: number]: T; }`
|
||||
const baseFullPath = getDirectoryPath(fullPath);
|
||||
if (fullPath !== baseFullPath) {
|
||||
// Add folder in the base folder
|
||||
const baseFolder = this.ensureFolder(baseFullPath);
|
||||
this.addFileOrFolderInFolder(baseFolder, folder);
|
||||
const baseFolder = this.ensureFolder(baseFullPath, ignoreWatch);
|
||||
this.addFileOrFolderInFolder(baseFolder, folder, ignoreWatch);
|
||||
}
|
||||
else {
|
||||
// root folder
|
||||
|
||||
@ -1104,7 +1104,7 @@ namespace ts.server {
|
||||
this.host,
|
||||
directory,
|
||||
fileOrDirectory => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
let fileOrDirectoryPath: Path | undefined = this.toPath(fileOrDirectory);
|
||||
const fsResult = project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
|
||||
// don't trigger callback on open, existing files
|
||||
@ -1115,7 +1115,8 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return;
|
||||
fileOrDirectoryPath = removeIgnoredPath(fileOrDirectoryPath);
|
||||
if (!fileOrDirectoryPath) return;
|
||||
const configFilename = project.getConfigFilePath();
|
||||
|
||||
if (getBaseFileName(fileOrDirectoryPath) === "package.json" && !isInsideNodeModules(fileOrDirectoryPath) &&
|
||||
@ -2272,8 +2273,8 @@ namespace ts.server {
|
||||
this.host,
|
||||
watchDir,
|
||||
(fileOrDirectory) => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return;
|
||||
const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory));
|
||||
if (!fileOrDirectoryPath) return;
|
||||
|
||||
// Has extension
|
||||
Debug.assert(result.refCount > 0);
|
||||
|
||||
@ -933,4 +933,118 @@ console.log(blabla);`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsserver:: Project Errors with npm install when", () => {
|
||||
function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) {
|
||||
const main: File = {
|
||||
path: `${tscWatch.projectRoot}/src/main.ts`,
|
||||
content: "import * as _a from '@angular/core';"
|
||||
};
|
||||
const config: File = {
|
||||
path: `${tscWatch.projectRoot}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const projectFiles = [main, libFile, config];
|
||||
const host = createServerHost(projectFiles);
|
||||
const session = createSession(host, { canUseEvents: true });
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: main, projectRootPath: tscWatch.projectRoot }], session);
|
||||
const span = protocolTextSpanFromSubstring(main.content, `'@angular/core'`);
|
||||
const moduleNotFoundErr: protocol.Diagnostic[] = [
|
||||
createDiagnostic(
|
||||
span.start,
|
||||
span.end,
|
||||
Diagnostics.Cannot_find_module_0,
|
||||
["@angular/core"]
|
||||
)
|
||||
];
|
||||
const expectedRecursiveWatches = arrayToMap([`${tscWatch.projectRoot}`, `${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/node_modules/@types`], identity, () => 1);
|
||||
verifyProject();
|
||||
verifyErrors(moduleNotFoundErr);
|
||||
|
||||
let npmInstallComplete = false;
|
||||
|
||||
// Simulate npm install
|
||||
let filesAndFoldersToAdd: (File | Folder)[] = [
|
||||
{ path: `${tscWatch.projectRoot}/node_modules` }, // This should queue update
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging` },
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging/@babel` },
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging/@babel/helper-plugin-utils-a06c629f` },
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging/core-js-db53158d` },
|
||||
];
|
||||
verifyWhileNpmInstall({ timeouts: 2, semantic: moduleNotFoundErr });
|
||||
|
||||
filesAndFoldersToAdd = [
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/platform-browser-dynamic-5efaaa1a` },
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/cli-c1e44b05/models/analytics.d.ts`, content: `export const x = 10;` },
|
||||
{ path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/core-0963aebf/index.d.ts`, content: `export const y = 10;` },
|
||||
];
|
||||
// Since we added/removed in .staging no timeout
|
||||
verifyWhileNpmInstall({ timeouts: 0, semantic: moduleNotFoundErr });
|
||||
|
||||
filesAndFoldersToAdd = [];
|
||||
// Move things from staging to node_modules without triggering watch
|
||||
const moduleFile: File = {
|
||||
path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`,
|
||||
content: `export const y = 10;`
|
||||
};
|
||||
host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true);
|
||||
// Since we added/removed in .staging no timeout
|
||||
verifyWhileNpmInstall({ timeouts: 0, semantic: moduleNotFoundErr });
|
||||
|
||||
// Remove staging folder to remove errors
|
||||
host.deleteFolder(`${tscWatch.projectRoot}/node_modules/.staging`, /*recursive*/ true);
|
||||
npmInstallComplete = true;
|
||||
projectFiles.push(moduleFile);
|
||||
// Additional watch for watching script infos from node_modules
|
||||
expectedRecursiveWatches.set(`${tscWatch.projectRoot}/node_modules`, 2);
|
||||
verifyWhileNpmInstall({ timeouts: 2, semantic: [] });
|
||||
|
||||
function verifyWhileNpmInstall({ timeouts, semantic }: { timeouts: number; semantic: protocol.Diagnostic[] }) {
|
||||
filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f));
|
||||
if (npmInstallComplete || timeoutDuringPartialInstallation) {
|
||||
host.checkTimeoutQueueLengthAndRun(timeouts);
|
||||
}
|
||||
else {
|
||||
host.checkTimeoutQueueLength(2);
|
||||
}
|
||||
verifyProject();
|
||||
verifyErrors(semantic, !npmInstallComplete && !timeoutDuringPartialInstallation ? 2 : undefined);
|
||||
}
|
||||
|
||||
function verifyProject() {
|
||||
checkNumberOfConfiguredProjects(service, 1);
|
||||
|
||||
const project = service.configuredProjects.get(config.path)!;
|
||||
checkProjectActualFiles(project, map(projectFiles, f => f.path));
|
||||
|
||||
checkWatchedFilesDetailed(host, mapDefined(projectFiles, f => f === main || f === moduleFile ? undefined : f.path), 1);
|
||||
checkWatchedDirectoriesDetailed(host, expectedRecursiveWatches, /*recursive*/ true);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
}
|
||||
|
||||
function verifyErrors(semantic: protocol.Diagnostic[], existingTimeouts?: number) {
|
||||
verifyGetErrRequest({
|
||||
session,
|
||||
host,
|
||||
expected: [{
|
||||
file: main,
|
||||
syntax: [],
|
||||
semantic,
|
||||
suggestion: []
|
||||
}],
|
||||
existingTimeouts
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
it("timeouts occur inbetween installation", () => {
|
||||
verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true);
|
||||
});
|
||||
|
||||
it("timeout occurs after installation", () => {
|
||||
verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user