@@ -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 {
@@ -1117,8 +1160,14 @@ namespace ts {
1117
1160
if ( traceEnabled ) {
1118
1161
trace ( host , Diagnostics . Loading_module_0_from_node_modules_folder_target_file_type_1 , moduleName , Extensions [ extensions ] ) ;
1119
1162
}
1120
- const resolved = loadModuleFromNearestNodeModulesDirectory ( extensions , moduleName , containingDirectory , state , cache , redirectedReference ) ;
1121
- if ( ! resolved ) return undefined ;
1163
+
1164
+ const resolved = getPnpApi ( containingDirectory )
1165
+ ? tryLoadModuleUsingPnpResolution ( extensions , moduleName , containingDirectory , state )
1166
+ : loadModuleFromNearestNodeModulesDirectory ( extensions , moduleName , containingDirectory , state , cache , redirectedReference ) ;
1167
+
1168
+ if ( ! resolved ) {
1169
+ return undefined ;
1170
+ }
1122
1171
1123
1172
let resolvedValue = resolved . value ;
1124
1173
if ( ! compilerOptions . preserveSymlinks && resolvedValue && ! resolvedValue . originalPath ) {
@@ -1497,7 +1546,15 @@ namespace ts {
1497
1546
1498
1547
function loadModuleFromSpecificNodeModulesDirectory ( extensions : Extensions , moduleName : string , nodeModulesDirectory : string , nodeModulesDirectoryExists : boolean , state : ModuleResolutionState ) : Resolved | undefined {
1499
1548
const candidate = normalizePath ( combinePaths ( nodeModulesDirectory , moduleName ) ) ;
1549
+ return loadModuleFromSpecificNodeModulesDirectoryImpl ( extensions , moduleName , nodeModulesDirectory , nodeModulesDirectoryExists , state , candidate , undefined , undefined ) ;
1550
+ }
1500
1551
1552
+ function loadModuleFromPnpResolution ( extensions : Extensions , packageDirectory : string , rest : string , state : ModuleResolutionState ) : Resolved | undefined {
1553
+ const candidate = normalizePath ( combinePaths ( packageDirectory , rest ) ) ;
1554
+ return loadModuleFromSpecificNodeModulesDirectoryImpl ( extensions , undefined , undefined , true , state , candidate , rest , packageDirectory ) ;
1555
+ }
1556
+
1557
+ function loadModuleFromSpecificNodeModulesDirectoryImpl ( extensions : Extensions , moduleName : string | undefined , nodeModulesDirectory : string | undefined , nodeModulesDirectoryExists : boolean , state : ModuleResolutionState , candidate : string , rest : string | undefined , packageDirectory : string | undefined ) : Resolved | undefined {
1501
1558
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
1502
1559
let packageInfo = getPackageJsonInfo ( candidate , ! nodeModulesDirectoryExists , state ) ;
1503
1560
if ( packageInfo ) {
@@ -1531,9 +1588,10 @@ namespace ts {
1531
1588
return withPackageId ( packageInfo , pathAndExtension ) ;
1532
1589
} ;
1533
1590
1534
- const { packageName, rest } = parsePackageName ( moduleName ) ;
1591
+ let packageName : string ;
1592
+ if ( rest === undefined ) ( { packageName, rest } = parsePackageName ( moduleName ! ) ) ;
1535
1593
if ( rest !== "" ) { // If "rest" is empty, we just did this search above.
1536
- const packageDirectory = combinePaths ( nodeModulesDirectory , packageName ) ;
1594
+ if ( packageDirectory === undefined ) packageDirectory = combinePaths ( nodeModulesDirectory ! , packageName ! ) ;
1537
1595
1538
1596
// 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.
1539
1597
packageInfo = getPackageJsonInfo ( packageDirectory , ! nodeModulesDirectoryExists , state ) ;
@@ -1713,4 +1771,64 @@ namespace ts {
1713
1771
function toSearchResult < T > ( value : T | undefined ) : SearchResult < T > {
1714
1772
return value !== undefined ? { value } : undefined ;
1715
1773
}
1774
+
1775
+ /**
1776
+ * We only allow PnP to be used as a resolution strategy if TypeScript
1777
+ * itself is executed under a PnP runtime (and we only allow it to access
1778
+ * the current PnP runtime, not any on the disk). This ensures that we
1779
+ * don't execute potentially malicious code that didn't already have a
1780
+ * chance to be executed (if we're running within the runtime, it means
1781
+ * that the runtime has already been executed).
1782
+ * @internal
1783
+ */
1784
+ function getPnpApi ( path : string ) {
1785
+ const { findPnpApi} = require ( "module" ) ;
1786
+ if ( findPnpApi === undefined ) {
1787
+ return undefined ;
1788
+ }
1789
+ return findPnpApi ( `${ path } /` ) ;
1790
+ }
1791
+
1792
+ function loadPnpPackageResolution ( packageName : string , containingDirectory : string ) {
1793
+ try {
1794
+ const resolution = getPnpApi ( containingDirectory ) . resolveToUnqualified ( packageName , `${ containingDirectory } /` , { considerBuiltins : false } ) ;
1795
+ return normalizeSlashes ( resolution ) ;
1796
+ }
1797
+ catch {
1798
+ // Nothing to do
1799
+ }
1800
+ }
1801
+
1802
+ function loadPnpTypePackageResolution ( packageName : string , containingDirectory : string ) {
1803
+ return loadPnpPackageResolution ( getTypesPackageName ( packageName ) , containingDirectory ) ;
1804
+ }
1805
+
1806
+ /* @internal */
1807
+ function tryLoadModuleUsingPnpResolution ( extensions : Extensions , moduleName : string , containingDirectory : string , state : ModuleResolutionState ) {
1808
+ const { packageName, rest} = parsePackageName ( moduleName ) ;
1809
+
1810
+ const packageResolution = loadPnpPackageResolution ( packageName , containingDirectory ) ;
1811
+ const packageFullResolution = packageResolution
1812
+ ? loadModuleFromPnpResolution ( extensions , packageResolution , rest , state )
1813
+ : undefined ;
1814
+
1815
+ let resolved ;
1816
+ if ( packageFullResolution ) {
1817
+ resolved = packageFullResolution ;
1818
+ }
1819
+ else if ( extensions === Extensions . TypeScript || extensions === Extensions . DtsOnly ) {
1820
+ const typePackageResolution = loadPnpTypePackageResolution ( packageName , containingDirectory ) ;
1821
+ const typePackageFullResolution = typePackageResolution
1822
+ ? loadModuleFromPnpResolution ( Extensions . DtsOnly , typePackageResolution , rest , state )
1823
+ : undefined ;
1824
+
1825
+ if ( typePackageFullResolution ) {
1826
+ resolved = typePackageFullResolution ;
1827
+ }
1828
+ }
1829
+
1830
+ if ( resolved ) {
1831
+ return toSearchResult ( resolved ) ;
1832
+ }
1833
+ }
1716
1834
}
0 commit comments