Handle package.json watch in tsc and tsserver. (#49328)

* refactoring

* Maintain packagejson paths per resolution so we dont have invalidate everything on package json change

* Fix todo
This commit is contained in:
Sheetal Nandi
2022-06-08 10:22:29 -07:00
committed by GitHub
parent b8f6488323
commit 5cedf3e680
85 changed files with 1974 additions and 550 deletions
+1
View File
@@ -164,6 +164,7 @@
"unittests/tscWatch/emitAndErrorUpdates.ts",
"unittests/tscWatch/forceConsistentCasingInFileNames.ts",
"unittests/tscWatch/incremental.ts",
"unittests/tscWatch/moduleResolution.ts",
"unittests/tscWatch/programUpdates.ts",
"unittests/tscWatch/projectsWithReferences.ts",
"unittests/tscWatch/resolutionCache.ts",
@@ -141,6 +141,7 @@ namespace ts {
checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name));
// expect three failed lookup location - attempt to load module as file with all supported extensions
assert.equal(resolution.failedLookupLocations.length, supportedTSExtensions[0].length);
assert.deepEqual(resolution.affectingLocations, [packageJsonFileName]);
}
}
@@ -215,6 +216,7 @@ namespace ts {
extension: Extension.Ts,
},
failedLookupLocations: [],
affectingLocations: [],
resolutionDiagnostics: [],
});
assert.isDefined(cache.get("/sub"));
@@ -229,6 +231,7 @@ namespace ts {
extension: Extension.Ts,
},
failedLookupLocations: [],
affectingLocations: [],
resolutionDiagnostics: [],
});
assert.isDefined(cache.get("/sub/dir/foo"));
@@ -245,6 +248,7 @@ namespace ts {
extension: Extension.Ts,
},
failedLookupLocations: [],
affectingLocations: [],
resolutionDiagnostics: [],
});
assert.isDefined(cache.get("/foo/bar"));
@@ -260,6 +264,7 @@ namespace ts {
extension: Extension.Ts,
},
failedLookupLocations: [],
affectingLocations: [],
resolutionDiagnostics: [],
});
assert.isDefined(cache.get("/foo"));
@@ -274,6 +279,7 @@ namespace ts {
extension: Extension.Ts,
},
failedLookupLocations: [],
affectingLocations: [],
resolutionDiagnostics: [],
});
assert.isDefined(cache.get("c:/foo"));
@@ -284,6 +290,7 @@ namespace ts {
cache.set("/foo/bar/baz", {
resolvedModule: undefined,
failedLookupLocations: [],
affectingLocations: [],
resolutionDiagnostics: [],
});
assert.isDefined(cache.get("/foo/bar/baz"));
@@ -66,96 +66,6 @@ namespace ts.tscWatch {
changes: emptyArray
});
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`,
sys: () => createWatchedSystem([
{
path: `${projectRoot}/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
main: "build/index.js",
type: "module"
})
},
{
path: `${projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
module: "node16",
},
references: [{ path: "../pkg2" }]
})
},
{
path: `${projectRoot}/packages/pkg2/const.cts`,
content: `export type TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg2/index.ts`,
content: `export type { TheNum } from './const.cjs';`
},
{
path: `${projectRoot}/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "build",
module: "node16",
}
})
},
{
path: `${projectRoot}/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
main: "build/index.js",
type: "module"
})
},
{
path: `${projectRoot}/node_modules/pkg2`,
symLink: `${projectRoot}/packages/pkg2`,
},
{ ...libFile, path: `/a/lib/lib.es2022.full.d.ts` }
], { currentDirectory: projectRoot }),
commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "removes those errors when a package file is changed back",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "removes those errors when a package file is changed to cjs extensions",
change: sys => {
replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`);
sys.renameFile(`${projectRoot}/packages/pkg2/index.ts`, `${projectRoot}/packages/pkg2/index.cts`);
},
timeouts: runQueuedTimeoutCallbacks,
},
]
});
verifyTsc({
scenario: "moduleResolution",
subScenario: `type reference resolution uses correct options for different resolution options referenced project`,
@@ -175,151 +85,5 @@ namespace ts.tscWatch {
}),
commandLineArgs: ["-b", "/src/packages/pkg1.tsconfig.json", "/src/packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"],
});
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `watches for changes to package-json main fields`,
sys: () => createWatchedSystem([
{
path: `${projectRoot}/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
},
})
},
{
path: `${projectRoot}/packages/pkg2/build/const.d.ts`,
content: `export type TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg2/build/index.d.ts`,
content: `export type { TheNum } from './const.js';`
},
{
path: `${projectRoot}/packages/pkg2/build/other.d.ts`,
content: `export type TheStr = string;`
},
{
path: `${projectRoot}/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/node_modules/pkg2`,
symLink: `${projectRoot}/packages/pkg2`,
},
libFile
], { currentDirectory: projectRoot }),
commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "removes those errors when a package file is changed back",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: runQueuedTimeoutCallbacks,
},
]
});
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `build mode watches for changes to package-json main fields`,
sys: () => createWatchedSystem([
{
path: `${projectRoot}/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
},
references: [{ path: "../pkg2" }]
})
},
{
path: `${projectRoot}/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "build",
baseUrl: ".",
}
})
},
{
path: `${projectRoot}/packages/pkg2/const.ts`,
content: `export type TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg2/index.ts`,
content: `export type { TheNum } from './const.js';`
},
{
path: `${projectRoot}/packages/pkg2/other.ts`,
content: `export type TheStr = string;`
},
{
path: `${projectRoot}/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/node_modules/pkg2`,
symLink: `${projectRoot}/packages/pkg2`,
},
libFile
], { currentDirectory: projectRoot }),
commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "removes those errors when a package file is changed back",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: runQueuedTimeoutCallbacks,
},
]
});
});
}
@@ -1,5 +1,5 @@
namespace ts.tscWatch {
describe("unittests:: tsbuildWatch:: watchMode:: module resolution different in referenced project", () => {
describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => {
verifyTscWatch({
scenario: "moduleResolutionCache",
subScenario: "handles the cache correctly when two projects use different module resolution settings",
@@ -51,5 +51,176 @@ namespace ts.tscWatch {
},
]
});
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`,
sys: () => createWatchedSystem([
{
path: `${projectRoot}/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
main: "build/index.js",
type: "module"
})
},
{
path: `${projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
module: "node16",
},
references: [{ path: "../pkg2" }]
})
},
{
path: `${projectRoot}/packages/pkg2/const.cts`,
content: `export type TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg2/index.ts`,
content: `export type { TheNum } from './const.cjs';`
},
{
path: `${projectRoot}/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "build",
module: "node16",
}
})
},
{
path: `${projectRoot}/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
main: "build/index.js",
type: "module"
})
},
{
path: `${projectRoot}/node_modules/pkg2`,
symLink: `${projectRoot}/packages/pkg2`,
},
{ ...libFile, path: `/a/lib/lib.es2022.full.d.ts` }
], { currentDirectory: projectRoot }),
commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: runQueuedTimeoutCallbacks
},
{
caption: "removes those errors when a package file is changed back",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`),
timeouts: runQueuedTimeoutCallbacks
},
{
caption: "removes those errors when a package file is changed to cjs extensions",
change: sys => {
replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`);
sys.renameFile(`${projectRoot}/packages/pkg2/index.ts`, `${projectRoot}/packages/pkg2/index.cts`);
},
timeouts: sys => {
sys.runQueuedTimeoutCallbacks(); // building pkg2
sys.runQueuedTimeoutCallbacks(); // building pkg1
},
},
]
});
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `build mode watches for changes to package-json main fields`,
sys: () => createWatchedSystem([
{
path: `${projectRoot}/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
},
references: [{ path: "../pkg2" }]
})
},
{
path: `${projectRoot}/packages/pkg2/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
outDir: "build",
baseUrl: ".",
}
})
},
{
path: `${projectRoot}/packages/pkg2/const.ts`,
content: `export type TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg2/index.ts`,
content: `export type { TheNum } from './const.js';`
},
{
path: `${projectRoot}/packages/pkg2/other.ts`,
content: `export type TheStr = string;`
},
{
path: `${projectRoot}/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/node_modules/pkg2`,
symLink: `${projectRoot}/packages/pkg2`,
},
libFile
], { currentDirectory: projectRoot }),
commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: runQueuedTimeoutCallbacks,
},
{
caption: "removes those errors when a package file is changed back",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: runQueuedTimeoutCallbacks,
},
]
});
});
}
@@ -0,0 +1,76 @@
namespace ts.tscWatch {
describe("unittests:: tsc-watch:: moduleResolution", () => {
verifyTscWatch({
scenario: "moduleResolution",
subScenario: `watches for changes to package-json main fields`,
sys: () => createWatchedSystem([
{
path: `${projectRoot}/packages/pkg1/package.json`,
content: JSON.stringify({
name: "pkg1",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/packages/pkg1/index.ts`,
content: Utils.dedent`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
outDir: "build",
},
})
},
{
path: `${projectRoot}/packages/pkg2/build/const.d.ts`,
content: `export type TheNum = 42;`
},
{
path: `${projectRoot}/packages/pkg2/build/index.d.ts`,
content: `export type { TheNum } from './const.js';`
},
{
path: `${projectRoot}/packages/pkg2/build/other.d.ts`,
content: `export type TheStr = string;`
},
{
path: `${projectRoot}/packages/pkg2/package.json`,
content: JSON.stringify({
name: "pkg2",
version: "1.0.0",
main: "build/index.js",
})
},
{
path: `${projectRoot}/node_modules/pkg2`,
symLink: `${projectRoot}/packages/pkg2`,
},
libFile
], { currentDirectory: projectRoot }),
commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"],
changes: [
{
caption: "reports import errors after change to package file",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups
sys.runQueuedTimeoutCallbacks(); // actual update
},
},
{
caption: "removes those errors when a package file is changed back",
change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups
sys.runQueuedTimeoutCallbacks(); // actual update
},
},
]
});
});
}
@@ -540,15 +540,13 @@ namespace ts.projectSystem {
});
const appFolder = getDirectoryPath(app.path);
const projectFiles = [app, libFile, tsconfigJson];
const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path));
const otherFiles = [packageJson];
const host = createServerHost(projectFiles.concat(otherFiles));
const projectService = createProjectService(host);
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs() });
projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } });
const { configFileName } = projectService.openClientFile(app.path);
assert.equal(configFileName, tsconfigJson.path as server.NormalizedPath, `should find config`); // TODO: GH#18217
const recursiveWatchedDirectories: string[] = [`${appFolder}`, `${appFolder}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(appFolder)));
verifyProject();
let npmInstallComplete = false;
@@ -635,6 +633,12 @@ namespace ts.projectSystem {
npmInstallComplete = true;
verifyAfterPartialOrCompleteNpmInstall(2);
baselineTsserverLogs(
"cachingFileSystemInformation",
`npm install works when ${timeoutDuringPartialInstallation ? "timeout occurs inbetween installation" : "timeout occurs after installation"}`,
projectService
);
function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) {
filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f));
if (npmInstallComplete || timeoutDuringPartialInstallation) {
@@ -650,20 +654,6 @@ namespace ts.projectSystem {
else {
host.checkTimeoutQueueLength(3);
}
verifyProject();
}
function verifyProject() {
checkNumberOfConfiguredProjects(projectService, 1);
const project = projectService.configuredProjects.get(tsconfigJson.path)!;
const projectFilePaths = map(projectFiles, f => f.path);
checkProjectActualFiles(project, projectFilePaths);
const filesWatched = filter(projectFilePaths, p => p !== app.path && p.indexOf("/a/b/node_modules") === -1);
checkWatchedFiles(host, filesWatched);
checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true);
checkWatchedDirectories(host, [], /*recursive*/ false);
}
}