From 267fa55b6b2b5e288d779cc8266c7af4f083c2fd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 30 Jun 2022 15:59:23 -0700 Subject: [PATCH 1/2] Add test where module resolution cache is not local and hence doesnt report errors in watch mode --- .../unittests/tscWatch/moduleResolution.ts | 42 ++++++++ .../diagnostics-from-cache.js | 99 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js diff --git a/src/testRunner/unittests/tscWatch/moduleResolution.ts b/src/testRunner/unittests/tscWatch/moduleResolution.ts index 5e596d9c9d5c8..11be6723ee382 100644 --- a/src/testRunner/unittests/tscWatch/moduleResolution.ts +++ b/src/testRunner/unittests/tscWatch/moduleResolution.ts @@ -72,5 +72,47 @@ namespace ts.tscWatch { }, ] }); + + verifyTscWatch({ + scenario: "moduleResolution", + subScenario: "diagnostics from cache", + sys: () => createWatchedSystem([ + { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + moduleResolution: "nodenext", + outDir: "./dist", + declaration: true, + declarationDir: "./types" + }, + }) + }, + { + path: `${projectRoot}/package.json`, + content: JSON.stringify({ + name: "@this/package", + type: "module", + exports: { + ".": { + default: "./dist/index.js", + types: "./types/index.d.ts" + } + } + }) + }, + { + path: `${projectRoot}/index.ts`, + content: Utils.dedent` + import * as me from "@this/package"; + me.thing() + export function thing(): void {} + ` + }, + libFile + ], { currentDirectory: projectRoot }), + commandLineArgs: ["-w", "--traceResolution"], + changes: emptyArray + }); }); } \ No newline at end of file diff --git a/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js b/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js new file mode 100644 index 0000000000000..808ab569c1985 --- /dev/null +++ b/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js @@ -0,0 +1,99 @@ +Input:: +//// [/user/username/projects/myproject/tsconfig.json] +{"compilerOptions":{"moduleResolution":"nodenext","outDir":"./dist","declaration":true,"declarationDir":"./types"}} + +//// [/user/username/projects/myproject/package.json] +{"name":"@this/package","type":"module","exports":{".":{"default":"./dist/index.js","types":"./types/index.d.ts"}}} + +//// [/user/username/projects/myproject/index.ts] +import * as me from "@this/package"; +me.thing() +export function thing(): void {} + + +//// [/a/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } + + +/a/lib/tsc.js -w --traceResolution +Output:: +>> Screen clear +[12:00:23 AM] Starting compilation in watch mode... + +Found 'package.json' at '/user/username/projects/myproject/package.json'. +'package.json' does not have a 'typesVersions' field. +Found 'package.json' at '/user/username/projects/myproject/package.json'. +'package.json' does not have a 'typesVersions' field. +======== Resolving module '@this/package' from '/user/username/projects/myproject/index.ts'. ======== +Explicitly specified module resolution kind: 'NodeNext'. +File '/user/username/projects/myproject/package.json' exists according to earlier cached lookups. +File '/user/username/projects/myproject/index.ts' exist - use it as a name resolution result. +Resolving real path for '/user/username/projects/myproject/index.ts', result '/user/username/projects/myproject/index.ts'. +======== Module name '@this/package' was successfully resolved to '/user/username/projects/myproject/index.ts'. ======== +File '/a/lib/package.json' does not exist. +File '/a/package.json' does not exist. +File '/package.json' does not exist. +File '/a/lib/package.json' does not exist. +File '/a/package.json' does not exist. +File '/package.json' does not exist. +[12:00:34 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/user/username/projects/myproject/index.ts"] +Program options: {"moduleResolution":99,"outDir":"/user/username/projects/myproject/dist","declaration":true,"declarationDir":"/user/username/projects/myproject/types","watch":true,"traceResolution":true,"configFilePath":"/user/username/projects/myproject/tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/user/username/projects/myproject/index.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/user/username/projects/myproject/index.ts + +Shape signatures in builder refreshed for:: +/a/lib/lib.d.ts (used version) +/user/username/projects/myproject/index.ts (computed .d.ts during emit) + +WatchedFiles:: +/user/username/projects/myproject/tsconfig.json: + {"fileName":"/user/username/projects/myproject/tsconfig.json","pollingInterval":250} +/user/username/projects/myproject/index.ts: + {"fileName":"/user/username/projects/myproject/index.ts","pollingInterval":250} +/a/lib/lib.d.ts: + {"fileName":"/a/lib/lib.d.ts","pollingInterval":250} +/user/username/projects/myproject/node_modules/@types: + {"fileName":"/user/username/projects/myproject/node_modules/@types","pollingInterval":500} + +FsWatches:: + +FsWatchesRecursive:: +/user/username/projects/myproject: + {"directoryName":"/user/username/projects/myproject"} + +exitCode:: ExitStatus.undefined + +//// [/user/username/projects/myproject/dist/index.js] +"use strict"; +exports.__esModule = true; +exports.thing = void 0; +var me = require("@this/package"); +me.thing(); +function thing() { } +exports.thing = thing; + + +//// [/user/username/projects/myproject/types/index.d.ts] +export declare function thing(): void; + + From 69b38f2a24341fbd164430edea51e94240061921 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 30 Jun 2022 16:12:49 -0700 Subject: [PATCH 2/2] Ensure module resolution cache is passed through in watch mode --- src/compiler/resolutionCache.ts | 6 +++--- src/compiler/tsbuildPublic.ts | 1 + src/compiler/watchPublic.ts | 7 +++++++ src/services/types.ts | 5 ++++- .../reference/api/tsserverlibrary.d.ts | 8 ++++++++ tests/baselines/reference/api/typescript.d.ts | 8 ++++++++ .../moduleResolution/diagnostics-from-cache.js | 17 ++++++++--------- 7 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index efc0b4c95a11c..cd6248119e83f 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -264,6 +264,7 @@ namespace ts { resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); resolvedFileToResolution.clear(); + nonRelativeExternalModuleResolutions.clear(); resolutionsWithFailedLookups.length = 0; resolutionsWithOnlyAffectingLocations.length = 0; failedLookupChecks = undefined; @@ -313,13 +314,12 @@ namespace ts { function clearPerDirectoryResolutions() { moduleResolutionCache.clear(); typeReferenceDirectiveResolutionCache.clear(); - nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); - nonRelativeExternalModuleResolutions.clear(); } function finishCachingPerDirectoryResolution() { filesWithInvalidatedNonRelativeUnresolvedImports = undefined; - clearPerDirectoryResolutions(); + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); directoryWatchesOfFailedLookups.forEach((watcher, path) => { if (watcher.refCount === 0) { directoryWatchesOfFailedLookups.delete(path); diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index b4aecc55c8589..03aba29198a67 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -296,6 +296,7 @@ namespace ts { compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName)); compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames); compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); + compilerHost.getModuleResolutionCache = maybeBind(host, host.getModuleResolutionCache); const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; const typeReferenceDirectiveResolutionCache = !compilerHost.resolveTypeReferenceDirectives ? createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()) : undefined; if (!compilerHost.resolveModuleNames) { diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 060fd309f8b73..3c8c95fcb9c57 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -112,6 +112,10 @@ namespace ts { /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; } @@ -366,6 +370,9 @@ namespace ts { compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? ((...args) => host.resolveTypeReferenceDirectives!(...args)) : ((typeDirectiveNames, containingFile, redirectedReference, _options, containingFileMode) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference, containingFileMode)); + compilerHost.getModuleResolutionCache = host.resolveModuleNames ? + maybeBind(host, host.getModuleResolutionCache) : + (() => resolutionCache.getModuleResolutionCache()); const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T; diff --git a/src/services/types.ts b/src/services/types.ts index 27c7616bf34b2..061e514186b7b 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -292,7 +292,10 @@ namespace ts { /* @internal */ getGlobalTypingsCacheLocation?(): string | undefined; /* @internal */ getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache; /* Lets the Program from a AutoImportProviderProject use its host project's ModuleResolutionCache */ - /* @internal */ getModuleResolutionCache?(): ModuleResolutionCache | undefined; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; /* * Required for full import and type reference completions. diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index fb15ec4f0113b..e9665dd6bd7f3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5403,6 +5403,10 @@ declare namespace ts { getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; } @@ -5801,6 +5805,10 @@ declare namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; getDirectories?(directoryName: string): string[]; /** * Gets a set of custom transformers to use during emit. diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ed87f7ef8913b..60b72631e5493 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5403,6 +5403,10 @@ declare namespace ts { getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; } @@ -5801,6 +5805,10 @@ declare namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; getDirectories?(directoryName: string): string[]; /** * Gets a set of custom transformers to use during emit. diff --git a/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js b/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js index 808ab569c1985..5569058cec851 100644 --- a/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js +++ b/tests/baselines/reference/tscWatch/moduleResolution/diagnostics-from-cache.js @@ -32,8 +32,7 @@ Output:: Found 'package.json' at '/user/username/projects/myproject/package.json'. 'package.json' does not have a 'typesVersions' field. -Found 'package.json' at '/user/username/projects/myproject/package.json'. -'package.json' does not have a 'typesVersions' field. +File '/user/username/projects/myproject/package.json' exists according to earlier cached lookups. ======== Resolving module '@this/package' from '/user/username/projects/myproject/index.ts'. ======== Explicitly specified module resolution kind: 'NodeNext'. File '/user/username/projects/myproject/package.json' exists according to earlier cached lookups. @@ -43,10 +42,12 @@ Resolving real path for '/user/username/projects/myproject/index.ts', result '/u File '/a/lib/package.json' does not exist. File '/a/package.json' does not exist. File '/package.json' does not exist. -File '/a/lib/package.json' does not exist. -File '/a/package.json' does not exist. -File '/package.json' does not exist. -[12:00:34 AM] Found 0 errors. Watching for file changes. +File '/a/lib/package.json' does not exist according to earlier cached lookups. +File '/a/package.json' does not exist according to earlier cached lookups. +File '/package.json' does not exist according to earlier cached lookups. +error TS2209: The project root is ambiguous, but is required to resolve export map entry '.' in file '/user/username/projects/myproject/package.json'. Supply the `rootDir` compiler option to disambiguate. + +[12:00:34 AM] Found 1 error. Watching for file changes. @@ -57,9 +58,7 @@ Program files:: /a/lib/lib.d.ts /user/username/projects/myproject/index.ts -Semantic diagnostics in builder refreshed for:: -/a/lib/lib.d.ts -/user/username/projects/myproject/index.ts +No cached semantic diagnostics in the builder:: Shape signatures in builder refreshed for:: /a/lib/lib.d.ts (used version)