Skip to content

Commit e33ced1

Browse files
committed
Detects workspaces when suggesting package names
1 parent 2f85932 commit e33ced1

File tree

1 file changed

+49
-11
lines changed

1 file changed

+49
-11
lines changed

src/compiler/moduleSpecifiers.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,27 @@ namespace ts.moduleSpecifiers {
290290
if (!host.fileExists || !host.readFile) {
291291
return undefined;
292292
}
293-
const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!;
293+
294+
let parts: NodeModulePathParts | PackagePathParts | undefined
295+
= getNodeModulePathParts(moduleFileName);
296+
297+
let packageName: string | undefined;
298+
if (!parts && isPnpAvailable()) {
299+
const pnpApi = require(`pnpapi`) as any;
300+
const locator = pnpApi.findPackageLocator(moduleFileName);
301+
// eslint-disable-next-line no-null/no-null
302+
if (locator !== null) {
303+
const information = pnpApi.getPackageInformation(locator);
304+
packageName = locator.name;
305+
parts = {
306+
topLevelNodeModulesIndex: undefined,
307+
topLevelPackageNameIndex: undefined,
308+
packageRootIndex: information.packageLocation.length,
309+
fileNameIndex: moduleFileName.lastIndexOf(`/`),
310+
};
311+
}
312+
}
313+
294314
if (!parts) {
295315
return undefined;
296316
}
@@ -322,21 +342,33 @@ namespace ts.moduleSpecifiers {
322342

323343
// If the module could be imported by a directory name, use that directory's name
324344
const moduleSpecifier = packageNameOnly ? moduleFileName : getDirectoryOrExtensionlessFileName(moduleFileName);
325-
const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation();
326-
// Get a path that's relative to node_modules or the importing file's path
327-
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
328-
const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex));
345+
// If we already know the package name, no need to go further
346+
if (packageName !== undefined) {
347+
const typelessPackageName = getPackageNameFromTypesPackageName(packageName);
348+
return typelessPackageName + moduleSpecifier.substring(parts.packageRootIndex);
349+
}
350+
351+
if (parts.topLevelNodeModulesIndex === undefined || parts.topLevelPackageNameIndex === undefined) {
352+
return undefined;
353+
}
354+
329355
// If PnP is enabled the node_modules entries we'll get will always be relevant even if they
330356
// are located in a weird path apparently outside of the source directory
331-
if (!isPnpAvailable() && !(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) {
332-
return undefined;
357+
if (!isPnpAvailable()) {
358+
const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation();
359+
// Get a path that's relative to node_modules or the importing file's path
360+
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
361+
const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex));
362+
if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) {
363+
return undefined;
364+
}
333365
}
334366

335367
// If the module was found in @types, get the actual Node package name
336368
const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1);
337-
const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName);
369+
const packageNameFromPath = getPackageNameFromTypesPackageName(nodeModulesDirectoryName);
338370
// For classic resolution, only allow importing from node_modules/@types, not other node_modules
339-
return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName;
371+
return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageNameFromPath === nodeModulesDirectoryName ? undefined : packageNameFromPath;
340372

341373
function getDirectoryOrExtensionlessFileName(path: string): string {
342374
// If the file is the main module, it can be imported by the package name
@@ -355,8 +387,8 @@ namespace ts.moduleSpecifiers {
355387

356388
// If the file is /index, it can be imported by its directory name
357389
// IFF there is not _also_ a file by the same name
358-
if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) {
359-
return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex);
390+
if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts!.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts!.fileNameIndex))) {
391+
return fullModulePathWithoutExtension.substring(0, parts!.fileNameIndex);
360392
}
361393

362394
return fullModulePathWithoutExtension;
@@ -381,6 +413,12 @@ namespace ts.moduleSpecifiers {
381413
readonly packageRootIndex: number;
382414
readonly fileNameIndex: number;
383415
}
416+
interface PackagePathParts {
417+
readonly topLevelNodeModulesIndex: undefined;
418+
readonly topLevelPackageNameIndex: undefined;
419+
readonly packageRootIndex: number;
420+
readonly fileNameIndex: number;
421+
}
384422
function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined {
385423
// If fullPath can't be valid module file within node_modules, returns undefined.
386424
// Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js

0 commit comments

Comments
 (0)