Skip to content

Commit 5d50de3

Browse files
committed
Implements native support for PnP
1 parent e39bdc3 commit 5d50de3

File tree

1 file changed

+96
-6
lines changed

1 file changed

+96
-6
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -259,26 +259,56 @@ namespace ts {
259259

260260
/**
261261
* Returns the path to every node_modules/@types directory from some ancestor directory.
262-
* Returns undefined if there are none.
263262
*/
264-
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
263+
function getNodeModulesTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }) {
265264
if (!host.directoryExists) {
266265
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
267266
// And if it doesn't exist, tough.
268267
}
269268

270-
let typeRoots: string[] | undefined;
269+
const typeRoots: string[] = [];
271270
forEachAncestorDirectory(normalizePath(currentDirectory), directory => {
272271
const atTypes = combinePaths(directory, nodeModulesAtTypes);
273272
if (host.directoryExists!(atTypes)) {
274-
(typeRoots || (typeRoots = [])).push(atTypes);
273+
typeRoots.push(atTypes);
275274
}
276275
return undefined;
277276
});
277+
278278
return typeRoots;
279279
}
280280
const nodeModulesAtTypes = combinePaths("node_modules", "@types");
281281

282+
function getPnpTypeRoots(currentDirectory: string) {
283+
if (!isPnpAvailable()) {
284+
return [];
285+
}
286+
287+
const pnpapi = getPnpApi();
288+
const locator = pnpapi.findPackageLocator(`${currentDirectory}/`);
289+
const {packageDependencies} = pnpapi.getPackageInformation(locator);
290+
291+
const typeRoots: string[] = [];
292+
for (const [name, referencish] of Array.from<any>(packageDependencies.entries())) {
293+
if (name.startsWith(typesPackagePrefix) && referencish !== null) {
294+
const dependencyLocator = pnpapi.getLocator(name, referencish);
295+
const {packageLocation} = pnpapi.getPackageInformation(dependencyLocator);
296+
297+
typeRoots.push(getDirectoryPath(packageLocation));
298+
}
299+
}
300+
301+
return typeRoots;
302+
}
303+
const typesPackagePrefix = "@types/";
304+
305+
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
306+
const nmTypes = getNodeModulesTypeRoots(currentDirectory, host);
307+
const pnpTypes = getPnpTypeRoots(currentDirectory);
308+
309+
return [...nmTypes, ...pnpTypes];
310+
}
311+
282312
/**
283313
* @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
284314
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
@@ -951,8 +981,14 @@ namespace ts {
951981
if (traceEnabled) {
952982
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]);
953983
}
954-
const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference);
955-
if (!resolved) return undefined;
984+
985+
const resolved = isPnpAvailable()
986+
? tryLoadModuleUsingPnpResolution(extensions, moduleName, containingDirectory, state)
987+
: loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference);
988+
989+
if (!resolved) {
990+
return undefined;
991+
}
956992

957993
let resolvedValue = resolved.value;
958994
if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) {
@@ -1524,4 +1560,58 @@ namespace ts {
15241560
function toSearchResult<T>(value: T | undefined): SearchResult<T> {
15251561
return value !== undefined ? { value } : undefined;
15261562
}
1563+
1564+
/**
1565+
* We only allow PnP to be used as a resolution strategy if TypeScript
1566+
* itself is executed under a PnP runtime (and we only allow it to access
1567+
* the current PnP runtime, not any on the disk). This ensures that we
1568+
* don't execute potentially malicious code that didn't already have a
1569+
* chance to be executed (if we're running within the runtime, it means
1570+
* that the runtime has already been executed).
1571+
* @internal
1572+
*/
1573+
export function isPnpAvailable() {
1574+
// @ts-ignore
1575+
return process.versions.pnp;
1576+
}
1577+
1578+
function getPnpApi() {
1579+
return require("pnpapi");
1580+
}
1581+
1582+
function loadPnpPackageResolution(packageName: string, issuer: string) {
1583+
return getPnpApi().resolveToUnqualified(packageName, issuer, { considerBuiltins: false });
1584+
}
1585+
1586+
function loadPnpTypePackageResolution(packageName: string, issuer: string) {
1587+
return loadPnpPackageResolution(getTypesPackageName(packageName), issuer);
1588+
}
1589+
1590+
/* @internal */
1591+
export function tryLoadModuleUsingPnpResolution(extensions: Extensions, moduleName: string, issuer: string, state: ModuleResolutionState) {
1592+
const {packageName, rest} = parsePackageName(moduleName);
1593+
1594+
const packageResolution = loadPnpPackageResolution(packageName, issuer);
1595+
const packageFullResolution = packageResolution !== null
1596+
? nodeLoadModuleByRelativeName(extensions, combinePaths(packageResolution, rest), /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true)
1597+
: undefined;
1598+
1599+
let resolved;
1600+
if (packageFullResolution) {
1601+
resolved = packageFullResolution;
1602+
} else if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) {
1603+
const typePackageResolution = loadPnpTypePackageResolution(packageName, issuer);
1604+
const typePackageFullResolution = typePackageResolution !== null
1605+
? nodeLoadModuleByRelativeName(Extensions.DtsOnly, combinePaths(typePackageResolution, rest), /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true)
1606+
: undefined;
1607+
1608+
if (typePackageFullResolution) {
1609+
resolved = typePackageFullResolution;
1610+
}
1611+
}
1612+
1613+
if (resolved) {
1614+
return toSearchResult(resolved);
1615+
}
1616+
}
15271617
}

0 commit comments

Comments
 (0)