github.com/evanw/esbuild@v0.21.4/internal/resolver/yarnpnp.go (about)

     1  package resolver
     2  
     3  // This file implements the Yarn PnP specification: https://yarnpkg.com/advanced/pnp-spec/
     4  
     5  import (
     6  	"fmt"
     7  	"regexp"
     8  	"strings"
     9  	"syscall"
    10  
    11  	"github.com/evanw/esbuild/internal/helpers"
    12  	"github.com/evanw/esbuild/internal/js_ast"
    13  	"github.com/evanw/esbuild/internal/js_parser"
    14  	"github.com/evanw/esbuild/internal/logger"
    15  )
    16  
    17  type pnpData struct {
    18  	// Keys are the package idents, values are sets of references. Combining the
    19  	// ident with each individual reference yields the set of affected locators.
    20  	fallbackExclusionList map[string]map[string]bool
    21  
    22  	// A map of locators that all packages are allowed to access, regardless
    23  	// whether they list them in their dependencies or not.
    24  	fallbackPool map[string]pnpIdentAndReference
    25  
    26  	// A nullable regexp. If set, all project-relative importer paths should be
    27  	// matched against it. If the match succeeds, the resolution should follow
    28  	// the classic Node.js resolution algorithm rather than the Plug'n'Play one.
    29  	// Note that unlike other paths in the manifest, the one checked against this
    30  	// regexp won't begin by `./`.
    31  	ignorePatternData        *regexp.Regexp
    32  	invalidIgnorePatternData string
    33  
    34  	// This is the main part of the PnP data file. This table contains the list
    35  	// of all packages, first keyed by package ident then by package reference.
    36  	// One entry will have `null` in both fields and represents the absolute
    37  	// top-level package.
    38  	packageRegistryData map[string]map[string]pnpPackage
    39  
    40  	packageLocatorsByLocations map[string]pnpPackageLocatorByLocation
    41  
    42  	// If true, should a dependency resolution fail for an importer that isn't
    43  	// explicitly listed in `fallbackExclusionList`, the runtime must first check
    44  	// whether the resolution would succeed for any of the packages in
    45  	// `fallbackPool`; if it would, transparently return this resolution. Note
    46  	// that all dependencies from the top-level package are implicitly part of
    47  	// the fallback pool, even if not listed here.
    48  	enableTopLevelFallback bool
    49  
    50  	tracker    logger.LineColumnTracker
    51  	absPath    string
    52  	absDirPath string
    53  }
    54  
    55  // This is called both a "locator" and a "dependency target" in the specification.
    56  // When it's used as a dependency target, it can only be in one of three states:
    57  //
    58  //  1. A reference, to link with the dependency name
    59  //     In this case ident is "".
    60  //
    61  //  2. An aliased package
    62  //     In this case neither ident nor reference are "".
    63  //
    64  //  3. A missing peer dependency
    65  //     In this case ident and reference are "".
    66  type pnpIdentAndReference struct {
    67  	ident     string // Empty if null
    68  	reference string // Empty if null
    69  	span      logger.Range
    70  }
    71  
    72  type pnpPackage struct {
    73  	packageDependencies      map[string]pnpIdentAndReference
    74  	packageLocation          string
    75  	packageDependenciesRange logger.Range
    76  	discardFromLookup        bool
    77  }
    78  
    79  type pnpPackageLocatorByLocation struct {
    80  	locator           pnpIdentAndReference
    81  	discardFromLookup bool
    82  }
    83  
    84  func parseBareIdentifier(specifier string) (ident string, modulePath string, ok bool) {
    85  	slash := strings.IndexByte(specifier, '/')
    86  
    87  	// If specifier starts with "@", then
    88  	if strings.HasPrefix(specifier, "@") {
    89  		// If specifier doesn't contain a "/" separator, then
    90  		if slash == -1 {
    91  			// Throw an error
    92  			return
    93  		}
    94  
    95  		// Otherwise,
    96  		// Set ident to the substring of specifier until the second "/" separator or the end of string, whatever happens first
    97  		if slash2 := strings.IndexByte(specifier[slash+1:], '/'); slash2 != -1 {
    98  			ident = specifier[:slash+1+slash2]
    99  		} else {
   100  			ident = specifier
   101  		}
   102  	} else {
   103  		// Otherwise,
   104  		// Set ident to the substring of specifier until the first "/" separator or the end of string, whatever happens first
   105  		if slash != -1 {
   106  			ident = specifier[:slash]
   107  		} else {
   108  			ident = specifier
   109  		}
   110  	}
   111  
   112  	// Set modulePath to the substring of specifier starting from ident.length
   113  	modulePath = specifier[len(ident):]
   114  
   115  	// Return {ident, modulePath}
   116  	ok = true
   117  	return
   118  }
   119  
   120  type pnpStatus uint8
   121  
   122  const (
   123  	pnpErrorGeneric pnpStatus = iota
   124  	pnpErrorDependencyNotFound
   125  	pnpErrorUnfulfilledPeerDependency
   126  	pnpSuccess
   127  	pnpSkipped
   128  )
   129  
   130  func (status pnpStatus) isError() bool {
   131  	return status < pnpSuccess
   132  }
   133  
   134  type pnpResult struct {
   135  	status     pnpStatus
   136  	pkgDirPath string
   137  	pkgIdent   string
   138  	pkgSubpath string
   139  
   140  	// This is for error messages
   141  	errorIdent string
   142  	errorRange logger.Range
   143  }
   144  
   145  // Note: If this returns successfully then the node module resolution algorithm
   146  // (i.e. NM_RESOLVE in the Yarn PnP specification) is always run afterward
   147  func (r resolverQuery) resolveToUnqualified(specifier string, parentURL string, manifest *pnpData) pnpResult {
   148  	// Let resolved be undefined
   149  
   150  	// Let manifest be FIND_PNP_MANIFEST(parentURL)
   151  	// (this is already done by the time we get here)
   152  	if r.debugLogs != nil {
   153  		r.debugLogs.addNote(fmt.Sprintf("Using Yarn PnP manifest from %q", manifest.absPath))
   154  		r.debugLogs.addNote(fmt.Sprintf("  Resolving %q in %q", specifier, parentURL))
   155  	}
   156  
   157  	// Let ident and modulePath be the result of PARSE_BARE_IDENTIFIER(specifier)
   158  	ident, modulePath, ok := parseBareIdentifier(specifier)
   159  	if !ok {
   160  		if r.debugLogs != nil {
   161  			r.debugLogs.addNote(fmt.Sprintf("  Failed to parse specifier %q into a bare identifier", specifier))
   162  		}
   163  		return pnpResult{status: pnpErrorGeneric}
   164  	}
   165  	if r.debugLogs != nil {
   166  		r.debugLogs.addNote(fmt.Sprintf("  Parsed bare identifier %q and module path %q", ident, modulePath))
   167  	}
   168  
   169  	// Let parentLocator be FIND_LOCATOR(manifest, parentURL)
   170  	parentLocator, ok := r.findLocator(manifest, parentURL)
   171  
   172  	// If parentLocator is null, then
   173  	// Set resolved to NM_RESOLVE(specifier, parentURL) and return it
   174  	if !ok {
   175  		return pnpResult{status: pnpSkipped}
   176  	}
   177  	if r.debugLogs != nil {
   178  		r.debugLogs.addNote(fmt.Sprintf("  Found parent locator: [%s, %s]", quoteOrNullIfEmpty(parentLocator.ident), quoteOrNullIfEmpty(parentLocator.reference)))
   179  	}
   180  
   181  	// Let parentPkg be GET_PACKAGE(manifest, parentLocator)
   182  	parentPkg, ok := r.getPackage(manifest, parentLocator.ident, parentLocator.reference)
   183  	if !ok {
   184  		// We aren't supposed to get here according to the Yarn PnP specification
   185  		return pnpResult{status: pnpErrorGeneric}
   186  	}
   187  	if r.debugLogs != nil {
   188  		r.debugLogs.addNote(fmt.Sprintf("  Found parent package at %q", parentPkg.packageLocation))
   189  	}
   190  
   191  	// Let referenceOrAlias be the entry from parentPkg.packageDependencies referenced by ident
   192  	referenceOrAlias, ok := parentPkg.packageDependencies[ident]
   193  
   194  	// If referenceOrAlias is null or undefined, then
   195  	if !ok || referenceOrAlias.reference == "" {
   196  		if r.debugLogs != nil {
   197  			r.debugLogs.addNote(fmt.Sprintf("  Failed to find %q in \"packageDependencies\" of parent package", ident))
   198  		}
   199  
   200  		// If manifest.enableTopLevelFallback is true, then
   201  		if manifest.enableTopLevelFallback {
   202  			if r.debugLogs != nil {
   203  				r.debugLogs.addNote("  Searching for a fallback because \"enableTopLevelFallback\" is true")
   204  			}
   205  
   206  			// If parentLocator isn't in manifest.fallbackExclusionList, then
   207  			if set := manifest.fallbackExclusionList[parentLocator.ident]; !set[parentLocator.reference] {
   208  				// Let fallback be RESOLVE_VIA_FALLBACK(manifest, ident)
   209  				fallback, _ := r.resolveViaFallback(manifest, ident)
   210  
   211  				// If fallback is neither null nor undefined
   212  				if fallback.reference != "" {
   213  					// Set referenceOrAlias to fallback
   214  					referenceOrAlias = fallback
   215  					ok = true
   216  				}
   217  			} else if r.debugLogs != nil {
   218  				r.debugLogs.addNote(fmt.Sprintf("    Stopping because [%s, %s] is in \"fallbackExclusionList\"",
   219  					quoteOrNullIfEmpty(parentLocator.ident), quoteOrNullIfEmpty(parentLocator.reference)))
   220  			}
   221  		}
   222  	}
   223  
   224  	// If referenceOrAlias is still undefined, then
   225  	if !ok {
   226  		// Throw a resolution error
   227  		return pnpResult{
   228  			status:     pnpErrorDependencyNotFound,
   229  			errorIdent: ident,
   230  			errorRange: parentPkg.packageDependenciesRange,
   231  		}
   232  	}
   233  
   234  	// If referenceOrAlias is still null, then
   235  	if referenceOrAlias.reference == "" {
   236  		// Note: It means that parentPkg has an unfulfilled peer dependency on ident
   237  		// Throw a resolution error
   238  		return pnpResult{
   239  			status:     pnpErrorUnfulfilledPeerDependency,
   240  			errorIdent: ident,
   241  			errorRange: referenceOrAlias.span,
   242  		}
   243  	}
   244  
   245  	if r.debugLogs != nil {
   246  		var referenceOrAliasStr string
   247  		if referenceOrAlias.ident != "" {
   248  			referenceOrAliasStr = fmt.Sprintf("[%q, %q]", referenceOrAlias.ident, referenceOrAlias.reference)
   249  		} else {
   250  			referenceOrAliasStr = quoteOrNullIfEmpty(referenceOrAlias.reference)
   251  		}
   252  		r.debugLogs.addNote(fmt.Sprintf("  Found dependency locator: [%s, %s]", quoteOrNullIfEmpty(ident), referenceOrAliasStr))
   253  	}
   254  
   255  	// Otherwise, if referenceOrAlias is an array, then
   256  	var dependencyPkg pnpPackage
   257  	if referenceOrAlias.ident != "" {
   258  		// Let alias be referenceOrAlias
   259  		alias := referenceOrAlias
   260  
   261  		// Let dependencyPkg be GET_PACKAGE(manifest, alias)
   262  		dependencyPkg, ok = r.getPackage(manifest, alias.ident, alias.reference)
   263  		if !ok {
   264  			// We aren't supposed to get here according to the Yarn PnP specification
   265  			return pnpResult{status: pnpErrorGeneric}
   266  		}
   267  	} else {
   268  		// Otherwise,
   269  		// Let dependencyPkg be GET_PACKAGE(manifest, {ident, reference})
   270  		dependencyPkg, ok = r.getPackage(manifest, ident, referenceOrAlias.reference)
   271  		if !ok {
   272  			// We aren't supposed to get here according to the Yarn PnP specification
   273  			return pnpResult{status: pnpErrorGeneric}
   274  		}
   275  	}
   276  	if r.debugLogs != nil {
   277  		r.debugLogs.addNote(fmt.Sprintf("  Found package %q at %q", ident, dependencyPkg.packageLocation))
   278  	}
   279  
   280  	// Return path.resolve(manifest.dirPath, dependencyPkg.packageLocation, modulePath)
   281  	pkgDirPath := r.fs.Join(manifest.absDirPath, dependencyPkg.packageLocation)
   282  	if r.debugLogs != nil {
   283  		r.debugLogs.addNote(fmt.Sprintf("  Resolved %q via Yarn PnP to %q with subpath %q", specifier, pkgDirPath, modulePath))
   284  	}
   285  	return pnpResult{
   286  		status:     pnpSuccess,
   287  		pkgDirPath: pkgDirPath,
   288  		pkgIdent:   ident,
   289  		pkgSubpath: modulePath,
   290  	}
   291  }
   292  
   293  func (r resolverQuery) findLocator(manifest *pnpData, moduleUrl string) (pnpIdentAndReference, bool) {
   294  	// Let relativeUrl be the relative path between manifest and moduleUrl
   295  	relativeUrl, ok := r.fs.Rel(manifest.absDirPath, moduleUrl)
   296  	if !ok {
   297  		return pnpIdentAndReference{}, false
   298  	} else {
   299  		// Relative URLs on Windows will use \ instead of /, which will break
   300  		// everything we do below. Use normal slashes to keep things working.
   301  		relativeUrl = strings.ReplaceAll(relativeUrl, "\\", "/")
   302  	}
   303  
   304  	// The relative path must not start with ./; trim it if needed
   305  	relativeUrl = strings.TrimPrefix(relativeUrl, "./")
   306  
   307  	// If relativeUrl matches manifest.ignorePatternData, then
   308  	if manifest.ignorePatternData != nil && manifest.ignorePatternData.MatchString(relativeUrl) {
   309  		if r.debugLogs != nil {
   310  			r.debugLogs.addNote(fmt.Sprintf("  Ignoring %q because it matches \"ignorePatternData\"", relativeUrl))
   311  		}
   312  
   313  		// Return null
   314  		return pnpIdentAndReference{}, false
   315  	}
   316  
   317  	// Note: Make sure relativeUrl always starts with a ./ or ../
   318  	if !strings.HasSuffix(relativeUrl, "/") {
   319  		relativeUrl += "/"
   320  	}
   321  	if !strings.HasPrefix(relativeUrl, "./") && !strings.HasPrefix(relativeUrl, "../") {
   322  		relativeUrl = "./" + relativeUrl
   323  	}
   324  
   325  	// This is the inner loop from Yarn's PnP resolver implementation. This is
   326  	// different from the specification, which contains a hypothetical slow
   327  	// algorithm instead. The algorithm from the specification can sometimes
   328  	// produce different results from the one used by the implementation, so
   329  	// we follow the implementation.
   330  	for {
   331  		entry, ok := manifest.packageLocatorsByLocations[relativeUrl]
   332  		if !ok || entry.discardFromLookup {
   333  			// Remove the last path component and try again
   334  			relativeUrl = relativeUrl[:strings.LastIndexByte(relativeUrl[:len(relativeUrl)-1], '/')+1]
   335  			if relativeUrl == "" {
   336  				break
   337  			}
   338  			continue
   339  		}
   340  		return entry.locator, true
   341  	}
   342  
   343  	return pnpIdentAndReference{}, false
   344  }
   345  
   346  func (r resolverQuery) resolveViaFallback(manifest *pnpData, ident string) (pnpIdentAndReference, bool) {
   347  	// Let topLevelPkg be GET_PACKAGE(manifest, {null, null})
   348  	topLevelPkg, ok := r.getPackage(manifest, "", "")
   349  	if !ok {
   350  		// We aren't supposed to get here according to the Yarn PnP specification
   351  		return pnpIdentAndReference{}, false
   352  	}
   353  
   354  	// Let referenceOrAlias be the entry from topLevelPkg.packageDependencies referenced by ident
   355  	referenceOrAlias, ok := topLevelPkg.packageDependencies[ident]
   356  
   357  	// If referenceOrAlias is defined, then
   358  	if ok {
   359  		// Return it immediately
   360  		if r.debugLogs != nil {
   361  			r.debugLogs.addNote(fmt.Sprintf("    Found fallback for %q in \"packageDependencies\" of top-level package: [%s, %s]", ident,
   362  				quoteOrNullIfEmpty(referenceOrAlias.ident), quoteOrNullIfEmpty(referenceOrAlias.reference)))
   363  		}
   364  		return referenceOrAlias, true
   365  	}
   366  
   367  	// Otherwise,
   368  	// Let referenceOrAlias be the entry from manifest.fallbackPool referenced by ident
   369  	referenceOrAlias, ok = manifest.fallbackPool[ident]
   370  
   371  	// Return it immediately, whether it's defined or not
   372  	if r.debugLogs != nil {
   373  		if ok {
   374  			r.debugLogs.addNote(fmt.Sprintf("    Found fallback for %q in \"fallbackPool\": [%s, %s]", ident,
   375  				quoteOrNullIfEmpty(referenceOrAlias.ident), quoteOrNullIfEmpty(referenceOrAlias.reference)))
   376  		} else {
   377  			r.debugLogs.addNote(fmt.Sprintf("    Failed to find fallback for %q in \"fallbackPool\"", ident))
   378  		}
   379  	}
   380  	return referenceOrAlias, ok
   381  }
   382  
   383  func (r resolverQuery) getPackage(manifest *pnpData, ident string, reference string) (pnpPackage, bool) {
   384  	if inner, ok := manifest.packageRegistryData[ident]; ok {
   385  		if pkg, ok := inner[reference]; ok {
   386  			return pkg, true
   387  		}
   388  	}
   389  
   390  	if r.debugLogs != nil {
   391  		// We aren't supposed to get here according to the Yarn PnP specification:
   392  		// "Note: pkg cannot be undefined here; all packages referenced in any of the
   393  		// Plug'n'Play data tables MUST have a corresponding entry inside packageRegistryData."
   394  		r.debugLogs.addNote(fmt.Sprintf("  Yarn PnP invariant violation: GET_PACKAGE failed to find a package: [%s, %s]",
   395  			quoteOrNullIfEmpty(ident), quoteOrNullIfEmpty(reference)))
   396  	}
   397  	return pnpPackage{}, false
   398  }
   399  
   400  func quoteOrNullIfEmpty(str string) string {
   401  	if str != "" {
   402  		return fmt.Sprintf("%q", str)
   403  	}
   404  	return "null"
   405  }
   406  
   407  func compileYarnPnPData(absPath string, absDirPath string, json js_ast.Expr, source logger.Source) *pnpData {
   408  	data := pnpData{
   409  		absPath:    absPath,
   410  		absDirPath: absDirPath,
   411  		tracker:    logger.MakeLineColumnTracker(&source),
   412  	}
   413  
   414  	if value, _, ok := getProperty(json, "enableTopLevelFallback"); ok {
   415  		if enableTopLevelFallback, ok := getBool(value); ok {
   416  			data.enableTopLevelFallback = enableTopLevelFallback
   417  		}
   418  	}
   419  
   420  	if value, _, ok := getProperty(json, "fallbackExclusionList"); ok {
   421  		if array, ok := value.Data.(*js_ast.EArray); ok {
   422  			data.fallbackExclusionList = make(map[string]map[string]bool, len(array.Items))
   423  
   424  			for _, item := range array.Items {
   425  				if tuple, ok := item.Data.(*js_ast.EArray); ok && len(tuple.Items) == 2 {
   426  					if ident, ok := getStringOrNull(tuple.Items[0]); ok {
   427  						if array2, ok := tuple.Items[1].Data.(*js_ast.EArray); ok {
   428  							references := make(map[string]bool, len(array2.Items))
   429  
   430  							for _, item2 := range array2.Items {
   431  								if reference, ok := getString(item2); ok {
   432  									references[reference] = true
   433  								}
   434  							}
   435  
   436  							data.fallbackExclusionList[ident] = references
   437  						}
   438  					}
   439  				}
   440  			}
   441  		}
   442  	}
   443  
   444  	if value, _, ok := getProperty(json, "fallbackPool"); ok {
   445  		if array, ok := value.Data.(*js_ast.EArray); ok {
   446  			data.fallbackPool = make(map[string]pnpIdentAndReference, len(array.Items))
   447  
   448  			for _, item := range array.Items {
   449  				if array2, ok := item.Data.(*js_ast.EArray); ok && len(array2.Items) == 2 {
   450  					if ident, ok := getString(array2.Items[0]); ok {
   451  						if dependencyTarget, ok := getDependencyTarget(array2.Items[1]); ok {
   452  							data.fallbackPool[ident] = dependencyTarget
   453  						}
   454  					}
   455  				}
   456  			}
   457  		}
   458  	}
   459  
   460  	if value, _, ok := getProperty(json, "ignorePatternData"); ok {
   461  		if ignorePatternData, ok := getString(value); ok {
   462  			// The Go regular expression engine doesn't support some of the features
   463  			// that JavaScript regular expressions support, including "(?!" negative
   464  			// lookaheads which Yarn uses. This is deliberate on Go's part. See this:
   465  			// https://github.com/golang/go/issues/18868.
   466  			//
   467  			// Yarn uses this feature to exclude the "." and ".." path segments in
   468  			// the middle of a relative path. However, we shouldn't ever generate
   469  			// such path segments in the first place. So as a hack, we just remove
   470  			// the specific character sequences used by Yarn for this so that the
   471  			// regular expression is more likely to be able to be compiled.
   472  			ignorePatternData = strings.ReplaceAll(ignorePatternData, `(?!\.)`, "")
   473  			ignorePatternData = strings.ReplaceAll(ignorePatternData, `(?!(?:^|\/)\.)`, "")
   474  			ignorePatternData = strings.ReplaceAll(ignorePatternData, `(?!\.{1,2}(?:\/|$))`, "")
   475  			ignorePatternData = strings.ReplaceAll(ignorePatternData, `(?!(?:^|\/)\.{1,2}(?:\/|$))`, "")
   476  
   477  			if reg, err := regexp.Compile(ignorePatternData); err == nil {
   478  				data.ignorePatternData = reg
   479  			} else {
   480  				data.invalidIgnorePatternData = ignorePatternData
   481  			}
   482  		}
   483  	}
   484  
   485  	if value, _, ok := getProperty(json, "packageRegistryData"); ok {
   486  		if array, ok := value.Data.(*js_ast.EArray); ok {
   487  			data.packageRegistryData = make(map[string]map[string]pnpPackage, len(array.Items))
   488  			data.packageLocatorsByLocations = make(map[string]pnpPackageLocatorByLocation)
   489  
   490  			for _, item := range array.Items {
   491  				if tuple, ok := item.Data.(*js_ast.EArray); ok && len(tuple.Items) == 2 {
   492  					if packageIdent, ok := getStringOrNull(tuple.Items[0]); ok {
   493  						if array2, ok := tuple.Items[1].Data.(*js_ast.EArray); ok {
   494  							references := make(map[string]pnpPackage, len(array2.Items))
   495  							data.packageRegistryData[packageIdent] = references
   496  
   497  							for _, item2 := range array2.Items {
   498  								if tuple2, ok := item2.Data.(*js_ast.EArray); ok && len(tuple2.Items) == 2 {
   499  									if packageReference, ok := getStringOrNull(tuple2.Items[0]); ok {
   500  										pkg := tuple2.Items[1]
   501  
   502  										if packageLocation, _, ok := getProperty(pkg, "packageLocation"); ok {
   503  											if packageDependencies, _, ok := getProperty(pkg, "packageDependencies"); ok {
   504  												if packageLocation, ok := getString(packageLocation); ok {
   505  													if array3, ok := packageDependencies.Data.(*js_ast.EArray); ok {
   506  														deps := make(map[string]pnpIdentAndReference, len(array3.Items))
   507  														discardFromLookup := false
   508  
   509  														for _, dep := range array3.Items {
   510  															if array4, ok := dep.Data.(*js_ast.EArray); ok && len(array4.Items) == 2 {
   511  																if ident, ok := getString(array4.Items[0]); ok {
   512  																	if dependencyTarget, ok := getDependencyTarget(array4.Items[1]); ok {
   513  																		deps[ident] = dependencyTarget
   514  																	}
   515  																}
   516  															}
   517  														}
   518  
   519  														if value, _, ok := getProperty(pkg, "discardFromLookup"); ok {
   520  															if value, ok := getBool(value); ok {
   521  																discardFromLookup = value
   522  															}
   523  														}
   524  
   525  														references[packageReference] = pnpPackage{
   526  															packageLocation:     packageLocation,
   527  															packageDependencies: deps,
   528  															packageDependenciesRange: logger.Range{
   529  																Loc: packageDependencies.Loc,
   530  																Len: array3.CloseBracketLoc.Start + 1 - packageDependencies.Loc.Start,
   531  															},
   532  															discardFromLookup: discardFromLookup,
   533  														}
   534  
   535  														// This is what Yarn's PnP implementation does (specifically in
   536  														// "hydrateRuntimeState"), so we replicate that behavior here:
   537  														if entry, ok := data.packageLocatorsByLocations[packageLocation]; !ok {
   538  															data.packageLocatorsByLocations[packageLocation] = pnpPackageLocatorByLocation{
   539  																locator:           pnpIdentAndReference{ident: packageIdent, reference: packageReference},
   540  																discardFromLookup: discardFromLookup,
   541  															}
   542  														} else {
   543  															entry.discardFromLookup = entry.discardFromLookup && discardFromLookup
   544  															if !discardFromLookup {
   545  																entry.locator = pnpIdentAndReference{ident: packageIdent, reference: packageReference}
   546  															}
   547  															data.packageLocatorsByLocations[packageLocation] = entry
   548  														}
   549  													}
   550  												}
   551  											}
   552  										}
   553  									}
   554  								}
   555  							}
   556  						}
   557  					}
   558  				}
   559  			}
   560  		}
   561  	}
   562  
   563  	return &data
   564  }
   565  
   566  func getStringOrNull(json js_ast.Expr) (string, bool) {
   567  	switch value := json.Data.(type) {
   568  	case *js_ast.EString:
   569  		return helpers.UTF16ToString(value.Value), true
   570  
   571  	case *js_ast.ENull:
   572  		return "", true
   573  	}
   574  
   575  	return "", false
   576  }
   577  
   578  func getDependencyTarget(json js_ast.Expr) (pnpIdentAndReference, bool) {
   579  	switch d := json.Data.(type) {
   580  	case *js_ast.ENull:
   581  		return pnpIdentAndReference{span: logger.Range{Loc: json.Loc, Len: 4}}, true
   582  
   583  	case *js_ast.EString:
   584  		return pnpIdentAndReference{reference: helpers.UTF16ToString(d.Value), span: logger.Range{Loc: json.Loc}}, true
   585  
   586  	case *js_ast.EArray:
   587  		if len(d.Items) == 2 {
   588  			if name, ok := getString(d.Items[0]); ok {
   589  				if reference, ok := getString(d.Items[1]); ok {
   590  					return pnpIdentAndReference{
   591  						ident:     name,
   592  						reference: reference,
   593  						span:      logger.Range{Loc: json.Loc, Len: d.CloseBracketLoc.Start + 1 - json.Loc.Start},
   594  					}, true
   595  				}
   596  			}
   597  		}
   598  	}
   599  
   600  	return pnpIdentAndReference{}, false
   601  }
   602  
   603  type pnpDataMode uint8
   604  
   605  const (
   606  	pnpIgnoreErrorsAboutMissingFiles pnpDataMode = iota
   607  	pnpReportErrorsAboutMissingFiles
   608  )
   609  
   610  func (r resolverQuery) extractYarnPnPDataFromJSON(pnpDataPath string, mode pnpDataMode) (result js_ast.Expr, source logger.Source) {
   611  	contents, err, originalError := r.caches.FSCache.ReadFile(r.fs, pnpDataPath)
   612  	if r.debugLogs != nil && originalError != nil {
   613  		r.debugLogs.addNote(fmt.Sprintf("Failed to read file %q: %s", pnpDataPath, originalError.Error()))
   614  	}
   615  	if err != nil {
   616  		if mode == pnpReportErrorsAboutMissingFiles || err != syscall.ENOENT {
   617  			r.log.AddError(nil, logger.Range{},
   618  				fmt.Sprintf("Cannot read file %q: %s",
   619  					PrettyPath(r.fs, logger.Path{Text: pnpDataPath, Namespace: "file"}), err.Error()))
   620  		}
   621  		return
   622  	}
   623  	if r.debugLogs != nil {
   624  		r.debugLogs.addNote(fmt.Sprintf("The file %q exists", pnpDataPath))
   625  	}
   626  	keyPath := logger.Path{Text: pnpDataPath, Namespace: "file"}
   627  	source = logger.Source{
   628  		KeyPath:    keyPath,
   629  		PrettyPath: PrettyPath(r.fs, keyPath),
   630  		Contents:   contents,
   631  	}
   632  	result, _ = r.caches.JSONCache.Parse(r.log, source, js_parser.JSONOptions{})
   633  	return
   634  }
   635  
   636  func (r resolverQuery) tryToExtractYarnPnPDataFromJS(pnpDataPath string, mode pnpDataMode) (result js_ast.Expr, source logger.Source) {
   637  	contents, err, originalError := r.caches.FSCache.ReadFile(r.fs, pnpDataPath)
   638  	if r.debugLogs != nil && originalError != nil {
   639  		r.debugLogs.addNote(fmt.Sprintf("Failed to read file %q: %s", pnpDataPath, originalError.Error()))
   640  	}
   641  	if err != nil {
   642  		if mode == pnpReportErrorsAboutMissingFiles || err != syscall.ENOENT {
   643  			r.log.AddError(nil, logger.Range{},
   644  				fmt.Sprintf("Cannot read file %q: %s",
   645  					PrettyPath(r.fs, logger.Path{Text: pnpDataPath, Namespace: "file"}), err.Error()))
   646  		}
   647  		return
   648  	}
   649  	if r.debugLogs != nil {
   650  		r.debugLogs.addNote(fmt.Sprintf("The file %q exists", pnpDataPath))
   651  	}
   652  
   653  	keyPath := logger.Path{Text: pnpDataPath, Namespace: "file"}
   654  	source = logger.Source{
   655  		KeyPath:    keyPath,
   656  		PrettyPath: PrettyPath(r.fs, keyPath),
   657  		Contents:   contents,
   658  	}
   659  	ast, _ := r.caches.JSCache.Parse(r.log, source, js_parser.OptionsForYarnPnP())
   660  
   661  	if r.debugLogs != nil && ast.ManifestForYarnPnP.Data != nil {
   662  		r.debugLogs.addNote(fmt.Sprintf("  Extracted JSON data from %q", pnpDataPath))
   663  	}
   664  	return ast.ManifestForYarnPnP, source
   665  }