Skip to content

Commit 4887016

Browse files
authored
Check nearest package.json dependencies for possible package names for specifier candidates (#58176)
1 parent e33b8d9 commit 4887016

File tree

23 files changed

+1016
-6
lines changed

23 files changed

+1016
-6
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,14 @@ export interface PackageJsonPathFields {
342342
imports?: object;
343343
exports?: object;
344344
name?: string;
345+
dependencies?: MapLike<string>;
346+
peerDependencies?: MapLike<string>;
347+
optionalDependencies?: MapLike<string>;
345348
}
346349

347350
interface PackageJson extends PackageJsonPathFields {
348351
name?: string;
349352
version?: string;
350-
peerDependencies?: MapLike<string>;
351353
}
352354

353355
function readPackageJsonField<K extends MatchingKeys<PackageJson, string | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined;

src/compiler/moduleSpecifiers.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
comparePaths,
1212
Comparison,
1313
CompilerOptions,
14+
concatenate,
1415
containsIgnoredPath,
1516
containsPath,
1617
createGetCanonicalFileName,
@@ -48,13 +49,15 @@ import {
4849
getOwnKeys,
4950
getPackageJsonTypesVersionsPaths,
5051
getPackageNameFromTypesPackageName,
52+
getPackageScopeForPath,
5153
getPathsBasePath,
5254
getRelativePathFromDirectory,
5355
getRelativePathToDirectoryOrUrl,
5456
getResolvePackageJsonExports,
5557
getResolvePackageJsonImports,
5658
getSourceFileOfModule,
5759
getSupportedExtensions,
60+
getTemporaryModuleResolutionState,
5861
getTextOfIdentifierOrLiteral,
5962
hasJSFileExtension,
6063
hasTSFileExtension,
@@ -84,6 +87,7 @@ import {
8487
ModuleDeclaration,
8588
ModuleKind,
8689
ModulePath,
90+
ModuleResolutionHost,
8791
ModuleResolutionKind,
8892
ModuleSpecifierCache,
8993
ModuleSpecifierEnding,
@@ -92,6 +96,7 @@ import {
9296
NodeFlags,
9397
NodeModulePathParts,
9498
normalizePath,
99+
PackageJsonPathFields,
95100
pathContainsNodeModules,
96101
pathIsBareSpecifier,
97102
pathIsRelative,
@@ -102,6 +107,7 @@ import {
102107
removeTrailingDirectorySeparator,
103108
replaceFirstStar,
104109
ResolutionMode,
110+
resolveModuleName,
105111
resolvePath,
106112
ScriptKind,
107113
shouldAllowImportingTsExtension,
@@ -254,7 +260,7 @@ export function getNodeModulesPackageName(
254260
options: ModuleSpecifierOptions = {},
255261
): string | undefined {
256262
const info = getInfo(importingSourceFile.fileName, host);
257-
const modulePaths = getAllModulePaths(info, nodeModulesFileName, host, preferences, options);
263+
const modulePaths = getAllModulePaths(info, nodeModulesFileName, host, preferences, compilerOptions, options);
258264
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode));
259265
}
260266

@@ -269,7 +275,7 @@ function getModuleSpecifierWorker(
269275
options: ModuleSpecifierOptions = {},
270276
): string {
271277
const info = getInfo(importingSourceFileName, host);
272-
const modulePaths = getAllModulePaths(info, toFileName, host, userPreferences, options);
278+
const modulePaths = getAllModulePaths(info, toFileName, host, userPreferences, compilerOptions, options);
273279
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) ||
274280
getLocalModuleSpecifier(toFileName, info, compilerOptions, host, options.overrideImportMode || getDefaultResolutionModeForFile(importingSourceFile, host, compilerOptions), preferences);
275281
}
@@ -361,7 +367,7 @@ export function getModuleSpecifiersWithCacheInfo(
361367
if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache };
362368

363369
computedWithoutCache = true;
364-
modulePaths ||= getAllModulePathsWorker(getInfo(importingSourceFile.fileName, host), moduleSourceFile.originalFileName, host);
370+
modulePaths ||= getAllModulePathsWorker(getInfo(importingSourceFile.fileName, host), moduleSourceFile.originalFileName, host, compilerOptions, options);
365371
const result = computeModuleSpecifiers(
366372
modulePaths,
367373
compilerOptions,
@@ -691,6 +697,7 @@ function getAllModulePaths(
691697
importedFileName: string,
692698
host: ModuleSpecifierResolutionHost,
693699
preferences: UserPreferences,
700+
compilerOptions: CompilerOptions,
694701
options: ModuleSpecifierOptions = {},
695702
) {
696703
const importingFilePath = toPath(info.importingSourceFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host));
@@ -700,14 +707,45 @@ function getAllModulePaths(
700707
const cached = cache.get(importingFilePath, importedFilePath, preferences, options);
701708
if (cached?.modulePaths) return cached.modulePaths;
702709
}
703-
const modulePaths = getAllModulePathsWorker(info, importedFileName, host);
710+
const modulePaths = getAllModulePathsWorker(info, importedFileName, host, compilerOptions, options);
704711
if (cache) {
705712
cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths);
706713
}
707714
return modulePaths;
708715
}
709716

710-
function getAllModulePathsWorker(info: Info, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] {
717+
const runtimeDependencyFields = ["dependencies", "peerDependencies", "optionalDependencies"] as const;
718+
719+
function getAllRuntimeDependencies(packageJson: PackageJsonPathFields) {
720+
let result;
721+
for (const field of runtimeDependencyFields) {
722+
const deps = packageJson[field];
723+
if (deps && typeof deps === "object") {
724+
result = concatenate(result, getOwnKeys(deps));
725+
}
726+
}
727+
return result;
728+
}
729+
730+
function getAllModulePathsWorker(info: Info, importedFileName: string, host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions, options: ModuleSpecifierOptions): readonly ModulePath[] {
731+
const cache = host.getModuleResolutionCache?.();
732+
const links = host.getSymlinkCache?.();
733+
if (cache && links && host.readFile && !pathContainsNodeModules(info.importingSourceFileName)) {
734+
Debug.type<ModuleResolutionHost>(host);
735+
// Cache resolutions for all `dependencies` of the `package.json` context of the input file.
736+
// This should populate all the relevant symlinks in the symlink cache, and most, if not all, of these resolutions
737+
// should get (re)used.
738+
const state = getTemporaryModuleResolutionState(cache.getPackageJsonInfoCache(), host, {});
739+
const packageJson = getPackageScopeForPath(info.importingSourceFileName, state);
740+
if (packageJson) {
741+
const toResolve = getAllRuntimeDependencies(packageJson.contents.packageJsonContent);
742+
for (const depName of (toResolve || emptyArray)) {
743+
const resolved = resolveModuleName(depName, combinePaths(packageJson.packageDirectory, "package.json"), compilerOptions, host, cache, /*redirectedReference*/ undefined, options.overrideImportMode);
744+
links.setSymlinksFromResolution(resolved.resolvedModule);
745+
}
746+
}
747+
}
748+
711749
const allFileNames = new Map<string, { path: string; isRedirect: boolean; isInNodeModules: boolean; }>();
712750
let importedFileFromNodeModules = false;
713751
forEachFileNameOfModule(

src/compiler/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,12 +2698,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26982698
// Before falling back to the host
26992699
return host.fileExists(f);
27002700
},
2701+
realpath: maybeBind(host, host.realpath),
27012702
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
27022703
getBuildInfo: () => program.getBuildInfo?.(),
27032704
getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref),
27042705
redirectTargetsMap,
27052706
getFileIncludeReasons: program.getFileIncludeReasons,
27062707
createHash: maybeBind(host, host.createHash),
2708+
getModuleResolutionCache: () => program.getModuleResolutionCache(),
2709+
trace: maybeBind(host, host.trace),
27072710
};
27082711
}
27092712

