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

     1  package resolver
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"path"
     7  	"regexp"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/evanw/esbuild/internal/config"
    12  	"github.com/evanw/esbuild/internal/helpers"
    13  	"github.com/evanw/esbuild/internal/js_ast"
    14  	"github.com/evanw/esbuild/internal/js_lexer"
    15  	"github.com/evanw/esbuild/internal/js_parser"
    16  	"github.com/evanw/esbuild/internal/logger"
    17  )
    18  
    19  type packageJSON struct {
    20  	name           string
    21  	mainFields     map[string]mainField
    22  	moduleTypeData js_ast.ModuleTypeData
    23  
    24  	// "TypeScript will first check whether package.json contains a "tsconfig"
    25  	// field, and if it does, TypeScript will try to load a configuration file
    26  	// from that field. If neither exists, TypeScript will try to read from a
    27  	// tsconfig.json at the root."
    28  	//
    29  	// See: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#tsconfigjson-inheritance-via-nodejs-packages
    30  	tsconfig string
    31  
    32  	// Present if the "browser" field is present. This field is intended to be
    33  	// used by bundlers and lets you redirect the paths of certain 3rd-party
    34  	// modules that don't work in the browser to other modules that shim that
    35  	// functionality. That way you don't have to rewrite the code for those 3rd-
    36  	// party modules. For example, you might remap the native "util" node module
    37  	// to something like https://www.npmjs.com/package/util so it works in the
    38  	// browser.
    39  	//
    40  	// This field contains the original mapping object in "package.json". Mapping
    41  	// to a nil path indicates that the module is disabled. As far as I can
    42  	// tell, the official spec is an abandoned GitHub repo hosted by a user account:
    43  	// https://github.com/defunctzombie/package-browser-field-spec. The npm docs
    44  	// say almost nothing: https://docs.npmjs.com/files/package.json.
    45  	//
    46  	// Note that the non-package "browser" map has to be checked twice to match
    47  	// Webpack's behavior: once before resolution and once after resolution. It
    48  	// leads to some unintuitive failure cases that we must emulate around missing
    49  	// file extensions:
    50  	//
    51  	// * Given the mapping "./no-ext": "./no-ext-browser.js" the query "./no-ext"
    52  	//   should match but the query "./no-ext.js" should NOT match.
    53  	//
    54  	// * Given the mapping "./ext.js": "./ext-browser.js" the query "./ext.js"
    55  	//   should match and the query "./ext" should ALSO match.
    56  	//
    57  	browserMap map[string]*string
    58  
    59  	// If this is non-nil, each entry in this map is the absolute path of a file
    60  	// with side effects. Any entry not in this map should be considered to have
    61  	// no side effects, which means import statements for these files can be
    62  	// removed if none of the imports are used. This is a convention from Webpack:
    63  	// https://webpack.js.org/guides/tree-shaking/.
    64  	//
    65  	// Note that if a file is included, all statements that can't be proven to be
    66  	// free of side effects must be included. This convention does not say
    67  	// anything about whether any statements within the file have side effects or
    68  	// not.
    69  	sideEffectsMap     map[string]bool
    70  	sideEffectsRegexps []*regexp.Regexp
    71  	sideEffectsData    *SideEffectsData
    72  
    73  	// This represents the "imports" field in this package.json file.
    74  	importsMap *pjMap
    75  
    76  	// This represents the "exports" field in this package.json file.
    77  	exportsMap *pjMap
    78  
    79  	source logger.Source
    80  }
    81  
    82  type mainField struct {
    83  	relPath string
    84  	keyLoc  logger.Loc
    85  }
    86  
    87  type browserPathKind uint8
    88  
    89  const (
    90  	absolutePathKind browserPathKind = iota
    91  	packagePathKind
    92  )
    93  
    94  func (r resolverQuery) checkBrowserMap(resolveDirInfo *dirInfo, inputPath string, kind browserPathKind) (remapped *string, ok bool) {
    95  	// This only applies if the current platform is "browser"
    96  	if r.options.Platform != config.PlatformBrowser {
    97  		return nil, false
    98  	}
    99  
   100  	// There must be an enclosing directory with a "package.json" file with a "browser" map
   101  	if resolveDirInfo.enclosingBrowserScope == nil {
   102  		if r.debugLogs != nil {
   103  			r.debugLogs.addNote(fmt.Sprintf("No \"browser\" map found in directory %q", resolveDirInfo.absPath))
   104  		}
   105  		return nil, false
   106  	}
   107  
   108  	packageJSON := resolveDirInfo.enclosingBrowserScope.packageJSON
   109  	browserMap := packageJSON.browserMap
   110  
   111  	type implicitExtensions uint8
   112  
   113  	const (
   114  		includeImplicitExtensions implicitExtensions = iota
   115  		skipImplicitExtensions
   116  	)
   117  
   118  	checkPath := func(pathToCheck string, implicitExtensions implicitExtensions) bool {
   119  		if r.debugLogs != nil {
   120  			r.debugLogs.addNote(fmt.Sprintf("Checking for %q in the \"browser\" map in %q",
   121  				pathToCheck, packageJSON.source.KeyPath.Text))
   122  		}
   123  
   124  		// Check for equality
   125  		if r.debugLogs != nil {
   126  			r.debugLogs.addNote(fmt.Sprintf("  Checking for %q", pathToCheck))
   127  		}
   128  		remapped, ok = browserMap[pathToCheck]
   129  		if ok {
   130  			inputPath = pathToCheck
   131  			return true
   132  		}
   133  
   134  		// If that failed, try adding implicit extensions
   135  		if implicitExtensions == includeImplicitExtensions {
   136  			for _, ext := range r.options.ExtensionOrder {
   137  				extPath := pathToCheck + ext
   138  				if r.debugLogs != nil {
   139  					r.debugLogs.addNote(fmt.Sprintf("  Checking for %q", extPath))
   140  				}
   141  				remapped, ok = browserMap[extPath]
   142  				if ok {
   143  					inputPath = extPath
   144  					return true
   145  				}
   146  			}
   147  		}
   148  
   149  		// If that failed, try assuming this is a directory and looking for an "index" file
   150  		indexPath := path.Join(pathToCheck, "index")
   151  		if IsPackagePath(indexPath) && !IsPackagePath(pathToCheck) {
   152  			indexPath = "./" + indexPath
   153  		}
   154  
   155  		// Check for equality
   156  		if r.debugLogs != nil {
   157  			r.debugLogs.addNote(fmt.Sprintf("  Checking for %q", indexPath))
   158  		}
   159  		remapped, ok = browserMap[indexPath]
   160  		if ok {
   161  			inputPath = indexPath
   162  			return true
   163  		}
   164  
   165  		// If that failed, try adding implicit extensions
   166  		if implicitExtensions == includeImplicitExtensions {
   167  			for _, ext := range r.options.ExtensionOrder {
   168  				extPath := indexPath + ext
   169  				if r.debugLogs != nil {
   170  					r.debugLogs.addNote(fmt.Sprintf("  Checking for %q", extPath))
   171  				}
   172  				remapped, ok = browserMap[extPath]
   173  				if ok {
   174  					inputPath = extPath
   175  					return true
   176  				}
   177  			}
   178  		}
   179  
   180  		return false
   181  	}
   182  
   183  	// Turn absolute paths into paths relative to the "browser" map location
   184  	if kind == absolutePathKind {
   185  		relPath, ok := r.fs.Rel(resolveDirInfo.enclosingBrowserScope.absPath, inputPath)
   186  		if !ok {
   187  			return nil, false
   188  		}
   189  		inputPath = strings.ReplaceAll(relPath, "\\", "/")
   190  	}
   191  
   192  	if inputPath == "." {
   193  		// No bundler supports remapping ".", so we don't either
   194  		return nil, false
   195  	}
   196  
   197  	// First try the import path as a package path
   198  	if !checkPath(inputPath, includeImplicitExtensions) && IsPackagePath(inputPath) {
   199  		// If a package path didn't work, try the import path as a relative path
   200  		switch kind {
   201  		case absolutePathKind:
   202  			checkPath("./"+inputPath, includeImplicitExtensions)
   203  
   204  		case packagePathKind:
   205  			// Browserify allows a browser map entry of "./pkg" to override a package
   206  			// path of "require('pkg')". This is weird, and arguably a bug. But we
   207  			// replicate this bug for compatibility. However, Browserify only allows
   208  			// this within the same package. It does not allow such an entry in a
   209  			// parent package to override this in a child package. So this behavior
   210  			// is disallowed if there is a "node_modules" folder in between the child
   211  			// package and the parent package.
   212  			isInSamePackage := true
   213  			for info := resolveDirInfo; info != nil && info != resolveDirInfo.enclosingBrowserScope; info = info.parent {
   214  				if info.isNodeModules {
   215  					isInSamePackage = false
   216  					break
   217  				}
   218  			}
   219  			if isInSamePackage {
   220  				relativePathPrefix := "./"
   221  
   222  				// Use the relative path from the file containing the import path to the
   223  				// enclosing package.json file. This includes any subdirectories within the
   224  				// package if there are any.
   225  				if relPath, ok := r.fs.Rel(resolveDirInfo.enclosingBrowserScope.absPath, resolveDirInfo.absPath); ok && relPath != "." {
   226  					relativePathPrefix += strings.ReplaceAll(relPath, "\\", "/") + "/"
   227  				}
   228  
   229  				// Browserify lets "require('pkg')" match "./pkg" but not "./pkg.js".
   230  				// So don't add implicit extensions specifically in this place so we
   231  				// match Browserify's behavior.
   232  				checkPath(relativePathPrefix+inputPath, skipImplicitExtensions)
   233  			}
   234  		}
   235  	}
   236  
   237  	if r.debugLogs != nil {
   238  		if ok {
   239  			if remapped == nil {
   240  				r.debugLogs.addNote(fmt.Sprintf("Found %q marked as disabled", inputPath))
   241  			} else {
   242  				r.debugLogs.addNote(fmt.Sprintf("Found %q mapping to %q", inputPath, *remapped))
   243  			}
   244  		} else {
   245  			r.debugLogs.addNote(fmt.Sprintf("Failed to find %q", inputPath))
   246  		}
   247  	}
   248  	return
   249  }
   250  
   251  func (r resolverQuery) parsePackageJSON(inputPath string) *packageJSON {
   252  	packageJSONPath := r.fs.Join(inputPath, "package.json")
   253  	contents, err, originalError := r.caches.FSCache.ReadFile(r.fs, packageJSONPath)
   254  	if r.debugLogs != nil && originalError != nil {
   255  		r.debugLogs.addNote(fmt.Sprintf("Failed to read file %q: %s", packageJSONPath, originalError.Error()))
   256  	}
   257  	if err != nil {
   258  		r.log.AddError(nil, logger.Range{},
   259  			fmt.Sprintf("Cannot read file %q: %s",
   260  				PrettyPath(r.fs, logger.Path{Text: packageJSONPath, Namespace: "file"}), err.Error()))
   261  		return nil
   262  	}
   263  	if r.debugLogs != nil {
   264  		r.debugLogs.addNote(fmt.Sprintf("The file %q exists", packageJSONPath))
   265  	}
   266  
   267  	keyPath := logger.Path{Text: packageJSONPath, Namespace: "file"}
   268  	jsonSource := logger.Source{
   269  		KeyPath:    keyPath,
   270  		PrettyPath: PrettyPath(r.fs, keyPath),
   271  		Contents:   contents,
   272  	}
   273  	tracker := logger.MakeLineColumnTracker(&jsonSource)
   274  
   275  	json, ok := r.caches.JSONCache.Parse(r.log, jsonSource, js_parser.JSONOptions{})
   276  	if !ok {
   277  		return nil
   278  	}
   279  
   280  	packageJSON := &packageJSON{
   281  		source:     jsonSource,
   282  		mainFields: make(map[string]mainField),
   283  	}
   284  
   285  	// Read the "name" field
   286  	if nameJSON, _, ok := getProperty(json, "name"); ok {
   287  		if nameValue, ok := getString(nameJSON); ok {
   288  			packageJSON.name = nameValue
   289  		}
   290  	}
   291  
   292  	// Read the "type" field
   293  	if typeJSON, typeKeyLoc, ok := getProperty(json, "type"); ok {
   294  		if typeValue, ok := getString(typeJSON); ok {
   295  			switch typeValue {
   296  			case "commonjs":
   297  				packageJSON.moduleTypeData = js_ast.ModuleTypeData{
   298  					Type:   js_ast.ModuleCommonJS_PackageJSON,
   299  					Source: &packageJSON.source,
   300  					Range:  jsonSource.RangeOfString(typeJSON.Loc),
   301  				}
   302  			case "module":
   303  				packageJSON.moduleTypeData = js_ast.ModuleTypeData{
   304  					Type:   js_ast.ModuleESM_PackageJSON,
   305  					Source: &packageJSON.source,
   306  					Range:  jsonSource.RangeOfString(typeJSON.Loc),
   307  				}
   308  			default:
   309  				notes := []logger.MsgData{{Text: "The \"type\" field must be set to either \"commonjs\" or \"module\"."}}
   310  				kind := logger.Warning
   311  
   312  				// If someone does something like "type": "./index.d.ts" then they
   313  				// likely meant "types" instead of "type". Customize the message
   314  				// for this and hide it if it's inside a published npm package.
   315  				if strings.HasSuffix(typeValue, ".d.ts") {
   316  					notes[0] = tracker.MsgData(jsonSource.RangeOfString(typeKeyLoc),
   317  						"TypeScript type declarations use the \"types\" field, not the \"type\" field:")
   318  					notes[0].Location.Suggestion = "\"types\""
   319  					if helpers.IsInsideNodeModules(jsonSource.KeyPath.Text) {
   320  						kind = logger.Debug
   321  					}
   322  				}
   323  
   324  				r.log.AddIDWithNotes(logger.MsgID_PackageJSON_InvalidType, kind, &tracker, jsonSource.RangeOfString(typeJSON.Loc),
   325  					fmt.Sprintf("%q is not a valid value for the \"type\" field", typeValue),
   326  					notes)
   327  			}
   328  		} else {
   329  			r.log.AddID(logger.MsgID_PackageJSON_InvalidType, logger.Warning, &tracker, logger.Range{Loc: typeJSON.Loc},
   330  				"The value for \"type\" must be a string")
   331  		}
   332  	}
   333  
   334  	// Read the "tsconfig" field
   335  	if tsconfigJSON, _, ok := getProperty(json, "tsconfig"); ok {
   336  		if tsconfigValue, ok := getString(tsconfigJSON); ok {
   337  			packageJSON.tsconfig = tsconfigValue
   338  		}
   339  	}
   340  
   341  	// Read the "main" fields
   342  	mainFields := r.options.MainFields
   343  	if mainFields == nil {
   344  		mainFields = defaultMainFields[r.options.Platform]
   345  	}
   346  	for _, field := range mainFields {
   347  		if mainJSON, mainLoc, ok := getProperty(json, field); ok {
   348  			if main, ok := getString(mainJSON); ok && main != "" {
   349  				packageJSON.mainFields[field] = mainField{keyLoc: mainLoc, relPath: main}
   350  			}
   351  		}
   352  	}
   353  	for _, field := range mainFieldsForFailure {
   354  		if _, ok := packageJSON.mainFields[field]; !ok {
   355  			if mainJSON, mainLoc, ok := getProperty(json, field); ok {
   356  				if main, ok := getString(mainJSON); ok && main != "" {
   357  					packageJSON.mainFields[field] = mainField{keyLoc: mainLoc, relPath: main}
   358  				}
   359  			}
   360  		}
   361  	}
   362  
   363  	// Read the "browser" property, but only when targeting the browser
   364  	if browserJSON, _, ok := getProperty(json, "browser"); ok && r.options.Platform == config.PlatformBrowser {
   365  		// We both want the ability to have the option of CJS vs. ESM and the
   366  		// option of having node vs. browser. The way to do this is to use the
   367  		// object literal form of the "browser" field like this:
   368  		//
   369  		//   "main": "dist/index.node.cjs.js",
   370  		//   "module": "dist/index.node.esm.js",
   371  		//   "browser": {
   372  		//     "./dist/index.node.cjs.js": "./dist/index.browser.cjs.js",
   373  		//     "./dist/index.node.esm.js": "./dist/index.browser.esm.js"
   374  		//   },
   375  		//
   376  		if browser, ok := browserJSON.Data.(*js_ast.EObject); ok {
   377  			// The value is an object
   378  			browserMap := make(map[string]*string)
   379  
   380  			// Remap all files in the browser field
   381  			for _, prop := range browser.Properties {
   382  				if key, ok := getString(prop.Key); ok && prop.ValueOrNil.Data != nil {
   383  					if value, ok := getString(prop.ValueOrNil); ok {
   384  						// If this is a string, it's a replacement package
   385  						browserMap[key] = &value
   386  					} else if value, ok := getBool(prop.ValueOrNil); ok {
   387  						// If this is false, it means the package is disabled
   388  						if !value {
   389  							browserMap[key] = nil
   390  						}
   391  					} else {
   392  						r.log.AddID(logger.MsgID_PackageJSON_InvalidBrowser, logger.Warning, &tracker, logger.Range{Loc: prop.ValueOrNil.Loc},
   393  							"Each \"browser\" mapping must be a string or a boolean")
   394  					}
   395  				}
   396  			}
   397  
   398  			packageJSON.browserMap = browserMap
   399  		}
   400  	}
   401  
   402  	// Read the "sideEffects" property
   403  	if sideEffectsJSON, sideEffectsLoc, ok := getProperty(json, "sideEffects"); ok {
   404  		switch data := sideEffectsJSON.Data.(type) {
   405  		case *js_ast.EBoolean:
   406  			if !data.Value {
   407  				// Make an empty map for "sideEffects: false", which indicates all
   408  				// files in this module can be considered to not have side effects.
   409  				packageJSON.sideEffectsMap = make(map[string]bool)
   410  				packageJSON.sideEffectsData = &SideEffectsData{
   411  					IsSideEffectsArrayInJSON: false,
   412  					Source:                   &jsonSource,
   413  					Range:                    jsonSource.RangeOfString(sideEffectsLoc),
   414  				}
   415  			}
   416  
   417  		case *js_ast.EArray:
   418  			// The "sideEffects: []" format means all files in this module but not in
   419  			// the array can be considered to not have side effects.
   420  			packageJSON.sideEffectsMap = make(map[string]bool)
   421  			packageJSON.sideEffectsData = &SideEffectsData{
   422  				IsSideEffectsArrayInJSON: true,
   423  				Source:                   &jsonSource,
   424  				Range:                    jsonSource.RangeOfString(sideEffectsLoc),
   425  			}
   426  			for _, itemJSON := range data.Items {
   427  				item, ok := itemJSON.Data.(*js_ast.EString)
   428  				if !ok || item.Value == nil {
   429  					r.log.AddID(logger.MsgID_PackageJSON_InvalidSideEffects, logger.Warning, &tracker, logger.Range{Loc: itemJSON.Loc},
   430  						"Expected string in array for \"sideEffects\"")
   431  					continue
   432  				}
   433  
   434  				// Reference: https://github.com/webpack/webpack/blob/ed175cd22f89eb9fecd0a70572a3fd0be028e77c/lib/optimize/SideEffectsFlagPlugin.js
   435  				pattern := helpers.UTF16ToString(item.Value)
   436  				if !strings.ContainsRune(pattern, '/') {
   437  					pattern = "**/" + pattern
   438  				}
   439  				absPattern := r.fs.Join(inputPath, pattern)
   440  				absPattern = strings.ReplaceAll(absPattern, "\\", "/") // Avoid problems with Windows-style slashes
   441  				re, hadWildcard := globstarToEscapedRegexp(absPattern)
   442  
   443  				// Wildcard patterns require more expensive matching
   444  				if hadWildcard {
   445  					packageJSON.sideEffectsRegexps = append(packageJSON.sideEffectsRegexps, regexp.MustCompile(re))
   446  					continue
   447  				}
   448  
   449  				// Normal strings can be matched with a map lookup
   450  				packageJSON.sideEffectsMap[absPattern] = true
   451  			}
   452  
   453  		default:
   454  			r.log.AddID(logger.MsgID_PackageJSON_InvalidSideEffects, logger.Warning, &tracker, logger.Range{Loc: sideEffectsJSON.Loc},
   455  				"The value for \"sideEffects\" must be a boolean or an array")
   456  		}
   457  	}
   458  
   459  	// Read the "imports" map
   460  	if importsJSON, importsLoc, ok := getProperty(json, "imports"); ok {
   461  		if importsMap := parseImportsExportsMap(jsonSource, r.log, importsJSON, "imports", importsLoc); importsMap != nil {
   462  			if importsMap.root.kind != pjObject {
   463  				r.log.AddID(logger.MsgID_PackageJSON_InvalidImportsOrExports, logger.Warning, &tracker, importsMap.root.firstToken,
   464  					"The value for \"imports\" must be an object")
   465  			}
   466  			packageJSON.importsMap = importsMap
   467  		}
   468  	}
   469  
   470  	// Read the "exports" map
   471  	if exportsJSON, exportsLoc, ok := getProperty(json, "exports"); ok {
   472  		if exportsMap := parseImportsExportsMap(jsonSource, r.log, exportsJSON, "exports", exportsLoc); exportsMap != nil {
   473  			packageJSON.exportsMap = exportsMap
   474  		}
   475  	}
   476  
   477  	return packageJSON
   478  }
   479  
   480  // Reference: https://github.com/fitzgen/glob-to-regexp/blob/2abf65a834259c6504ed3b80e85f893f8cd99127/index.js
   481  func globstarToEscapedRegexp(glob string) (string, bool) {
   482  	sb := strings.Builder{}
   483  	sb.WriteByte('^')
   484  	hadWildcard := false
   485  	n := len(glob)
   486  
   487  	for i := 0; i < n; i++ {
   488  		c := glob[i]
   489  		switch c {
   490  		case '\\', '^', '$', '.', '+', '|', '(', ')', '[', ']', '{', '}':
   491  			sb.WriteByte('\\')
   492  			sb.WriteByte(c)
   493  
   494  		case '?':
   495  			sb.WriteByte('.')
   496  			hadWildcard = true
   497  
   498  		case '*':
   499  			// Move over all consecutive "*"'s.
   500  			// Also store the previous and next characters
   501  			prevChar := -1
   502  			if i > 0 {
   503  				prevChar = int(glob[i-1])
   504  			}
   505  			starCount := 1
   506  			for i+1 < n && glob[i+1] == '*' {
   507  				starCount++
   508  				i++
   509  			}
   510  			nextChar := -1
   511  			if i+1 < n {
   512  				nextChar = int(glob[i+1])
   513  			}
   514  
   515  			// Determine if this is a globstar segment
   516  			isGlobstar := starCount > 1 && // multiple "*"'s
   517  				(prevChar == '/' || prevChar == -1) && // from the start of the segment
   518  				(nextChar == '/' || nextChar == -1) // to the end of the segment
   519  
   520  			if isGlobstar {
   521  				// It's a globstar, so match zero or more path segments
   522  				sb.WriteString("(?:[^/]*(?:/|$))*")
   523  				i++ // Move over the "/"
   524  			} else {
   525  				// It's not a globstar, so only match one path segment
   526  				sb.WriteString("[^/]*")
   527  			}
   528  
   529  			hadWildcard = true
   530  
   531  		default:
   532  			sb.WriteByte(c)
   533  		}
   534  	}
   535  
   536  	sb.WriteByte('$')
   537  	return sb.String(), hadWildcard
   538  }
   539  
   540  // Reference: https://nodejs.org/api/esm.html#esm_resolver_algorithm_specification
   541  type pjMap struct {
   542  	root           pjEntry
   543  	propertyKey    string
   544  	propertyKeyLoc logger.Loc
   545  }
   546  
   547  type pjKind uint8
   548  
   549  const (
   550  	pjNull pjKind = iota
   551  	pjString
   552  	pjArray
   553  	pjObject
   554  	pjInvalid
   555  )
   556  
   557  type pjEntry struct {
   558  	strData       string
   559  	arrData       []pjEntry
   560  	mapData       []pjMapEntry // Can't be a "map" because order matters
   561  	expansionKeys expansionKeysArray
   562  	firstToken    logger.Range
   563  	kind          pjKind
   564  }
   565  
   566  type pjMapEntry struct {
   567  	key      string
   568  	value    pjEntry
   569  	keyRange logger.Range
   570  }
   571  
   572  // This type is just so we can use Go's native sort function
   573  type expansionKeysArray []pjMapEntry
   574  
   575  func (a expansionKeysArray) Len() int          { return len(a) }
   576  func (a expansionKeysArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
   577  
   578  func (a expansionKeysArray) Less(i int, j int) bool {
   579  	// Assert: keyA ends with "/" or contains only a single "*".
   580  	// Assert: keyB ends with "/" or contains only a single "*".
   581  	keyA := a[i].key
   582  	keyB := a[j].key
   583  
   584  	// Let baseLengthA be the index of "*" in keyA plus one, if keyA contains "*", or the length of keyA otherwise.
   585  	// Let baseLengthB be the index of "*" in keyB plus one, if keyB contains "*", or the length of keyB otherwise.
   586  	starA := strings.IndexByte(keyA, '*')
   587  	starB := strings.IndexByte(keyB, '*')
   588  	var baseLengthA int
   589  	var baseLengthB int
   590  	if starA >= 0 {
   591  		baseLengthA = starA
   592  	} else {
   593  		baseLengthA = len(keyA)
   594  	}
   595  	if starB >= 0 {
   596  		baseLengthB = starB
   597  	} else {
   598  		baseLengthB = len(keyB)
   599  	}
   600  
   601  	// If baseLengthA is greater than baseLengthB, return -1.
   602  	// If baseLengthB is greater than baseLengthA, return 1.
   603  	if baseLengthA > baseLengthB {
   604  		return true
   605  	}
   606  	if baseLengthB > baseLengthA {
   607  		return false
   608  	}
   609  
   610  	// If keyA does not contain "*", return 1.
   611  	// If keyB does not contain "*", return -1.
   612  	if starA < 0 {
   613  		return false
   614  	}
   615  	if starB < 0 {
   616  		return true
   617  	}
   618  
   619  	// If the length of keyA is greater than the length of keyB, return -1.
   620  	// If the length of keyB is greater than the length of keyA, return 1.
   621  	if len(keyA) > len(keyB) {
   622  		return true
   623  	}
   624  	if len(keyB) > len(keyA) {
   625  		return false
   626  	}
   627  
   628  	return false
   629  }
   630  
   631  func (entry pjEntry) valueForKey(key string) (pjEntry, bool) {
   632  	for _, item := range entry.mapData {
   633  		if item.key == key {
   634  			return item.value, true
   635  		}
   636  	}
   637  	return pjEntry{}, false
   638  }
   639  
   640  func parseImportsExportsMap(source logger.Source, log logger.Log, json js_ast.Expr, propertyKey string, propertyKeyLoc logger.Loc) *pjMap {
   641  	var visit func(expr js_ast.Expr) pjEntry
   642  	tracker := logger.MakeLineColumnTracker(&source)
   643  
   644  	visit = func(expr js_ast.Expr) pjEntry {
   645  		var firstToken logger.Range
   646  
   647  		switch e := expr.Data.(type) {
   648  		case *js_ast.ENull:
   649  			return pjEntry{
   650  				kind:       pjNull,
   651  				firstToken: js_lexer.RangeOfIdentifier(source, expr.Loc),
   652  			}
   653  
   654  		case *js_ast.EString:
   655  			return pjEntry{
   656  				kind:       pjString,
   657  				firstToken: source.RangeOfString(expr.Loc),
   658  				strData:    helpers.UTF16ToString(e.Value),
   659  			}
   660  
   661  		case *js_ast.EArray:
   662  			arrData := make([]pjEntry, len(e.Items))
   663  			for i, item := range e.Items {
   664  				arrData[i] = visit(item)
   665  			}
   666  			return pjEntry{
   667  				kind:       pjArray,
   668  				firstToken: logger.Range{Loc: expr.Loc, Len: 1},
   669  				arrData:    arrData,
   670  			}
   671  
   672  		case *js_ast.EObject:
   673  			mapData := make([]pjMapEntry, len(e.Properties))
   674  			expansionKeys := make(expansionKeysArray, 0, len(e.Properties))
   675  			firstToken := logger.Range{Loc: expr.Loc, Len: 1}
   676  			isConditionalSugar := false
   677  
   678  			for i, property := range e.Properties {
   679  				keyStr, _ := property.Key.Data.(*js_ast.EString)
   680  				key := helpers.UTF16ToString(keyStr.Value)
   681  				keyRange := source.RangeOfString(property.Key.Loc)
   682  
   683  				// If exports is an Object with both a key starting with "." and a key
   684  				// not starting with ".", throw an Invalid Package Configuration error.
   685  				curIsConditionalSugar := !strings.HasPrefix(key, ".")
   686  				if i == 0 {
   687  					isConditionalSugar = curIsConditionalSugar
   688  				} else if isConditionalSugar != curIsConditionalSugar {
   689  					prevEntry := mapData[i-1]
   690  					log.AddIDWithNotes(logger.MsgID_PackageJSON_InvalidImportsOrExports, logger.Warning, &tracker, keyRange,
   691  						"This object cannot contain keys that both start with \".\" and don't start with \".\"",
   692  						[]logger.MsgData{tracker.MsgData(prevEntry.keyRange,
   693  							fmt.Sprintf("The key %q is incompatible with the previous key %q:", key, prevEntry.key))})
   694  					return pjEntry{
   695  						kind:       pjInvalid,
   696  						firstToken: firstToken,
   697  					}
   698  				}
   699  
   700  				entry := pjMapEntry{
   701  					key:      key,
   702  					keyRange: keyRange,
   703  					value:    visit(property.ValueOrNil),
   704  				}
   705  
   706  				if strings.HasSuffix(key, "/") || strings.IndexByte(key, '*') >= 0 {
   707  					expansionKeys = append(expansionKeys, entry)
   708  				}
   709  
   710  				mapData[i] = entry
   711  			}
   712  
   713  			// Let expansionKeys be the list of keys of matchObj either ending in "/"
   714  			// or containing only a single "*", sorted by the sorting function
   715  			// PATTERN_KEY_COMPARE which orders in descending order of specificity.
   716  			sort.Stable(expansionKeys)
   717  
   718  			return pjEntry{
   719  				kind:          pjObject,
   720  				firstToken:    firstToken,
   721  				mapData:       mapData,
   722  				expansionKeys: expansionKeys,
   723  			}
   724  
   725  		case *js_ast.EBoolean:
   726  			firstToken = js_lexer.RangeOfIdentifier(source, expr.Loc)
   727  
   728  		case *js_ast.ENumber:
   729  			firstToken = source.RangeOfNumber(expr.Loc)
   730  
   731  		default:
   732  			firstToken.Loc = expr.Loc
   733  		}
   734  
   735  		log.AddID(logger.MsgID_PackageJSON_InvalidImportsOrExports, logger.Warning, &tracker, firstToken,
   736  			"This value must be a string, an object, an array, or null")
   737  		return pjEntry{
   738  			kind:       pjInvalid,
   739  			firstToken: firstToken,
   740  		}
   741  	}
   742  
   743  	root := visit(json)
   744  
   745  	if root.kind == pjNull {
   746  		return nil
   747  	}
   748  
   749  	return &pjMap{
   750  		root:           root,
   751  		propertyKey:    propertyKey,
   752  		propertyKeyLoc: propertyKeyLoc,
   753  	}
   754  }
   755  
   756  func (entry pjEntry) keysStartWithDot() bool {
   757  	return len(entry.mapData) > 0 && strings.HasPrefix(entry.mapData[0].key, ".")
   758  }
   759  
   760  type pjStatus uint8
   761  
   762  const (
   763  	pjStatusUndefined                  pjStatus = iota
   764  	pjStatusUndefinedNoConditionsMatch          // A more friendly error message for when no conditions are matched
   765  	pjStatusNull
   766  	pjStatusExact
   767  	pjStatusExactEndsWithStar
   768  	pjStatusInexact        // This means we may need to try CommonJS-style extension suffixes
   769  	pjStatusPackageResolve // Need to re-run package resolution on the result
   770  
   771  	// Module specifier is an invalid URL, package name or package subpath specifier.
   772  	pjStatusInvalidModuleSpecifier
   773  
   774  	// package.json configuration is invalid or contains an invalid configuration.
   775  	pjStatusInvalidPackageConfiguration
   776  
   777  	// Package exports or imports define a target module for the package that is an invalid type or string target.
   778  	pjStatusInvalidPackageTarget
   779  
   780  	// Package exports do not define or permit a target subpath in the package for the given module.
   781  	pjStatusPackagePathNotExported
   782  
   783  	// Package imports do not define the specifiespecifier
   784  	pjStatusPackageImportNotDefined
   785  
   786  	// The package or module requested does not exist.
   787  	pjStatusModuleNotFound
   788  	pjStatusModuleNotFoundMissingExtension // The user just needs to add the missing extension
   789  
   790  	// The resolved path corresponds to a directory, which is not a supported target for module imports.
   791  	pjStatusUnsupportedDirectoryImport
   792  	pjStatusUnsupportedDirectoryImportMissingIndex // The user just needs to add the missing "/index.js" suffix
   793  )
   794  
   795  func (status pjStatus) isUndefined() bool {
   796  	return status == pjStatusUndefined || status == pjStatusUndefinedNoConditionsMatch
   797  }
   798  
   799  type pjDebug struct {
   800  	// If the status is "pjStatusInvalidPackageTarget" or "pjStatusInvalidModuleSpecifier",
   801  	// then this is the reason. It always starts with " because".
   802  	invalidBecause string
   803  
   804  	// If the status is "pjStatusUndefinedNoConditionsMatch", this is the set of
   805  	// conditions that didn't match, in the order that they were found in the file.
   806  	// This information is used for error messages.
   807  	unmatchedConditions []logger.Span
   808  
   809  	// This is the range of the token to use for error messages
   810  	token logger.Range
   811  
   812  	// If true, the token is a "null" literal
   813  	isBecauseOfNullLiteral bool
   814  }
   815  
   816  func (r resolverQuery) esmHandlePostConditions(
   817  	resolved string,
   818  	status pjStatus,
   819  	debug pjDebug,
   820  ) (string, pjStatus, pjDebug) {
   821  	if status != pjStatusExact && status != pjStatusExactEndsWithStar && status != pjStatusInexact {
   822  		return resolved, status, debug
   823  	}
   824  
   825  	// If resolved contains any percent encodings of "/" or "\" ("%2f" and "%5C"
   826  	// respectively), then throw an Invalid Module Specifier error.
   827  	resolvedPath, err := url.PathUnescape(resolved)
   828  	if err != nil {
   829  		if r.debugLogs != nil {
   830  			r.debugLogs.addNote(fmt.Sprintf("The path %q contains invalid URL escapes: %s", resolved, err.Error()))
   831  		}
   832  		return resolved, pjStatusInvalidModuleSpecifier, debug
   833  	}
   834  	var found string
   835  	if strings.Contains(resolved, "%2f") {
   836  		found = "%2f"
   837  	} else if strings.Contains(resolved, "%2F") {
   838  		found = "%2F"
   839  	} else if strings.Contains(resolved, "%5c") {
   840  		found = "%5c"
   841  	} else if strings.Contains(resolved, "%5C") {
   842  		found = "%5C"
   843  	}
   844  	if found != "" {
   845  		if r.debugLogs != nil {
   846  			r.debugLogs.addNote(fmt.Sprintf("The path %q is not allowed to contain %q", resolved, found))
   847  		}
   848  		return resolved, pjStatusInvalidModuleSpecifier, debug
   849  	}
   850  
   851  	// If the file at resolved is a directory, then throw an Unsupported Directory
   852  	// Import error.
   853  	if strings.HasSuffix(resolvedPath, "/") || strings.HasSuffix(resolvedPath, "\\") {
   854  		if r.debugLogs != nil {
   855  			r.debugLogs.addNote(fmt.Sprintf("The path %q is not allowed to end with a slash", resolved))
   856  		}
   857  		return resolved, pjStatusUnsupportedDirectoryImport, debug
   858  	}
   859  
   860  	// Set resolved to the real path of resolved.
   861  	return resolvedPath, status, debug
   862  }
   863  
   864  func (r resolverQuery) esmPackageImportsResolve(
   865  	specifier string,
   866  	imports pjEntry,
   867  	conditions map[string]bool,
   868  ) (string, pjStatus, pjDebug) {
   869  	// ALGORITHM DEVIATION: Provide a friendly error message if "imports" is not an object
   870  	if imports.kind != pjObject {
   871  		return "", pjStatusInvalidPackageConfiguration, pjDebug{token: imports.firstToken}
   872  	}
   873  
   874  	resolved, status, debug := r.esmPackageImportsExportsResolve(specifier, imports, "/", true, conditions)
   875  	if status != pjStatusNull && status != pjStatusUndefined {
   876  		return resolved, status, debug
   877  	}
   878  
   879  	if r.debugLogs != nil {
   880  		r.debugLogs.addNote(fmt.Sprintf("The package import %q is not defined", specifier))
   881  	}
   882  	return specifier, pjStatusPackageImportNotDefined, pjDebug{token: imports.firstToken}
   883  }
   884  
   885  func (r resolverQuery) esmPackageExportsResolve(
   886  	packageURL string,
   887  	subpath string,
   888  	exports pjEntry,
   889  	conditions map[string]bool,
   890  ) (string, pjStatus, pjDebug) {
   891  	if exports.kind == pjInvalid {
   892  		if r.debugLogs != nil {
   893  			r.debugLogs.addNote("Invalid package configuration")
   894  		}
   895  		return "", pjStatusInvalidPackageConfiguration, pjDebug{token: exports.firstToken}
   896  	}
   897  
   898  	debugToReturn := pjDebug{token: exports.firstToken}
   899  	if subpath == "." {
   900  		mainExport := pjEntry{kind: pjNull}
   901  		if exports.kind == pjString || exports.kind == pjArray || (exports.kind == pjObject && !exports.keysStartWithDot()) {
   902  			mainExport = exports
   903  		} else if exports.kind == pjObject {
   904  			if dot, ok := exports.valueForKey("."); ok {
   905  				if r.debugLogs != nil {
   906  					r.debugLogs.addNote("Using the entry for \".\"")
   907  				}
   908  				mainExport = dot
   909  			}
   910  		}
   911  		if mainExport.kind != pjNull {
   912  			resolved, status, debug := r.esmPackageTargetResolve(packageURL, mainExport, "", false, false, conditions)
   913  			if status != pjStatusNull && status != pjStatusUndefined {
   914  				return resolved, status, debug
   915  			} else {
   916  				debugToReturn = debug
   917  			}
   918  		}
   919  	} else if exports.kind == pjObject && exports.keysStartWithDot() {
   920  		resolved, status, debug := r.esmPackageImportsExportsResolve(subpath, exports, packageURL, false, conditions)
   921  		if status != pjStatusNull && status != pjStatusUndefined {
   922  			return resolved, status, debug
   923  		} else {
   924  			debugToReturn = debug
   925  		}
   926  	}
   927  
   928  	if r.debugLogs != nil {
   929  		r.debugLogs.addNote(fmt.Sprintf("The path %q is not exported", subpath))
   930  	}
   931  	return "", pjStatusPackagePathNotExported, debugToReturn
   932  }
   933  
   934  func (r resolverQuery) esmPackageImportsExportsResolve(
   935  	matchKey string,
   936  	matchObj pjEntry,
   937  	packageURL string,
   938  	isImports bool,
   939  	conditions map[string]bool,
   940  ) (string, pjStatus, pjDebug) {
   941  	if r.debugLogs != nil {
   942  		r.debugLogs.addNote(fmt.Sprintf("Checking object path map for %q", matchKey))
   943  	}
   944  
   945  	// If matchKey is a key of matchObj and does not end in "/" or contain "*", then
   946  	if !strings.HasSuffix(matchKey, "/") && strings.IndexByte(matchKey, '*') < 0 {
   947  		if target, ok := matchObj.valueForKey(matchKey); ok {
   948  			if r.debugLogs != nil {
   949  				r.debugLogs.addNote(fmt.Sprintf("Found exact match for %q", matchKey))
   950  			}
   951  			return r.esmPackageTargetResolve(packageURL, target, "", false, isImports, conditions)
   952  		}
   953  	}
   954  
   955  	for _, expansion := range matchObj.expansionKeys {
   956  		// If expansionKey contains "*", set patternBase to the substring of
   957  		// expansionKey up to but excluding the first "*" character
   958  		if star := strings.IndexByte(expansion.key, '*'); star >= 0 {
   959  			patternBase := expansion.key[:star]
   960  
   961  			// If patternBase is not null and matchKey starts with but is not equal
   962  			// to patternBase, then
   963  			if strings.HasPrefix(matchKey, patternBase) {
   964  				// Let patternTrailer be the substring of expansionKey from the index
   965  				// after the first "*" character.
   966  				patternTrailer := expansion.key[star+1:]
   967  
   968  				// If patternTrailer has zero length, or if matchKey ends with
   969  				// patternTrailer and the length of matchKey is greater than or
   970  				// equal to the length of expansionKey, then
   971  				if patternTrailer == "" || (strings.HasSuffix(matchKey, patternTrailer) && len(matchKey) >= len(expansion.key)) {
   972  					target := expansion.value
   973  					subpath := matchKey[len(patternBase) : len(matchKey)-len(patternTrailer)]
   974  					if r.debugLogs != nil {
   975  						r.debugLogs.addNote(fmt.Sprintf("The key %q matched with %q left over", expansion.key, subpath))
   976  					}
   977  					return r.esmPackageTargetResolve(packageURL, target, subpath, true, isImports, conditions)
   978  				}
   979  			}
   980  		} else {
   981  			// Otherwise if patternBase is null and matchKey starts with
   982  			// expansionKey, then
   983  			if strings.HasPrefix(matchKey, expansion.key) {
   984  				target := expansion.value
   985  				subpath := matchKey[len(expansion.key):]
   986  				if r.debugLogs != nil {
   987  					r.debugLogs.addNote(fmt.Sprintf("The key %q matched with %q left over", expansion.key, subpath))
   988  				}
   989  				result, status, debug := r.esmPackageTargetResolve(packageURL, target, subpath, false, isImports, conditions)
   990  				if status == pjStatusExact || status == pjStatusExactEndsWithStar {
   991  					// Return the object { resolved, exact: false }.
   992  					status = pjStatusInexact
   993  				}
   994  				return result, status, debug
   995  			}
   996  		}
   997  
   998  		if r.debugLogs != nil {
   999  			r.debugLogs.addNote(fmt.Sprintf("The key %q did not match", expansion.key))
  1000  		}
  1001  	}
  1002  
  1003  	if r.debugLogs != nil {
  1004  		r.debugLogs.addNote(fmt.Sprintf("No keys matched %q", matchKey))
  1005  	}
  1006  	return "", pjStatusNull, pjDebug{token: matchObj.firstToken}
  1007  }
  1008  
  1009  // If path split on "/" or "\" contains any ".", ".." or "node_modules"
  1010  // segments after the first segment, throw an Invalid Package Target error.
  1011  func findInvalidSegment(path string) string {
  1012  	slash := strings.IndexAny(path, "/\\")
  1013  	if slash == -1 {
  1014  		return ""
  1015  	}
  1016  	path = path[slash+1:]
  1017  	for path != "" {
  1018  		slash := strings.IndexAny(path, "/\\")
  1019  		segment := path
  1020  		if slash != -1 {
  1021  			segment = path[:slash]
  1022  			path = path[slash+1:]
  1023  		} else {
  1024  			path = ""
  1025  		}
  1026  		if segment == "." || segment == ".." || segment == "node_modules" {
  1027  			return segment
  1028  		}
  1029  	}
  1030  	return ""
  1031  }
  1032  
  1033  func (r resolverQuery) esmPackageTargetResolve(
  1034  	packageURL string,
  1035  	target pjEntry,
  1036  	subpath string,
  1037  	pattern bool,
  1038  	internal bool,
  1039  	conditions map[string]bool,
  1040  ) (string, pjStatus, pjDebug) {
  1041  	switch target.kind {
  1042  	case pjString:
  1043  		if r.debugLogs != nil {
  1044  			r.debugLogs.addNote(fmt.Sprintf("Checking path %q against target %q", subpath, target.strData))
  1045  			r.debugLogs.increaseIndent()
  1046  			defer r.debugLogs.decreaseIndent()
  1047  		}
  1048  
  1049  		// If pattern is false, subpath has non-zero length and target
  1050  		// does not end with "/", throw an Invalid Module Specifier error.
  1051  		if !pattern && subpath != "" && !strings.HasSuffix(target.strData, "/") {
  1052  			if r.debugLogs != nil {
  1053  				r.debugLogs.addNote(fmt.Sprintf("The target %q is invalid because it doesn't end in \"/\"", target.strData))
  1054  			}
  1055  			return target.strData, pjStatusInvalidModuleSpecifier, pjDebug{
  1056  				token:          target.firstToken,
  1057  				invalidBecause: " because it doesn't end in \"/\"",
  1058  			}
  1059  		}
  1060  
  1061  		// If target does not start with "./", then...
  1062  		if !strings.HasPrefix(target.strData, "./") {
  1063  			if internal && !strings.HasPrefix(target.strData, "../") && !strings.HasPrefix(target.strData, "/") {
  1064  				if pattern {
  1065  					result := strings.ReplaceAll(target.strData, "*", subpath)
  1066  					if r.debugLogs != nil {
  1067  						r.debugLogs.addNote(fmt.Sprintf("Substituted %q for \"*\" in %q to get %q", subpath, target.strData, result))
  1068  					}
  1069  					return result, pjStatusPackageResolve, pjDebug{token: target.firstToken}
  1070  				}
  1071  				result := target.strData + subpath
  1072  				if r.debugLogs != nil {
  1073  					r.debugLogs.addNote(fmt.Sprintf("Joined %q to %q to get %q", target.strData, subpath, result))
  1074  				}
  1075  				return result, pjStatusPackageResolve, pjDebug{token: target.firstToken}
  1076  			}
  1077  			if r.debugLogs != nil {
  1078  				r.debugLogs.addNote(fmt.Sprintf("The target %q is invalid because it doesn't start with \"./\"", target.strData))
  1079  			}
  1080  			return target.strData, pjStatusInvalidPackageTarget, pjDebug{
  1081  				token:          target.firstToken,
  1082  				invalidBecause: " because it doesn't start with \"./\"",
  1083  			}
  1084  		}
  1085  
  1086  		// If target split on "/" or "\" contains any ".", ".." or "node_modules"
  1087  		// segments after the first segment, throw an Invalid Package Target error.
  1088  		if invalidSegment := findInvalidSegment(target.strData); invalidSegment != "" {
  1089  			if r.debugLogs != nil {
  1090  				r.debugLogs.addNote(fmt.Sprintf("The target %q is invalid because it contains invalid segment %q", target.strData, invalidSegment))
  1091  			}
  1092  			return target.strData, pjStatusInvalidPackageTarget, pjDebug{
  1093  				token:          target.firstToken,
  1094  				invalidBecause: fmt.Sprintf(" because it contains invalid segment %q", invalidSegment),
  1095  			}
  1096  		}
  1097  
  1098  		// Let resolvedTarget be the URL resolution of the concatenation of packageURL and target.
  1099  		resolvedTarget := path.Join(packageURL, target.strData)
  1100  
  1101  		// If subpath split on "/" or "\" contains any ".", ".." or "node_modules"
  1102  		// segments, throw an Invalid Module Specifier error.
  1103  		if invalidSegment := findInvalidSegment(subpath); invalidSegment != "" {
  1104  			if r.debugLogs != nil {
  1105  				r.debugLogs.addNote(fmt.Sprintf("The path %q is invalid because it contains invalid segment %q", subpath, invalidSegment))
  1106  			}
  1107  			return subpath, pjStatusInvalidModuleSpecifier, pjDebug{
  1108  				token:          target.firstToken,
  1109  				invalidBecause: fmt.Sprintf(" because it contains invalid segment %q", invalidSegment),
  1110  			}
  1111  		}
  1112  
  1113  		if pattern {
  1114  			// Return the URL resolution of resolvedTarget with every instance of "*" replaced with subpath.
  1115  			result := strings.ReplaceAll(resolvedTarget, "*", subpath)
  1116  			if r.debugLogs != nil {
  1117  				r.debugLogs.addNote(fmt.Sprintf("Substituted %q for \"*\" in %q to get %q", subpath, "."+resolvedTarget, "."+result))
  1118  			}
  1119  			status := pjStatusExact
  1120  			if strings.HasSuffix(resolvedTarget, "*") && strings.IndexByte(resolvedTarget, '*') == len(resolvedTarget)-1 {
  1121  				status = pjStatusExactEndsWithStar
  1122  			}
  1123  			return result, status, pjDebug{token: target.firstToken}
  1124  		} else {
  1125  			// Return the URL resolution of the concatenation of subpath and resolvedTarget.
  1126  			result := path.Join(resolvedTarget, subpath)
  1127  			if r.debugLogs != nil {
  1128  				r.debugLogs.addNote(fmt.Sprintf("Joined %q to %q to get %q", subpath, "."+resolvedTarget, "."+result))
  1129  			}
  1130  			return result, pjStatusExact, pjDebug{token: target.firstToken}
  1131  		}
  1132  
  1133  	case pjObject:
  1134  		if r.debugLogs != nil {
  1135  			keys := make([]string, 0, len(conditions))
  1136  			for key := range conditions {
  1137  				keys = append(keys, fmt.Sprintf("%q", key))
  1138  			}
  1139  			sort.Strings(keys)
  1140  			r.debugLogs.addNote(fmt.Sprintf("Checking condition map for one of [%s]", strings.Join(keys, ", ")))
  1141  			r.debugLogs.increaseIndent()
  1142  			defer r.debugLogs.decreaseIndent()
  1143  		}
  1144  
  1145  		var didFindMapEntry bool
  1146  		var lastMapEntry pjMapEntry
  1147  
  1148  		for _, p := range target.mapData {
  1149  			if p.key == "default" || conditions[p.key] {
  1150  				if r.debugLogs != nil {
  1151  					r.debugLogs.addNote(fmt.Sprintf("The key %q applies", p.key))
  1152  				}
  1153  				resolved, status, debug := r.esmPackageTargetResolve(packageURL, p.value, subpath, pattern, internal, conditions)
  1154  				if status.isUndefined() {
  1155  					didFindMapEntry = true
  1156  					lastMapEntry = p
  1157  					continue
  1158  				}
  1159  				return resolved, status, debug
  1160  			}
  1161  			if r.debugLogs != nil {
  1162  				r.debugLogs.addNote(fmt.Sprintf("The key %q does not apply", p.key))
  1163  			}
  1164  		}
  1165  
  1166  		if r.debugLogs != nil {
  1167  			r.debugLogs.addNote("No keys in the map were applicable")
  1168  		}
  1169  
  1170  		// ALGORITHM DEVIATION: Provide a friendly error message if no conditions matched
  1171  		if len(target.mapData) > 0 && !target.keysStartWithDot() {
  1172  			if didFindMapEntry && lastMapEntry.value.kind == pjObject &&
  1173  				len(lastMapEntry.value.mapData) > 0 && !lastMapEntry.value.keysStartWithDot() {
  1174  				// If a top-level condition did match but no sub-condition matched,
  1175  				// complain about the sub-condition instead of the top-level condition.
  1176  				// This leads to a less confusing error message. For example:
  1177  				//
  1178  				//   "exports": {
  1179  				//     "node": {
  1180  				//       "require": "./dist/bwip-js-node.js"
  1181  				//     }
  1182  				//   },
  1183  				//
  1184  				// We want the warning to say this:
  1185  				//
  1186  				//   note: None of the conditions in the package definition ("require") match any of the
  1187  				//         currently active conditions ("default", "import", "node")
  1188  				//   14 |       "node": {
  1189  				//      |               ^
  1190  				//
  1191  				// We don't want the warning to say this:
  1192  				//
  1193  				//   note: None of the conditions in the package definition ("browser", "electron", "node")
  1194  				//         match any of the currently active conditions ("default", "import", "node")
  1195  				//   7 |   "exports": {
  1196  				//     |              ^
  1197  				//
  1198  				// More information: https://github.com/evanw/esbuild/issues/1484
  1199  				target = lastMapEntry.value
  1200  			}
  1201  			keys := make([]logger.Span, len(target.mapData))
  1202  			for i, p := range target.mapData {
  1203  				keys[i] = logger.Span{Text: p.key, Range: p.keyRange}
  1204  			}
  1205  			return "", pjStatusUndefinedNoConditionsMatch, pjDebug{
  1206  				token:               target.firstToken,
  1207  				unmatchedConditions: keys,
  1208  			}
  1209  		}
  1210  
  1211  		return "", pjStatusUndefined, pjDebug{token: target.firstToken}
  1212  
  1213  	case pjArray:
  1214  		if len(target.arrData) == 0 {
  1215  			if r.debugLogs != nil {
  1216  				r.debugLogs.addNote(fmt.Sprintf("The path %q is set to an empty array", subpath))
  1217  			}
  1218  			return "", pjStatusNull, pjDebug{token: target.firstToken}
  1219  		}
  1220  		if r.debugLogs != nil {
  1221  			r.debugLogs.addNote(fmt.Sprintf("Checking for %q in an array", subpath))
  1222  			r.debugLogs.increaseIndent()
  1223  			defer r.debugLogs.decreaseIndent()
  1224  		}
  1225  		lastException := pjStatusUndefined
  1226  		lastDebug := pjDebug{token: target.firstToken}
  1227  		for _, targetValue := range target.arrData {
  1228  			// Let resolved be the result, continuing the loop on any Invalid Package Target error.
  1229  			resolved, status, debug := r.esmPackageTargetResolve(packageURL, targetValue, subpath, pattern, internal, conditions)
  1230  			if status == pjStatusInvalidPackageTarget || status == pjStatusNull {
  1231  				lastException = status
  1232  				lastDebug = debug
  1233  				continue
  1234  			}
  1235  			if status.isUndefined() {
  1236  				continue
  1237  			}
  1238  			return resolved, status, debug
  1239  		}
  1240  
  1241  		// Return or throw the last fallback resolution null return or error.
  1242  		return "", lastException, lastDebug
  1243  
  1244  	case pjNull:
  1245  		if r.debugLogs != nil {
  1246  			r.debugLogs.addNote(fmt.Sprintf("The path %q is set to null", subpath))
  1247  		}
  1248  		return "", pjStatusNull, pjDebug{token: target.firstToken, isBecauseOfNullLiteral: true}
  1249  	}
  1250  
  1251  	if r.debugLogs != nil {
  1252  		r.debugLogs.addNote(fmt.Sprintf("Invalid package target for path %q", subpath))
  1253  	}
  1254  	return "", pjStatusInvalidPackageTarget, pjDebug{token: target.firstToken}
  1255  }
  1256  
  1257  func esmParsePackageName(packageSpecifier string) (packageName string, packageSubpath string, ok bool) {
  1258  	if packageSpecifier == "" {
  1259  		return
  1260  	}
  1261  
  1262  	slash := strings.IndexByte(packageSpecifier, '/')
  1263  	if !strings.HasPrefix(packageSpecifier, "@") {
  1264  		if slash == -1 {
  1265  			slash = len(packageSpecifier)
  1266  		}
  1267  		packageName = packageSpecifier[:slash]
  1268  	} else {
  1269  		if slash == -1 {
  1270  			return
  1271  		}
  1272  		slash2 := strings.IndexByte(packageSpecifier[slash+1:], '/')
  1273  		if slash2 == -1 {
  1274  			slash2 = len(packageSpecifier[slash+1:])
  1275  		}
  1276  		packageName = packageSpecifier[:slash+1+slash2]
  1277  	}
  1278  
  1279  	if strings.HasPrefix(packageName, ".") || strings.ContainsAny(packageName, "\\%") {
  1280  		return
  1281  	}
  1282  
  1283  	packageSubpath = "." + packageSpecifier[len(packageName):]
  1284  	ok = true
  1285  	return
  1286  }
  1287  
  1288  func (r resolverQuery) esmPackageExportsReverseResolve(
  1289  	query string,
  1290  	root pjEntry,
  1291  	conditions map[string]bool,
  1292  ) (bool, string, logger.Range) {
  1293  	if root.kind == pjObject && root.keysStartWithDot() {
  1294  		if ok, subpath, token := r.esmPackageImportsExportsReverseResolve(query, root, conditions); ok {
  1295  			return true, subpath, token
  1296  		}
  1297  	}
  1298  
  1299  	return false, "", logger.Range{}
  1300  }
  1301  
  1302  func (r resolverQuery) esmPackageImportsExportsReverseResolve(
  1303  	query string,
  1304  	matchObj pjEntry,
  1305  	conditions map[string]bool,
  1306  ) (bool, string, logger.Range) {
  1307  	if !strings.HasSuffix(query, "*") {
  1308  		for _, entry := range matchObj.mapData {
  1309  			if ok, subpath, token := r.esmPackageTargetReverseResolve(query, entry.key, entry.value, esmReverseExact, conditions); ok {
  1310  				return true, subpath, token
  1311  			}
  1312  		}
  1313  	}
  1314  
  1315  	for _, expansion := range matchObj.expansionKeys {
  1316  		if strings.HasSuffix(expansion.key, "*") {
  1317  			if ok, subpath, token := r.esmPackageTargetReverseResolve(query, expansion.key, expansion.value, esmReversePattern, conditions); ok {
  1318  				return true, subpath, token
  1319  			}
  1320  		}
  1321  
  1322  		if ok, subpath, token := r.esmPackageTargetReverseResolve(query, expansion.key, expansion.value, esmReversePrefix, conditions); ok {
  1323  			return true, subpath, token
  1324  		}
  1325  	}
  1326  
  1327  	return false, "", logger.Range{}
  1328  }
  1329  
  1330  type esmReverseKind uint8
  1331  
  1332  const (
  1333  	esmReverseExact esmReverseKind = iota
  1334  	esmReversePattern
  1335  	esmReversePrefix
  1336  )
  1337  
  1338  func (r resolverQuery) esmPackageTargetReverseResolve(
  1339  	query string,
  1340  	key string,
  1341  	target pjEntry,
  1342  	kind esmReverseKind,
  1343  	conditions map[string]bool,
  1344  ) (bool, string, logger.Range) {
  1345  	switch target.kind {
  1346  	case pjString:
  1347  		switch kind {
  1348  		case esmReverseExact:
  1349  			if query == target.strData {
  1350  				return true, key, target.firstToken
  1351  			}
  1352  
  1353  		case esmReversePrefix:
  1354  			if strings.HasPrefix(query, target.strData) {
  1355  				return true, key + query[len(target.strData):], target.firstToken
  1356  			}
  1357  
  1358  		case esmReversePattern:
  1359  			star := strings.IndexByte(target.strData, '*')
  1360  			keyWithoutTrailingStar := strings.TrimSuffix(key, "*")
  1361  
  1362  			// Handle the case of no "*"
  1363  			if star == -1 {
  1364  				if query == target.strData {
  1365  					return true, keyWithoutTrailingStar, target.firstToken
  1366  				}
  1367  				break
  1368  			}
  1369  
  1370  			// Only support tracing through a single "*"
  1371  			prefix := target.strData[0:star]
  1372  			suffix := target.strData[star+1:]
  1373  			if !strings.ContainsRune(suffix, '*') && strings.HasPrefix(query, prefix) {
  1374  				if afterPrefix := query[len(prefix):]; strings.HasSuffix(afterPrefix, suffix) {
  1375  					starData := afterPrefix[:len(afterPrefix)-len(suffix)]
  1376  					return true, keyWithoutTrailingStar + starData, target.firstToken
  1377  				}
  1378  			}
  1379  		}
  1380  
  1381  	case pjObject:
  1382  		for _, p := range target.mapData {
  1383  			if p.key == "default" || conditions[p.key] {
  1384  				if ok, subpath, token := r.esmPackageTargetReverseResolve(query, key, p.value, kind, conditions); ok {
  1385  					return true, subpath, token
  1386  				}
  1387  			}
  1388  		}
  1389  
  1390  	case pjArray:
  1391  		for _, targetValue := range target.arrData {
  1392  			if ok, subpath, token := r.esmPackageTargetReverseResolve(query, key, targetValue, kind, conditions); ok {
  1393  				return true, subpath, token
  1394  			}
  1395  		}
  1396  	}
  1397  
  1398  	return false, "", logger.Range{}
  1399  }