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 }