@@ -260,26 +260,66 @@ namespace ts {
260
260
261
261
/**
262
262
* Returns the path to every node_modules/@types directory from some ancestor directory.
263
- * Returns undefined if there are none.
264
263
*/
265
- function getDefaultTypeRoots ( currentDirectory : string , host : { directoryExists ?: ( directoryName : string ) => boolean } ) : string [ ] | undefined {
264
+ function getNodeModulesTypeRoots ( currentDirectory : string , host : { directoryExists ?: ( directoryName : string ) => boolean } ) {
266
265
if ( ! host . directoryExists ) {
267
266
return [ combinePaths ( currentDirectory , nodeModulesAtTypes ) ] ;
268
267
// And if it doesn't exist, tough.
269
268
}
270
269
271
- let typeRoots : string [ ] | undefined ;
270
+ const typeRoots : string [ ] = [ ] ;
272
271
forEachAncestorDirectory ( normalizePath ( currentDirectory ) , directory => {
273
272
const atTypes = combinePaths ( directory , nodeModulesAtTypes ) ;
274
273
if ( host . directoryExists ! ( atTypes ) ) {
275
- ( typeRoots || ( typeRoots = [ ] ) ) . push ( atTypes ) ;
274
+ typeRoots . push ( atTypes ) ;
276
275
}
277
276
return undefined ;
278
277
} ) ;
278
+
279
279
return typeRoots ;
280
280
}
281
281
const nodeModulesAtTypes = combinePaths ( "node_modules" , "@types" ) ;
282
282
283
+ export function getPnpTypeRoots ( currentDirectory : string ) {
284
+ const pnpapi = getPnpApi ( currentDirectory ) ;
285
+ if ( ! pnpapi ) {
286
+ return [ ] ;
287
+ }
288
+
289
+ // Some TS consumers pass relative paths that aren't normalized
290
+ currentDirectory = sys . resolvePath ( currentDirectory ) ;
291
+
292
+ const currentPackage = pnpapi . findPackageLocator ( `${ currentDirectory } /` ) ;
293
+ if ( ! currentPackage ) {
294
+ return [ ] ;
295
+ }
296
+
297
+ const { packageDependencies} = pnpapi . getPackageInformation ( currentPackage ) ;
298
+
299
+ const typeRoots : string [ ] = [ ] ;
300
+ for ( const [ name , referencish ] of Array . from < any > ( packageDependencies . entries ( ) ) ) {
301
+ // eslint-disable-next-line no-null/no-null
302
+ if ( name . startsWith ( typesPackagePrefix ) && referencish !== null ) {
303
+ const dependencyLocator = pnpapi . getLocator ( name , referencish ) ;
304
+ const { packageLocation} = pnpapi . getPackageInformation ( dependencyLocator ) ;
305
+
306
+ typeRoots . push ( getDirectoryPath ( packageLocation ) ) ;
307
+ }
308
+ }
309
+
310
+ return typeRoots ;
311
+ }
312
+ const typesPackagePrefix = "@types/" ;
313
+
314
+ function getDefaultTypeRoots ( currentDirectory : string , host : { directoryExists ?: ( directoryName : string ) => boolean } ) : string [ ] | undefined {
315
+ const nmTypes = getNodeModulesTypeRoots ( currentDirectory , host ) ;
316
+ const pnpTypes = getPnpTypeRoots ( currentDirectory ) ;
317
+
318
+ if ( nmTypes . length > 0 || pnpTypes . length > 0 ) {
319
+ return [ ...nmTypes , ...pnpTypes ] ;
320
+ }
321
+ }
322
+
283
323
/**
284
324
* @param {string | undefined } containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
285
325
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
@@ -400,7 +440,10 @@ namespace ts {
400
440
}
401
441
let result : Resolved | undefined ;
402
442
if ( ! isExternalModuleNameRelative ( typeReferenceDirectiveName ) ) {
403
- const searchResult = loadModuleFromNearestNodeModulesDirectory ( Extensions . DtsOnly , typeReferenceDirectiveName , initialLocationForSecondaryLookup , moduleResolutionState , /*cache*/ undefined , /*redirectedReference*/ undefined ) ;
443
+ const searchResult = getPnpApi ( initialLocationForSecondaryLookup )
444
+ ? tryLoadModuleUsingPnpResolution ( Extensions . DtsOnly , typeReferenceDirectiveName , initialLocationForSecondaryLookup , moduleResolutionState )
445
+ : loadModuleFromNearestNodeModulesDirectory ( Extensions . DtsOnly , typeReferenceDirectiveName , initialLocationForSecondaryLookup , moduleResolutionState , /*cache*/ undefined , /*redirectedReference*/ undefined ) ;
446
+
404
447
result = searchResult && searchResult . value ;
405
448
}
406
449
else {
@@ -1111,8 +1154,14 @@ namespace ts {
1111
1154
if ( traceEnabled ) {
1112
1155
trace ( host , Diagnostics . Loading_module_0_from_node_modules_folder_target_file_type_1 , moduleName , Extensions [ extensions ] ) ;
1113
1156
}
1114
- const resolved = loadModuleFromNearestNodeModulesDirectory ( extensions , moduleName , containingDirectory , state , cache , redirectedReference ) ;
1115
- if ( ! resolved ) return undefined ;
1157
+
1158
+ const resolved = getPnpApi ( containingDirectory )
1159
+ ? tryLoadModuleUsingPnpResolution ( extensions , moduleName , containingDirectory , state )
1160
+ : loadModuleFromNearestNodeModulesDirectory ( extensions , moduleName , containingDirectory , state , cache , redirectedReference ) ;
1161
+
1162
+ if ( ! resolved ) {
1163
+ return undefined ;
1164
+ }
1116
1165
1117
1166
let resolvedValue = resolved . value ;
1118
1167
if ( ! compilerOptions . preserveSymlinks && resolvedValue && ! resolvedValue . originalPath ) {
@@ -1491,7 +1540,15 @@ namespace ts {
1491
1540
1492
1541
function loadModuleFromSpecificNodeModulesDirectory ( extensions : Extensions , moduleName : string , nodeModulesDirectory : string , nodeModulesDirectoryExists : boolean , state : ModuleResolutionState ) : Resolved | undefined {
1493
1542
const candidate = normalizePath ( combinePaths ( nodeModulesDirectory , moduleName ) ) ;
1543
+ return loadModuleFromSpecificNodeModulesDirectoryImpl ( extensions , moduleName , nodeModulesDirectory , nodeModulesDirectoryExists , state , candidate , undefined , undefined ) ;
1544
+ }
1494
1545
1546
+ function loadModuleFromPnpResolution ( extensions : Extensions , packageDirectory : string , rest : string , state : ModuleResolutionState ) : Resolved | undefined {
1547
+ const candidate = normalizePath ( combinePaths ( packageDirectory , rest ) ) ;
1548
+ return loadModuleFromSpecificNodeModulesDirectoryImpl ( extensions , undefined , undefined , true , state , candidate , rest , packageDirectory ) ;
1549
+ }
1550
+
1551
+ function loadModuleFromSpecificNodeModulesDirectoryImpl ( extensions : Extensions , moduleName : string | undefined , nodeModulesDirectory : string | undefined , nodeModulesDirectoryExists : boolean , state : ModuleResolutionState , candidate : string , rest : string | undefined , packageDirectory : string | undefined ) : Resolved | undefined {
1495
1552
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
1496
1553
let packageInfo = getPackageJsonInfo ( candidate , ! nodeModulesDirectoryExists , state ) ;
1497
1554
if ( packageInfo ) {
@@ -1525,9 +1582,10 @@ namespace ts {
1525
1582
return withPackageId ( packageInfo , pathAndExtension ) ;
1526
1583
} ;
1527
1584
1528
- const { packageName, rest } = parsePackageName ( moduleName ) ;
1585
+ let packageName : string ;
1586
+ if ( rest === undefined ) ( { packageName, rest } = parsePackageName ( moduleName ! ) ) ;
1529
1587
if ( rest !== "" ) { // If "rest" is empty, we just did this search above.
1530
- const packageDirectory = combinePaths ( nodeModulesDirectory , packageName ) ;
1588
+ if ( packageDirectory === undefined ) packageDirectory = combinePaths ( nodeModulesDirectory ! , packageName ! ) ;
1531
1589
1532
1590
// Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings.
1533
1591
packageInfo = getPackageJsonInfo ( packageDirectory , ! nodeModulesDirectoryExists , state ) ;
@@ -1706,4 +1764,64 @@ namespace ts {
1706
1764
function toSearchResult < T > ( value : T | undefined ) : SearchResult < T > {
1707
1765
return value !== undefined ? { value } : undefined ;
1708
1766
}
1767
+
1768
+ /**
1769
+ * We only allow PnP to be used as a resolution strategy if TypeScript
1770
+ * itself is executed under a PnP runtime (and we only allow it to access
1771
+ * the current PnP runtime, not any on the disk). This ensures that we
1772
+ * don't execute potentially malicious code that didn't already have a
1773
+ * chance to be executed (if we're running within the runtime, it means
1774
+ * that the runtime has already been executed).
1775
+ * @internal
1776
+ */
1777
+ function getPnpApi ( path : string ) {
1778
+ const { findPnpApi} = require ( "module" ) ;
1779
+ if ( findPnpApi === undefined ) {
1780
+ return undefined ;
1781
+ }
1782
+ return findPnpApi ( `${ path } /` ) ;
1783
+ }
1784
+
1785
+ function loadPnpPackageResolution ( packageName : string , containingDirectory : string ) {
1786
+ try {
1787
+ const resolution = getPnpApi ( containingDirectory ) . resolveToUnqualified ( packageName , `${ containingDirectory } /` , { considerBuiltins : false } ) ;
1788
+ return normalizeSlashes ( resolution ) ;
1789
+ }
1790
+ catch {
1791
+ // Nothing to do
1792
+ }
1793
+ }
1794
+
1795
+ function loadPnpTypePackageResolution ( packageName : string , containingDirectory : string ) {
1796
+ return loadPnpPackageResolution ( getTypesPackageName ( packageName ) , containingDirectory ) ;
1797
+ }
1798
+
1799
+ /* @internal */
1800
+ function tryLoadModuleUsingPnpResolution ( extensions : Extensions , moduleName : string , containingDirectory : string , state : ModuleResolutionState ) {
1801
+ const { packageName, rest} = parsePackageName ( moduleName ) ;
1802
+
1803
+ const packageResolution = loadPnpPackageResolution ( packageName , containingDirectory ) ;
1804
+ const packageFullResolution = packageResolution
1805
+ ? loadModuleFromPnpResolution ( extensions , packageResolution , rest , state )
1806
+ : undefined ;
1807
+
1808
+ let resolved ;
1809
+ if ( packageFullResolution ) {
1810
+ resolved = packageFullResolution ;
1811
+ }
1812
+ else if ( extensions === Extensions . TypeScript || extensions === Extensions . DtsOnly ) {
1813
+ const typePackageResolution = loadPnpTypePackageResolution ( packageName , containingDirectory ) ;
1814
+ const typePackageFullResolution = typePackageResolution
1815
+ ? loadModuleFromPnpResolution ( Extensions . DtsOnly , typePackageResolution , rest , state )
1816
+ : undefined ;
1817
+
1818
+ if ( typePackageFullResolution ) {
1819
+ resolved = typePackageFullResolution ;
1820
+ }
1821
+ }
1822
+
1823
+ if ( resolved ) {
1824
+ return toSearchResult ( resolved ) ;
1825
+ }
1826
+ }
1709
1827
}
0 commit comments