src/compiler/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9709,6 +9709,9 @@ export interface ModuleSpecifierResolutionHost {
97099709
getCommonSourceDirectory(): string;
97109710
getDefaultResolutionModeForFile(sourceFile: SourceFile): ResolutionMode;
97119711
getModeForResolutionAtIndex(file: SourceFile, index: number): ResolutionMode;
9712+
9713+
getModuleResolutionCache?(): ModuleResolutionCache | undefined;
9714+
trace?(s: string): void;
97129715
}
97139716

97149717
/** @internal */

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9080,6 +9080,7 @@ export interface SymlinkCache {
90809080
) => void,
90819081
typeReferenceDirectives: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>,
90829082
): void;
9083+
setSymlinksFromResolution(resolution: ResolvedModuleFull | undefined): void;
90839084
/**
90849085
* @internal
90859086
* Whether `setSymlinksFromResolutions` has already been called.
@@ -9119,6 +9120,9 @@ export function createSymlinkCache(cwd: string, getCanonicalFileName: GetCanonic
91199120
typeReferenceDirectives.forEach(resolution => processResolution(this, resolution.resolvedTypeReferenceDirective));
91209121
},
91219122
hasProcessedResolutions: () => hasProcessedResolutions,
9123+
setSymlinksFromResolution(resolution) {
9124+
processResolution(this, resolution);
9125+
},
91229126
hasAnySymlinks,
91239127
};
91249128

src/testRunner/unittests/tsc/moduleResolution.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,62 @@ describe("unittests:: tsc:: moduleResolution::", () => {
213213
}, { currentDirectory: "/home/src/projects/component-type-checker/packages/app" }),
214214
commandLineArgs: ["--traceResolution", "--explainFiles"],
215215
});
216+
217+
verifyTscWatch({
218+
scenario: "moduleResolution",
219+
subScenario: "late discovered dependency symlink",
220+
sys: () =>
221+
createWatchedSystem({
222+
"/workspace/packageA/index.d.ts": dedent`
223+
export declare class Foo {
224+
private f: any;
225+
}`,
226+
"/workspace/packageB/package.json": dedent`
227+
{
228+
"private": true,
229+
"dependencies": {
230+
"package-a": "file:../packageA"
231+
}
232+
}`,
233+
"/workspace/packageB/index.d.ts": dedent`
234+
import { Foo } from "package-a";
235+
export declare function invoke(): Foo;`,
236+
"/workspace/packageC/package.json": dedent`
237+
{
238+
"private": true,
239+
"dependencies": {
240+
"package-b": "file:../packageB",
241+
"package-a": "file:../packageA"
242+
}
243+
}`,
244+
"/workspace/packageC/index.ts": dedent`
245+
import * as pkg from "package-b";
246+
247+
export const a = pkg.invoke();`,
248+
"/workspace/packageC/node_modules/package-a": { symLink: "/workspace/packageA" },
249+
"/workspace/packageB/node_modules/package-a": { symLink: "/workspace/packageA" },
250+
"/workspace/packageC/node_modules/package-b": { symLink: "/workspace/packageB" },
251+
"/a/lib/lib.d.ts": libContent,
252+
"/workspace/packageC/tsconfig.json": jsonToReadableText({
253+
compilerOptions: {
254+
declaration: true,
255+
},
256+
}),
257+
}, { currentDirectory: "/workspace/packageC" }),
258+
commandLineArgs: ["--traceResolution", "--explainFiles", "--watch"],
259+
edits: [
260+
{
261+
caption: "change index.ts",
262+
edit: fs =>
263+
fs.writeFile(
264+
"/workspace/packageC/index.ts",
265+
dedent`
266+
import * as pkg from "package-b";
267+
268+
export const aa = pkg.invoke();`,
269+
),
270+
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
271+
},
272+
],
273+
});
216274
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//// [tests/cases/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.ts] ////
2+
3+
//// [foo.d.ts]
4+
export declare class Foo {
5+
private f: any;
6+
}
7+
//// [index.d.ts]
8+
import { Foo } from "./foo.js";
9+
export function create(): Foo;
10+
//// [package.json]
11+
{
12+
"name": "package-a",
13+
"version": "0.0.1",
14+
"exports": {
15+
".": "./index.js",
16+
"./cls": "./foo.js"
17+
}
18+
}
19+
//// [package.json]
20+
{
21+
"private": true,
22+
"dependencies": {
23+
"package-a": "file:../packageA"
24+
}
25+
}
26+
//// [index.d.ts]
27+
import { create } from "package-a";
28+
export declare function invoke(): ReturnType<typeof create>;
29+
//// [package.json]
30+
{
31+
"private": true,
32+
"dependencies": {
33+
"package-b": "file:../packageB",
34+
"package-a": "file:../packageA"
35+
}
36+
}
37+
//// [index.ts]
38+
import * as pkg from "package-b";
39+
40+
export const a = pkg.invoke();
41+
42+
//// [index.js]
43+
"use strict";
44+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
45+
if (k2 === undefined) k2 = k;
46+
var desc = Object.getOwnPropertyDescriptor(m, k);
47+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
48+
desc = { enumerable: true, get: function() { return m[k]; } };
49+
}
50+
Object.defineProperty(o, k2, desc);
51+
}) : (function(o, m, k, k2) {
52+
if (k2 === undefined) k2 = k;
53+
o[k2] = m[k];
54+
}));
55+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
56+
Object.defineProperty(o, "default", { enumerable: true, value: v });
57+
}) : function(o, v) {
58+
o["default"] = v;
59+
});
60+
var __importStar = (this && this.__importStar) || function (mod) {
61+
if (mod && mod.__esModule) return mod;
62+
var result = {};
63+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
64+
__setModuleDefault(result, mod);
65+
return result;
66+
};
67+
Object.defineProperty(exports, "__esModule", { value: true });
68+
exports.a = void 0;
69+
const pkg = __importStar(require("package-b"));
70+
exports.a = pkg.invoke();
71+
72+
73+
//// [index.d.ts]
74+
export declare const a: import("package-a/cls").Foo;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [tests/cases/compiler/symlinkedWorkspaceDependenciesNoDirectLinkGeneratesDeepNonrelativeName.ts] ////
2+
3+
=== workspace/packageA/foo.d.ts ===
4+
export declare class Foo {
5+
>Foo : Symbol(Foo, Decl(foo.d.ts, 0, 0))
6+
7+
private f: any;
8+
>f : Symbol(Foo.f, Decl(foo.d.ts, 0, 26))
9+
}
10+
=== workspace/packageA/index.d.ts ===
11+
import { Foo } from "./foo.js";
12+
>Foo : Symbol(Foo, Decl(index.d.ts, 0, 8))
13+
14+
export function create(): Foo;
15+
>create : Symbol(create, Decl(index.d.ts, 0, 31))
16+
>Foo : Symbol(Foo, Decl(index.d.ts, 0, 8))
17+
18+
=== workspace/packageB/index.d.ts ===
19+
import { create } from "package-a";
20+
>create : Symbol(create, Decl(index.d.ts, 0, 8))
21+
22+
export declare function invoke(): ReturnType<typeof create>;
23+
>invoke : Symbol(invoke, Decl(index.d.ts, 0, 35))
24+
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
25+
>create : Symbol(create, Decl(index.d.ts, 0, 8))
26+
27+
=== workspace/packageC/index.ts ===
28+
import * as pkg from "package-b";
29+
>pkg : Symbol(pkg, Decl(index.ts, 0, 6))
30+
31+
export const a = pkg.invoke();
32+
>a : Symbol(a, Decl(index.ts, 2, 12))
33+
>pkg.invoke : Symbol(pkg.invoke, Decl(index.d.ts, 0, 35))
34+
>pkg : Symbol(pkg, Decl(index.ts, 0, 6))
35+
>invoke : Symbol(pkg.invoke, Decl(index.d.ts, 0, 35))
36+

0 commit comments

Comments
 (0)