package bundler

import (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"sort"
	"strings"
	"sync"

	"github.com/evanw/esbuild/internal/compat"
	"github.com/evanw/esbuild/internal/resolver"

	"github.com/evanw/esbuild/internal/ast"
	"github.com/evanw/esbuild/internal/config"
	"github.com/evanw/esbuild/internal/fs"
	"github.com/evanw/esbuild/internal/lexer"
	"github.com/evanw/esbuild/internal/logging"
	"github.com/evanw/esbuild/internal/printer"
	"github.com/evanw/esbuild/internal/runtime"
)

type bitSet struct {
	entries []byte
}

func newBitSet(bitCount uint) bitSet {
	return bitSet{make([]byte, (bitCount+7)/8)}
}

func (bs bitSet) hasBit(bit uint) bool {
	return (bs.entries[bit/8] & (1 << (bit & 7))) != 0
}

func (bs bitSet) setBit(bit uint) {
	bs.entries[bit/8] |= 1 << (bit & 7)
}

func (bs bitSet) equals(other bitSet) bool {
	return bytes.Equal(bs.entries, other.entries)
}

func (bs bitSet) copyFrom(other bitSet) {
	copy(bs.entries, other.entries)
}

func (bs *bitSet) bitwiseOrWith(other bitSet) {
	for i := range bs.entries {
		bs.entries[i] |= other.entries[i]
	}
}

type linkerContext struct {
	options     *config.Options
	log         logging.Log
	fs          fs.FS
	res         resolver.Resolver
	symbols     ast.SymbolMap
	entryPoints []uint32
	sources     []logging.Source
	files       []file
	fileMeta    []fileMeta
	hasErrors   bool
	lcaAbsPath  string

	// We should avoid traversing all files in the bundle, because the linker
	// should be able to run a linking operation on a large bundle where only
	// a few files are needed (e.g. an incremental compilation scenario). This
	// holds all files that could possibly be reached through the entry points.
	// If you need to iterate over all files in the linking operation, iterate
	// over this array. This array is also sorted in a deterministic ordering
	// to help ensure deterministic builds (source indices are random).
	reachableFiles []uint32

	// We may need to refer to the CommonJS "module" symbol for exports
	unboundModuleRef ast.Ref
}

// This contains linker-specific metadata corresponding to a "file" struct
// from the initial scan phase of the bundler. It's separated out because it's
// conceptually only used for a single linking operation and because multiple
// linking operations may be happening in parallel with different metadata for
// the same file.
type fileMeta struct {
	partMeta     []partMeta
	isEntryPoint bool

	// The path of this entry point relative to the lowest common ancestor
	// directory containing all entry points. Note: this must have OS-independent
	// path separators (i.e. '/' not '\').
	entryPointRelPath string

	// This is the index to the automatically-generated part containing code that
	// calls "__export(exports, { ... getters ... })". This is used to generate
	// getters on an exports object for ES6 export statements, and is both for
	// ES6 star imports and CommonJS-style modules.
	nsExportPartIndex uint32

	// The index of the automatically-generated part containing export statements
	// for every export in the entry point. This also contains the call to the
	// require wrapper for CommonJS-style entry points.
	entryPointExportPartIndex *uint32

	// This is only for TypeScript files. If an import symbol is in this map, it
	// means the import couldn't be found and doesn't actually exist. This is not
	// an error in TypeScript because the import is probably just a type.
	//
	// Normally we remove all unused imports for TypeScript files during parsing,
	// which automatically removes type-only imports. But there are certain re-
	// export situations where it's impossible to tell if an import is a type or
	// not:
	//
	//   import {typeOrNotTypeWhoKnows} from 'path';
	//   export {typeOrNotTypeWhoKnows};
	//
	// Really people should be using the TypeScript "isolatedModules" flag with
	// bundlers like this one that compile TypeScript files independently without
	// type checking. That causes the TypeScript type checker to emit the error
	// "Re-exporting a type when the '--isolatedModules' flag is provided requires
	// using 'export type'." But we try to be robust to such code anyway.
	isProbablyTypeScriptType map[ast.Ref]bool

	// Imports are matched with exports in a separate pass from when the matched
	// exports are actually bound to the imports. Here "binding" means adding non-
	// local dependencies on the parts in the exporting file that declare the
	// exported symbol to all parts in the importing file that use the imported
	// symbol.
	//
	// This must be a separate pass because of the "probably TypeScript type"
	// check above. We can't generate the part for the export namespace until
	// we've matched imports with exports because the generated code must omit
	// type-only imports in the export namespace code. And we can't bind exports
	// to imports until the part for the export namespace is generated since that
	// part needs to participate in the binding.
	//
	// This array holds the deferred imports to bind so the pass can be split
	// into two separate passes.
	importsToBind map[ast.Ref]importToBind

	// If true, the module must be bundled CommonJS-style like this:
	//
	//   // foo.ts
	//   let require_foo = __commonJS((exports, module) => {
	//     ...
	//   });
	//
	//   // bar.ts
	//   let foo = flag ? require_foo() : null;
	//
	cjsWrap bool

	// If true, all exports must be reached via property accesses off a call to
	// the CommonJS wrapper for this module. In addition, all ES6 exports for
	// this module must be added as getters to the CommonJS "exports" object.
	cjsStyleExports bool

	// The index of the automatically-generated part used to represent the
	// CommonJS wrapper. This part is empty and is only useful for tree shaking
	// and code splitting. The CommonJS wrapper can't be inserted into the part
	// because the wrapper contains other parts, which can't be represented by
	// the current part system.
	cjsWrapperPartIndex *uint32

	// The minimum number of links in the module graph to get from an entry point
	// to this file
	distanceFromEntryPoint uint32

	// This holds all entry points that can reach this file. It will be used to
	// assign the parts in this file to a chunk.
	entryBits bitSet

	// This includes both named exports and re-exports.
	//
	// Named exports come from explicit export statements in the original file,
	// and are copied from the "NamedExports" field in the AST.
	//
	// Re-exports come from other files and are the result of resolving export
	// star statements (i.e. "export * from 'foo'").
	resolvedExports map[string]exportData

	// Never iterate over "resolvedExports" directly. Instead, iterate over this
	// array. Some exports in that map aren't meant to end up in generated code.
	// This array excludes these exports and is also sorted, which avoids non-
	// determinism due to random map iteration order.
	sortedAndFilteredExportAliases []string
}

type importToBind struct {
	sourceIndex uint32
	ref         ast.Ref
}

type exportData struct {
	ref ast.Ref

	// The location of the path string for error messages. This is only from re-
	// exports (i.e. "export * from 'foo'").
	pathLoc *ast.Loc

	// This is the file that the named export above came from. This will be
	// different from the file that contains this object if this is a re-export.
	sourceIndex uint32

	// Exports from export stars are shadowed by other exports. This flag helps
	// implement this behavior.
	isFromExportStar bool

	// If export star resolution finds two or more symbols with the same name,
	// then the name is a ambiguous and cannot be used. This causes the name to
	// be silently omitted from any namespace imports and causes a compile-time
	// failure if the name is used in an ES6 import statement.
	isAmbiguous bool
}

// This contains linker-specific metadata corresponding to an "ast.Part" struct
// from the initial scan phase of the bundler. It's separated out because it's
// conceptually only used for a single linking operation and because multiple
// linking operations may be happening in parallel with different metadata for
// the same part in the same file.
type partMeta struct {
	// This holds all entry points that can reach this part. It will be used to
	// assign this part to a chunk.
	entryBits bitSet

	// These are dependencies that come from other files via import statements.
	nonLocalDependencies []partRef
}

type partRef struct {
	sourceIndex uint32
	partIndex   uint32
}

type chunkMeta struct {
	// The path of this chunk relative to the output directory. Note: this must
	// have OS-independent path separators (i.e. '/' not '\').
	relPath string

	filesWithPartsInChunk map[uint32]bool
	entryBits             bitSet

	// This information is only useful if "isEntryPoint" is true
	isEntryPoint  bool
	sourceIndex   uint32 // An index into "c.sources"
	entryPointBit uint   // An index into "c.entryPoints"

	// For code splitting
	crossChunkImportRecords []ast.ImportRecord
	crossChunkPrefixStmts   []ast.Stmt
	crossChunkSuffixStmts   []ast.Stmt
}

func newLinkerContext(
	options *config.Options,
	log logging.Log,
	fs fs.FS,
	res resolver.Resolver,
	sources []logging.Source,
	files []file,
	entryPoints []uint32,
	lcaAbsPath string,
) linkerContext {
	// Clone information about symbols and files so we don't mutate the input data
	c := linkerContext{
		options:        options,
		log:            log,
		fs:             fs,
		res:            res,
		sources:        sources,
		entryPoints:    append([]uint32{}, entryPoints...),
		files:          make([]file, len(files)),
		fileMeta:       make([]fileMeta, len(files)),
		symbols:        ast.NewSymbolMap(len(files)),
		reachableFiles: findReachableFiles(sources, files, entryPoints),
		lcaAbsPath:     lcaAbsPath,
	}

	// Clone various things since we may mutate them later
	for _, sourceIndex := range c.reachableFiles {
		file := files[sourceIndex]

		// Clone the symbol map
		fileSymbols := append([]ast.Symbol{}, file.ast.Symbols.Outer[sourceIndex]...)
		c.symbols.Outer[sourceIndex] = fileSymbols
		file.ast.Symbols = c.symbols

		// Zero out the use count statistics. These will be recomputed later after
		// taking tree shaking into account.
		for i := range fileSymbols {
			fileSymbols[i].UseCountEstimate = 0
		}

		// Clone the parts
		file.ast.Parts = append([]ast.Part{}, file.ast.Parts...)
		for i, part := range file.ast.Parts {
			clone := make(map[ast.Ref]ast.SymbolUse, len(part.SymbolUses))
			for ref, uses := range part.SymbolUses {
				clone[ref] = uses
			}
			file.ast.Parts[i].SymbolUses = clone
		}

		// Clone the import records
		file.ast.ImportRecords = append([]ast.ImportRecord{}, file.ast.ImportRecords...)

		// Clone the import map
		namedImports := make(map[ast.Ref]ast.NamedImport, len(file.ast.NamedImports))
		for k, v := range file.ast.NamedImports {
			namedImports[k] = v
		}
		file.ast.NamedImports = namedImports

		// Clone the export map
		resolvedExports := make(map[string]exportData)
		for alias, ref := range file.ast.NamedExports {
			resolvedExports[alias] = exportData{
				ref:         ref,
				sourceIndex: sourceIndex,
			}
		}

		// Clone the top-level symbol-to-parts map
		topLevelSymbolToParts := make(map[ast.Ref][]uint32)
		for ref, parts := range file.ast.TopLevelSymbolToParts {
			topLevelSymbolToParts[ref] = parts
		}
		file.ast.TopLevelSymbolToParts = topLevelSymbolToParts

		// Clone the top-level scope so we can generate more variables
		{
			new := &ast.Scope{}
			*new = *file.ast.ModuleScope
			new.Generated = append([]ast.Ref{}, new.Generated...)
			file.ast.ModuleScope = new
		}

		// Update the file in our copy of the file array
		c.files[sourceIndex] = file

		// Also associate some default metadata with the file
		c.fileMeta[sourceIndex] = fileMeta{
			distanceFromEntryPoint:   ^uint32(0),
			cjsStyleExports:          file.ast.HasCommonJSFeatures() || (file.ast.HasLazyExport && !c.options.IsBundling),
			partMeta:                 make([]partMeta, len(file.ast.Parts)),
			resolvedExports:          resolvedExports,
			isProbablyTypeScriptType: make(map[ast.Ref]bool),
			importsToBind:            make(map[ast.Ref]importToBind),
		}
	}

	// Mark all entry points so we don't add them again for import() expressions
	for _, sourceIndex := range entryPoints {
		fileMeta := &c.fileMeta[sourceIndex]
		fileMeta.isEntryPoint = true

		// Entry points must be CommonJS-style if the output format doesn't support
		// ES6 export syntax
		if !options.OutputFormat.KeepES6ImportExportSyntax() && c.files[sourceIndex].ast.HasES6Exports {
			fileMeta.cjsStyleExports = true
		}
	}

	// Allocate a new unbound symbol called "module" in case we need it later
	{
		runtimeSymbols := &c.symbols.Outer[runtime.SourceIndex]
		c.unboundModuleRef = ast.Ref{OuterIndex: runtime.SourceIndex, InnerIndex: uint32(len(*runtimeSymbols))}
		*runtimeSymbols = append(*runtimeSymbols, ast.Symbol{
			Kind: ast.SymbolUnbound,
			Name: "module",
			Link: ast.InvalidRef,
		})
	}

	return c
}

type indexAndPath struct {
	sourceIndex uint32
	path        ast.Path
}

// This type is just so we can use Go's native sort function
type indexAndPathArray []indexAndPath

func (a indexAndPathArray) Len() int          { return len(a) }
func (a indexAndPathArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }

func (a indexAndPathArray) Less(i int, j int) bool {
	return a[i].path.ComesBeforeInSortedOrder(a[j].path)
}

// Find all files reachable from all entry points
func findReachableFiles(sources []logging.Source, files []file, entryPoints []uint32) []uint32 {
	visited := make(map[uint32]bool)
	sorted := indexAndPathArray{}
	var visit func(uint32)

	// Include this file and all files it imports
	visit = func(sourceIndex uint32) {
		if !visited[sourceIndex] {
			visited[sourceIndex] = true
			file := files[sourceIndex]
			for _, part := range file.ast.Parts {
				for _, importRecordIndex := range part.ImportRecordIndices {
					if record := &file.ast.ImportRecords[importRecordIndex]; record.SourceIndex != nil {
						visit(*record.SourceIndex)
					}
				}
			}
			sorted = append(sorted, indexAndPath{sourceIndex, sources[sourceIndex].KeyPath})
		}
	}

	// The runtime is always included in case it's needed
	visit(runtime.SourceIndex)

	// Include all files reachable from any entry point
	for _, entryPoint := range entryPoints {
		visit(entryPoint)
	}

	// Sort by absolute path for determinism
	sort.Sort(sorted)

	// Extract the source indices
	reachableFiles := make([]uint32, len(sorted))
	for i, item := range sorted {
		reachableFiles[i] = item.sourceIndex
	}
	return reachableFiles
}

func (c *linkerContext) addRangeError(source logging.Source, r ast.Range, text string) {
	c.log.AddRangeError(&source, r, text)
	c.hasErrors = true
}

func (c *linkerContext) link() []OutputFile {
	c.scanImportsAndExports()

	// Stop now if there were errors
	if c.hasErrors {
		return []OutputFile{}
	}

	c.markPartsReachableFromEntryPoints()

	if !c.options.IsBundling {
		for _, entryPoint := range c.entryPoints {
			c.markExportsAsUnbound(entryPoint)
		}
	}

	// Make sure calls to "ast.FollowSymbols()" in parallel goroutines after this
	// won't hit concurrent map mutation hazards
	ast.FollowAllSymbols(c.symbols)

	c.renameOrMinifyAllSymbols()

	chunks := c.computeChunks()
	c.computeCrossChunkDependencies(chunks)

	// Generate chunks in parallel
	results := make([][]OutputFile, len(chunks))
	waitGroup := sync.WaitGroup{}
	waitGroup.Add(len(chunks))
	for i, chunk := range chunks {
		go func(i int, chunk chunkMeta) {
			results[i] = c.generateChunk(chunk)
			waitGroup.Done()
		}(i, chunk)
	}
	waitGroup.Wait()

	// Join the results in chunk order for determinism
	var outputFiles []OutputFile
	for _, group := range results {
		outputFiles = append(outputFiles, group...)
	}
	return outputFiles
}

func (c *linkerContext) relativePathBetweenChunks(fromChunk *chunkMeta, toRelPath string) string {
	relPath, ok := c.fs.Rel(c.fs.Dir(fromChunk.relPath), toRelPath)
	if !ok {
		c.log.AddError(nil, ast.Loc{},
			fmt.Sprintf("Cannot traverse from chunk %q to chunk %q", fromChunk.relPath, toRelPath))
		return ""
	}

	// Make sure to always use forward slashes, even on Windows
	relPath = strings.ReplaceAll(relPath, "\\", "/")

	// Make sure the relative path doesn't start with a name, since that could
	// be interpreted as a package path instead of a relative path
	if !strings.HasPrefix(relPath, "./") && !strings.HasPrefix(relPath, "../") {
		relPath = "./" + relPath
	}

	return relPath
}

func (c *linkerContext) computeCrossChunkDependencies(chunks []chunkMeta) {
	if len(chunks) < 2 {
		// No need to compute cross-chunk dependencies if there can't be any
		return
	}

	type chunkMeta struct {
		imports map[ast.Ref]bool
		exports map[ast.Ref]bool
	}

	topLevelDeclaredSymbolToChunk := make(map[ast.Ref]uint32)
	chunkMetas := make([]chunkMeta, len(chunks))

	// For each chunk, see what symbols it uses from other chunks
	for chunkIndex, chunk := range chunks {
		chunkKey := string(chunk.entryBits.entries)
		imports := make(map[ast.Ref]bool)
		chunkMetas[chunkIndex] = chunkMeta{imports: imports, exports: make(map[ast.Ref]bool)}

		// Go over each file in this chunk
		for sourceIndex := range chunk.filesWithPartsInChunk {
			file := &c.files[sourceIndex]
			fileMeta := &c.fileMeta[sourceIndex]

			// Go over each part in this file that's marked for inclusion in this chunk
			for partIndex, partMeta := range fileMeta.partMeta {
				if string(partMeta.entryBits.entries) != chunkKey {
					continue
				}
				part := &file.ast.Parts[partIndex]

				// Rewrite external dynamic imports to point to the chunk for that entry point
				for _, importRecordIndex := range part.ImportRecordIndices {
					record := &file.ast.ImportRecords[importRecordIndex]
					if record.SourceIndex != nil && c.isExternalDynamicImport(record) {
						record.Path.Text = c.relativePathBetweenChunks(&chunk, c.fileMeta[*record.SourceIndex].entryPointRelPath)
						record.SourceIndex = nil
					}
				}

				// Remember what chunk each top-level symbol is declared in. Symbols
				// with multiple declarations such as repeated "var" statements with
				// the same name should already be marked as all being in a single
				// chunk. In that case this will overwrite the same value below which
				// is fine.
				for _, declared := range part.DeclaredSymbols {
					if declared.IsTopLevel {
						topLevelDeclaredSymbolToChunk[declared.Ref] = uint32(chunkIndex)
					}
				}

				// Record each symbol used in this part. This will later be matched up
				// with our map of which chunk a given symbol is declared in to
				// determine if the symbol needs to be imported from another chunk.
				for ref := range part.SymbolUses {
					symbol := c.symbols.Get(ref)

					// Ignore unbound symbols, which don't have declarations
					if symbol.Kind == ast.SymbolUnbound {
						continue
					}

					// Ignore symbols that are going to be replaced by undefined
					if symbol.ImportItemStatus == ast.ImportItemMissing {
						continue
					}

					// If this is imported from another file, follow the import
					// reference and reference the symbol in that file instead
					if importToBind, ok := fileMeta.importsToBind[ref]; ok {
						ref = importToBind.ref
						symbol = c.symbols.Get(ref)
					}

					// If this is an ES6 import from a CommonJS file, it will become a
					// property access off the namespace symbol instead of a bare
					// identifier. In that case we want to pull in the namespace symbol
					// instead. The namespace symbol stores the result of "require()".
					if symbol.NamespaceAlias != nil {
						ref = symbol.NamespaceAlias.NamespaceRef
					}

					// We must record this relationship even for symbols that are not
					// imports. Due to code splitting, the definition of a symbol may
					// be moved to a separate chunk than the use of a symbol even if
					// the definition and use of that symbol are originally from the
					// same source file.
					imports[ref] = true
				}
			}
		}
	}

	// Generate cross-chunk imports
	for chunkIndex := range chunks {
		chunk := &chunks[chunkIndex]

		// Find all uses in this chunk of symbols from other chunks
		importsFromOtherChunks := make(map[uint32][]ast.Ref)
		for importRef := range chunkMetas[chunkIndex].imports {
			// Ignore uses that aren't top-level symbols
			if otherChunkIndex, ok := topLevelDeclaredSymbolToChunk[importRef]; ok && otherChunkIndex != uint32(chunkIndex) {
				importsFromOtherChunks[otherChunkIndex] = append(importsFromOtherChunks[otherChunkIndex], importRef)
				chunkMetas[otherChunkIndex].exports[importRef] = true
			}
		}

		// If this is an entry point, make sure we import all chunks belonging to
		// this entry point, even if there are no imports. We need to make sure
		// these chunks are evaluated for their side effects too.
		if chunk.isEntryPoint {
			for otherChunkIndex, otherChunk := range chunks {
				if chunkIndex != otherChunkIndex && otherChunk.entryBits.hasBit(chunk.entryPointBit) {
					imports := importsFromOtherChunks[uint32(otherChunkIndex)]
					importsFromOtherChunks[uint32(otherChunkIndex)] = imports
				}
			}
		}

		var crossChunkImportRecords []ast.ImportRecord
		var crossChunkPrefixStmts []ast.Stmt

		for _, crossChunkImport := range c.sortedCrossChunkImports(chunks, importsFromOtherChunks) {
			switch c.options.OutputFormat {
			case config.FormatESModule:
				var items []ast.ClauseItem
				for _, alias := range crossChunkImport.sortedImportAliases {
					items = append(items, ast.ClauseItem{Name: ast.LocRef{Ref: alias.ref}, Alias: alias.name})
				}
				importRecordIndex := uint32(len(crossChunkImportRecords))
				crossChunkImportRecords = append(crossChunkImportRecords, ast.ImportRecord{
					Kind: ast.ImportStmt,
					Path: ast.Path{Text: c.relativePathBetweenChunks(chunk, chunks[crossChunkImport.chunkIndex].relPath)},
				})
				if len(items) > 0 {
					// "import {a, b} from './chunk.js'"
					crossChunkPrefixStmts = append(crossChunkPrefixStmts, ast.Stmt{Data: &ast.SImport{
						Items:             &items,
						ImportRecordIndex: importRecordIndex,
					}})
				} else {
					// "import './chunk.js'"
					crossChunkPrefixStmts = append(crossChunkPrefixStmts, ast.Stmt{Data: &ast.SImport{
						ImportRecordIndex: importRecordIndex,
					}})
				}

			default:
				panic("Internal error")
			}
		}

		chunk.crossChunkImportRecords = crossChunkImportRecords
		chunk.crossChunkPrefixStmts = crossChunkPrefixStmts
	}

	// Generate cross-chunk exports
	for chunkIndex := range chunks {
		switch c.options.OutputFormat {
		case config.FormatESModule:
			var items []ast.ClauseItem
			for _, alias := range c.sortedCrossChunkExportRefs(chunkMetas[chunkIndex].exports) {
				items = append(items, ast.ClauseItem{Name: ast.LocRef{Ref: alias.ref}, Alias: alias.name})
			}
			if len(items) > 0 {
				chunks[chunkIndex].crossChunkSuffixStmts = []ast.Stmt{{Data: &ast.SExportClause{
					Items: items,
				}}}
			}

		default:
			panic("Internal error")
		}
	}
}

type crossChunkImport struct {
	chunkIndex          uint32
	sortingKey          string
	sortedImportAliases crossChunkAliasArray
}

// This type is just so we can use Go's native sort function
type crossChunkImportArray []crossChunkImport

func (a crossChunkImportArray) Len() int          { return len(a) }
func (a crossChunkImportArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }

func (a crossChunkImportArray) Less(i int, j int) bool {
	return a[i].sortingKey < a[j].sortingKey
}

// Sort cross-chunk imports by chunk name for determinism
func (c *linkerContext) sortedCrossChunkImports(chunks []chunkMeta, importsFromOtherChunks map[uint32][]ast.Ref) crossChunkImportArray {
	result := make(crossChunkImportArray, 0, len(importsFromOtherChunks))

	for otherChunkIndex, importRefs := range importsFromOtherChunks {
		result = append(result, crossChunkImport{
			chunkIndex:          otherChunkIndex,
			sortingKey:          chunks[otherChunkIndex].relPath,
			sortedImportAliases: c.sortedCrossChunkImportRefs(importRefs),
		})
	}

	sort.Sort(result)
	return result
}

type crossChunkAlias struct {
	name string
	ref  ast.Ref
}

// This type is just so we can use Go's native sort function
type crossChunkAliasArray []crossChunkAlias

func (a crossChunkAliasArray) Len() int          { return len(a) }
func (a crossChunkAliasArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }

func (a crossChunkAliasArray) Less(i int, j int) bool {
	return a[i].name < a[j].name
}

// Sort cross-chunk imports by chunk name for determinism
func (c *linkerContext) sortedCrossChunkImportRefs(importRefs []ast.Ref) crossChunkAliasArray {
	result := make(crossChunkAliasArray, 0, len(importRefs))
	for _, ref := range importRefs {
		result = append(result, crossChunkAlias{ref: ref, name: c.symbols.Get(ref).Name})
	}
	sort.Sort(result)
	return result
}

// Sort cross-chunk exports by chunk name for determinism
func (c *linkerContext) sortedCrossChunkExportRefs(exportRefs map[ast.Ref]bool) crossChunkAliasArray {
	result := make(crossChunkAliasArray, 0, len(exportRefs))
	for ref := range exportRefs {
		result = append(result, crossChunkAlias{ref: ref, name: c.symbols.Get(ref).Name})
	}
	sort.Sort(result)
	return result
}

func (c *linkerContext) scanImportsAndExports() {
	// Step 1: Figure out what modules must be CommonJS
	for _, sourceIndex := range c.reachableFiles {
		file := &c.files[sourceIndex]
		for _, part := range file.ast.Parts {
			// Handle require() and import()
			for _, importRecordIndex := range part.ImportRecordIndices {
				record := &file.ast.ImportRecords[importRecordIndex]

				// Make sure the printer can require() CommonJS modules
				if record.SourceIndex != nil {
					record.WrapperRef = c.files[*record.SourceIndex].ast.WrapperRef
				}

				switch record.Kind {
				case ast.ImportStmt:
					// Importing using ES6 syntax from a file without any ES6 syntax
					// causes that module to be considered CommonJS-style, even if it
					// doesn't have any CommonJS exports.
					//
					// That means the ES6 imports will silently become undefined instead
					// of causing errors. This is for compatibility with older CommonJS-
					// style bundlers.
					//
					// I've only come across a single case where this mattered, in the
					// package https://github.com/megawac/MutationObserver.js. The library
					// used to look like this:
					//
					//   this.MutationObserver = this.MutationObserver || (function() {
					//     ...
					//     return MutationObserver;
					//   })();
					//
					// That is compatible with CommonJS since "this" is an alias for
					// "exports". The code in question used the package like this:
					//
					//   import MutationObserver from '@sheerun/mutationobserver-shim';
					//
					// Then the library was updated to do this instead:
					//
					//   window.MutationObserver = window.MutationObserver || (function() {
					//     ...
					//     return MutationObserver;
					//   })();
					//
					// The package was updated without the ES6 import being removed. The
					// code still has the import but "MutationObserver" is now undefined:
					//
					//   import MutationObserver from '@sheerun/mutationobserver-shim';
					//
					if !record.DoesNotUseExports && record.SourceIndex != nil {
						otherSourceIndex := *record.SourceIndex
						otherFile := &c.files[otherSourceIndex]
						if !otherFile.ast.HasES6Syntax() && !otherFile.ast.HasLazyExport {
							c.fileMeta[otherSourceIndex].cjsStyleExports = true
						}
					}

				case ast.ImportRequire:
					// Files that are imported with require() must be CommonJS modules
					if record.SourceIndex != nil {
						c.fileMeta[*record.SourceIndex].cjsStyleExports = true
					}

				case ast.ImportDynamic:
					if c.options.CodeSplitting {
						// Files that are imported with import() must be entry points
						if record.SourceIndex != nil {
							otherFileMeta := &c.fileMeta[*record.SourceIndex]
							if !otherFileMeta.isEntryPoint {
								c.entryPoints = append(c.entryPoints, *record.SourceIndex)
								otherFileMeta.isEntryPoint = true
							}
						}
					} else {
						// If we're not splitting, then import() is just a require() that
						// returns a promise, so the imported file must be a CommonJS module
						if record.SourceIndex != nil {
							c.fileMeta[*record.SourceIndex].cjsStyleExports = true
						}
					}
				}
			}
		}
	}

	// Step 2: Propagate CommonJS status for export star statements that are re-
	// exports from a CommonJS module. Exports from a CommonJS module are not
	// statically analyzable, so the export star must be evaluated at run time
	// instead of at bundle time.
	for _, sourceIndex := range c.reachableFiles {
		if len(c.files[sourceIndex].ast.ExportStarImportRecords) > 0 {
			visited := make(map[uint32]bool)
			c.isCommonJSDueToExportStar(sourceIndex, visited)
		}
	}

	// Step 3: Resolve "export * from" statements. This must be done after we
	// discover all modules that can be CommonJS because export stars are ignored
	// for CommonJS modules.
	for _, sourceIndex := range c.reachableFiles {
		file := &c.files[sourceIndex]
		fileMeta := &c.fileMeta[sourceIndex]

		// Expression-style loaders defer code generation until linking. Code
		// generation is done here because at this point we know that the
		// "cjsStyleExports" flag has its final value and will not be changed.
		if file.ast.HasLazyExport {
			c.generateCodeForLazyExport(sourceIndex, file, fileMeta)
		}

		// Even if the output file is CommonJS-like, we may still need to wrap
		// CommonJS-style files. Any file that imports a CommonJS-style file will
		// cause that file to need to be wrapped. This is because the import
		// method, whatever it is, will need to invoke the wrapper. Note that
		// this can include entry points (e.g. an entry point that imports a file
		// that imports that entry point).
		for _, part := range file.ast.Parts {
			for _, importRecordIndex := range part.ImportRecordIndices {
				if record := &file.ast.ImportRecords[importRecordIndex]; record.SourceIndex != nil {
					otherFileMeta := &c.fileMeta[*record.SourceIndex]
					if otherFileMeta.cjsStyleExports {
						otherFileMeta.cjsWrap = true
					}
				}
			}
		}

		// Propagate exports for export star statements
		if len(file.ast.ExportStarImportRecords) > 0 {
			visited := make(map[uint32]bool)
			c.addExportsForExportStar(fileMeta.resolvedExports, sourceIndex, nil, visited)
		}

		// Add an empty part for the namespace export that we can fill in later
		fileMeta.nsExportPartIndex = uint32(len(file.ast.Parts))
		file.ast.Parts = append(file.ast.Parts, ast.Part{
			LocalDependencies:    make(map[uint32]bool),
			SymbolUses:           make(map[ast.Ref]ast.SymbolUse),
			CanBeRemovedIfUnused: true,
			IsNamespaceExport:    true,
		})
		fileMeta.partMeta = append(fileMeta.partMeta, partMeta{
			entryBits: newBitSet(uint(len(c.entryPoints))),
		})

		// Also add a special export called "*" so import stars can bind to it.
		// This must be done in this step because it must come after CommonJS
		// module discovery but before matching imports with exports.
		fileMeta.resolvedExports["*"] = exportData{
			ref:         file.ast.ExportsRef,
			sourceIndex: sourceIndex,
		}
		file.ast.TopLevelSymbolToParts[file.ast.ExportsRef] = []uint32{fileMeta.nsExportPartIndex}
	}

	// Step 4: Match imports with exports. This must be done after we process all
	// export stars because imports can bind to export star re-exports.
	for _, sourceIndex := range c.reachableFiles {
		file := &c.files[sourceIndex]
		fileMeta := &c.fileMeta[sourceIndex]

		if len(file.ast.NamedImports) > 0 {
			c.matchImportsWithExportsForFile(uint32(sourceIndex))
		}

		// If the output format doesn't have an implicit CommonJS wrapper, any file
		// that uses CommonJS features will need to be wrapped, even though the
		// resulting wrapper won't be invoked by other files.
		if !fileMeta.cjsWrap && fileMeta.cjsStyleExports &&
			(c.options.OutputFormat == config.FormatIIFE ||
				c.options.OutputFormat == config.FormatESModule) {
			fileMeta.cjsWrap = true
		}

		// If we're exporting as CommonJS and this file doesn't need a wrapper,
		// then we'll be using the actual CommonJS "exports" and/or "module"
		// symbols. In that case make sure to mark them as such so they don't
		// get minified.
		if c.options.OutputFormat == config.FormatCommonJS && !fileMeta.cjsWrap && fileMeta.isEntryPoint {
			exportsRef := ast.FollowSymbols(c.symbols, file.ast.ExportsRef)
			moduleRef := ast.FollowSymbols(c.symbols, file.ast.ModuleRef)
			c.symbols.Get(exportsRef).Kind = ast.SymbolUnbound
			c.symbols.Get(moduleRef).Kind = ast.SymbolUnbound
		}
	}

	// Step 5: Create namespace exports for every file. This is always necessary
	// for CommonJS files, and is also necessary for other files if they are
	// imported using an import star statement.
	for _, sourceIndex := range c.reachableFiles {
		// Now that all exports have been resolved, sort and filter them to create
		// something we can iterate over later.
		fileMeta := &c.fileMeta[sourceIndex]
		aliases := make([]string, 0, len(fileMeta.resolvedExports))
		for alias, export := range fileMeta.resolvedExports {
			// The automatically-generated namespace export is just for internal binding
			// purposes and isn't meant to end up in generated code.
			if alias == "*" {
				continue
			}

			// Re-exporting multiple symbols with the same name causes an ambiguous
			// export. These names cannot be used and should not end up in generated code.
			if export.isAmbiguous {
				continue
			}

			// Ignore re-exported imports in TypeScript files that failed to be
			// resolved. These are probably just type-only imports so the best thing to
			// do is to silently omit them from the export list.
			if c.fileMeta[export.sourceIndex].isProbablyTypeScriptType[export.ref] {
				continue
			}

			aliases = append(aliases, alias)
		}
		sort.Strings(aliases)
		fileMeta.sortedAndFilteredExportAliases = aliases

		// Export creation uses "sortedAndFilteredExportAliases" so this must
		// come second after we fill in that array
		c.createExportsForFile(uint32(sourceIndex))
	}

	// Step 6: Bind imports to exports. This adds non-local dependencies on the
	// parts that declare the export to all parts that use the import.
	for _, sourceIndex := range c.reachableFiles {
		file := &c.files[sourceIndex]
		fileMeta := &c.fileMeta[sourceIndex]

		for importRef, importToBind := range fileMeta.importsToBind {
			resolvedFile := &c.files[importToBind.sourceIndex]
			partsDeclaringSymbol := resolvedFile.ast.TopLevelSymbolToParts[importToBind.ref]

			for _, partIndex := range file.ast.NamedImports[importRef].LocalPartsWithUses {
				partMeta := &fileMeta.partMeta[partIndex]

				for _, resolvedPartIndex := range partsDeclaringSymbol {
					partMeta.nonLocalDependencies = append(partMeta.nonLocalDependencies, partRef{
						sourceIndex: importToBind.sourceIndex,
						partIndex:   resolvedPartIndex,
					})
				}
			}

			// Merge these symbols so they will share the same name
			ast.MergeSymbols(c.symbols, importRef, importToBind.ref)
		}
	}
}

func (c *linkerContext) generateCodeForLazyExport(sourceIndex uint32, file *file, fileMeta *fileMeta) {
	// Grab the lazy expression
	if len(file.ast.Parts) < 1 {
		panic("Internal error")
	}
	part := &file.ast.Parts[0]
	if len(part.Stmts) != 1 {
		panic("Internal error")
	}
	lazy, ok := part.Stmts[0].Data.(*ast.SLazyExport)
	if !ok {
		panic("Internal error")
	}

	// Use "module.exports = value" for CommonJS-style modules
	if fileMeta.cjsStyleExports {
		part.Stmts = []ast.Stmt{ast.AssignStmt(
			ast.Expr{Loc: lazy.Value.Loc, Data: &ast.EDot{
				Target:  ast.Expr{Loc: lazy.Value.Loc, Data: &ast.EIdentifier{Ref: file.ast.ModuleRef}},
				Name:    "exports",
				NameLoc: lazy.Value.Loc,
			}},
			lazy.Value,
		)}
		part.SymbolUses[file.ast.ModuleRef] = ast.SymbolUse{CountEstimate: 1}
		file.ast.UsesModuleRef = true
		return
	}

	// Otherwise, generate ES6 export statements. These are added as additional
	// parts so they can be tree shaken individually.
	part.Stmts = nil

	type prevExport struct {
		ref       ast.Ref
		partIndex uint32
	}

	generateExport := func(name string, alias string, value ast.Expr, prevExports []prevExport) prevExport {
		// Generate a new symbol
		inner := &c.symbols.Outer[sourceIndex]
		ref := ast.Ref{OuterIndex: sourceIndex, InnerIndex: uint32(len(*inner))}
		*inner = append(*inner, ast.Symbol{Kind: ast.SymbolOther, Name: name, Link: ast.InvalidRef})
		file.ast.ModuleScope.Generated = append(file.ast.ModuleScope.Generated, ref)

		// Generate an ES6 export
		var stmt ast.Stmt
		if alias == "default" {
			stmt = ast.Stmt{Loc: value.Loc, Data: &ast.SExportDefault{
				DefaultName: ast.LocRef{Loc: value.Loc, Ref: ref},
				Value:       ast.ExprOrStmt{Expr: &value},
			}}
		} else {
			stmt = ast.Stmt{Loc: value.Loc, Data: &ast.SLocal{
				IsExport: true,
				Decls: []ast.Decl{{
					Binding: ast.Binding{Loc: value.Loc, Data: &ast.BIdentifier{Ref: ref}},
					Value:   &value,
				}},
			}}
		}

		// Link the export into the graph for tree shaking
		partIndex := uint32(len(file.ast.Parts))
		file.ast.Parts = append(file.ast.Parts, ast.Part{
			Stmts:                []ast.Stmt{stmt},
			LocalDependencies:    make(map[uint32]bool),
			SymbolUses:           map[ast.Ref]ast.SymbolUse{file.ast.ModuleRef: ast.SymbolUse{CountEstimate: 1}},
			DeclaredSymbols:      []ast.DeclaredSymbol{{Ref: ref, IsTopLevel: true}},
			CanBeRemovedIfUnused: true,
		})
		fileMeta.partMeta = append(fileMeta.partMeta, partMeta{
			entryBits: newBitSet(uint(len(c.entryPoints))),
		})
		file.ast.TopLevelSymbolToParts[ref] = []uint32{partIndex}
		fileMeta.resolvedExports[alias] = exportData{ref: ref, sourceIndex: sourceIndex}
		part := &file.ast.Parts[partIndex]
		for _, export := range prevExports {
			part.SymbolUses[export.ref] = ast.SymbolUse{CountEstimate: 1}
			part.LocalDependencies[export.partIndex] = true
		}
		return prevExport{ref: ref, partIndex: partIndex}
	}

	// Unwrap JSON objects into separate top-level variables
	var prevExports []prevExport
	if object, ok := lazy.Value.Data.(*ast.EObject); ok {
		for i, property := range object.Properties {
			if str, ok := property.Key.Data.(*ast.EString); ok && lexer.IsIdentifierUTF16(str.Value) {
				name := lexer.UTF16ToString(str.Value)
				export := generateExport(name, name, *property.Value, nil)
				prevExports = append(prevExports, export)
				object.Properties[i].Value = &ast.Expr{Loc: property.Key.Loc, Data: &ast.EIdentifier{Ref: export.ref}}
			}
		}
	}

	// Generate the default export
	generateExport(c.sources[sourceIndex].IdentifierName+"_default", "default", lazy.Value, prevExports)
}

func (c *linkerContext) createExportsForFile(sourceIndex uint32) {
	var entryPointES6ExportItems []ast.ClauseItem
	var entryPointExportStmts []ast.Stmt
	fileMeta := &c.fileMeta[sourceIndex]
	file := &c.files[sourceIndex]

	// If the output format is ES6 modules and we're an entry point, generate an
	// ES6 export statement containing all exports. Except don't do that if this
	// entry point is a CommonJS-style module, since that would generate an ES6
	// export statement that's not top-level. Instead, we will export the CommonJS
	// exports as a default export later on.
	needsEntryPointES6ExportPart := fileMeta.isEntryPoint && !fileMeta.cjsWrap &&
		c.options.OutputFormat == config.FormatESModule && len(fileMeta.sortedAndFilteredExportAliases) > 0

	// Generate a getter per export
	properties := []ast.Property{}
	nsExportNonLocalDependencies := []partRef{}
	entryPointExportNonLocalDependencies := []partRef{}
	nsExportSymbolUses := make(map[ast.Ref]ast.SymbolUse)
	entryPointExportSymbolUses := make(map[ast.Ref]ast.SymbolUse)
	for _, alias := range fileMeta.sortedAndFilteredExportAliases {
		export := fileMeta.resolvedExports[alias]

		// If this is an export of an import, reference the symbol that the import
		// was eventually resolved to. We need to do this because imports have
		// already been resolved by this point, so we can't generate a new import
		// and have that be resolved later.
		otherFileMeta := &c.fileMeta[export.sourceIndex]
		if importToBind, ok := otherFileMeta.importsToBind[export.ref]; ok {
			export.ref = importToBind.ref
			export.sourceIndex = importToBind.sourceIndex
		}

		// Exports of imports need EImportIdentifier in case they need to be re-
		// written to a property access later on
		var value ast.Expr
		if c.symbols.Get(export.ref).NamespaceAlias != nil {
			value = ast.Expr{Data: &ast.EImportIdentifier{Ref: export.ref}}

			// Imported identifiers must be assigned to a local variable to be
			// exported using an ES6 export clause. The import needs to be an
			// EImportIdentifier in case it's imported from a CommonJS module.
			if needsEntryPointES6ExportPart {
				// Generate a temporary variable
				inner := &c.symbols.Outer[sourceIndex]
				tempRef := ast.Ref{OuterIndex: sourceIndex, InnerIndex: uint32(len(*inner))}
				*inner = append(*inner, ast.Symbol{
					Kind: ast.SymbolOther,
					Name: "export_" + alias,
					Link: ast.InvalidRef,
				})

				// Stick it on the module scope so it gets renamed and minified
				generated := &file.ast.ModuleScope.Generated
				*generated = append(*generated, tempRef)

				// Create both a local variable and an export clause for that variable.
				// The local variable is initialized with the initial value of the
				// export. This isn't fully correct because it's a "dead" binding and
				// doesn't update with the "live" value as it changes. But ES6 modules
				// don't have any syntax for bare named getter functions so this is the
				// best we can do.
				//
				// These input files:
				//
				//   // entry_point.js
				//   export {foo} from './cjs-format.js'
				//
				//   // cjs-format.js
				//   Object.defineProperty(exports, 'foo', {
				//     enumerable: true,
				//     get: () => Math.random(),
				//   })
				//
				// Become this output file:
				//
				//   // cjs-format.js
				//   var require_cjs_format = __commonJS((exports) => {
				//     Object.defineProperty(exports, "foo", {
				//       enumerable: true,
				//       get: () => Math.random()
				//     });
				//   });
				//
				//   // entry_point.js
				//   const cjs_format = __toModule(require_cjs_format());
				//   const export_foo = cjs_format.foo;
				//   export {
				//     export_foo as foo
				//   };
				//
				entryPointExportStmts = append(entryPointExportStmts, ast.Stmt{Data: &ast.SLocal{
					Kind: ast.LocalConst,
					Decls: []ast.Decl{{
						Binding: ast.Binding{Data: &ast.BIdentifier{Ref: tempRef}},
						Value:   &ast.Expr{Data: &ast.EImportIdentifier{Ref: export.ref}},
					}},
				}})
				entryPointES6ExportItems = append(entryPointES6ExportItems, ast.ClauseItem{
					Name:  ast.LocRef{Ref: tempRef},
					Alias: alias,
				})
			}
		} else {
			value = ast.Expr{Data: &ast.EIdentifier{Ref: export.ref}}

			if needsEntryPointES6ExportPart {
				// Local identifiers can be exported using an export clause. This is done
				// this way instead of leaving the "export" keyword on the local declaration
				// itself both because it lets the local identifier be minified and because
				// it works transparently for re-exports across files.
				//
				// These input files:
				//
				//   // entry_point.js
				//   export * from './esm-format.js'
				//
				//   // esm-format.js
				//   export let foo = 123
				//
				// Become this output file:
				//
				//   // esm-format.js
				//   let foo = 123;
				//
				//   // entry_point.js
				//   export {
				//     foo
				//   };
				//
				entryPointES6ExportItems = append(entryPointES6ExportItems, ast.ClauseItem{
					Name:  ast.LocRef{Ref: export.ref},
					Alias: alias,
				})
			}
		}

		// Add a getter property
		properties = append(properties, ast.Property{
			Key: ast.Expr{Data: &ast.EString{Value: lexer.StringToUTF16(alias)}},
			Value: &ast.Expr{Data: &ast.EArrow{
				PreferExpr: true,
				Body:       ast.FnBody{Stmts: []ast.Stmt{{Loc: value.Loc, Data: &ast.SReturn{Value: &value}}}},
			}},
		})
		nsExportSymbolUses[export.ref] = ast.SymbolUse{CountEstimate: 1}
		if fileMeta.isEntryPoint {
			entryPointExportSymbolUses[export.ref] = ast.SymbolUse{CountEstimate: 1}
		}

		// Make sure the part that declares the export is included
		for _, partIndex := range c.files[export.sourceIndex].ast.TopLevelSymbolToParts[export.ref] {
			// Use a non-local dependency since this is likely from a different
			// file if it came in through an export star
			dep := partRef{sourceIndex: export.sourceIndex, partIndex: partIndex}
			nsExportNonLocalDependencies = append(nsExportNonLocalDependencies, dep)
			if fileMeta.isEntryPoint {
				entryPointExportNonLocalDependencies = append(entryPointExportNonLocalDependencies, dep)
			}
		}
	}

	// Prefix this part with "var exports = {}" if this isn't a CommonJS module
	declaredSymbols := []ast.DeclaredSymbol{}
	var nsExportStmts []ast.Stmt
	if !fileMeta.cjsStyleExports {
		nsExportStmts = append(nsExportStmts, ast.Stmt{Data: &ast.SLocal{Kind: ast.LocalConst, Decls: []ast.Decl{{
			Binding: ast.Binding{Data: &ast.BIdentifier{Ref: file.ast.ExportsRef}},
			Value:   &ast.Expr{Data: &ast.EObject{}},
		}}}})
		declaredSymbols = append(declaredSymbols, ast.DeclaredSymbol{
			Ref:        file.ast.ExportsRef,
			IsTopLevel: true,
		})
	}

	// "__export(exports, { foo: () => foo })"
	exportRef := ast.InvalidRef
	if len(properties) > 0 {
		runtimeFile := &c.files[runtime.SourceIndex]
		exportRef = runtimeFile.ast.ModuleScope.Members["__export"]
		nsExportStmts = append(nsExportStmts, ast.Stmt{Data: &ast.SExpr{Value: ast.Expr{Data: &ast.ECall{
			Target: ast.Expr{Data: &ast.EIdentifier{Ref: exportRef}},
			Args: []ast.Expr{
				{Data: &ast.EIdentifier{Ref: file.ast.ExportsRef}},
				{Data: &ast.EObject{
					Properties: properties,
				}},
			},
		}}}})

		// Make sure this file depends on the "__export" symbol
		for _, partIndex := range runtimeFile.ast.TopLevelSymbolToParts[exportRef] {
			dep := partRef{sourceIndex: runtime.SourceIndex, partIndex: partIndex}
			nsExportNonLocalDependencies = append(nsExportNonLocalDependencies, dep)
		}

		// Make sure the CommonJS closure, if there is one, includes "exports"
		file.ast.UsesExportsRef = true
	}

	// No need to generate a part if it'll be empty
	if len(nsExportStmts) > 0 {
		// Initialize the part that was allocated for us earlier. The information
		// here will be used after this during tree shaking.
		exportPart := &file.ast.Parts[fileMeta.nsExportPartIndex]
		*exportPart = ast.Part{
			Stmts:             nsExportStmts,
			LocalDependencies: make(map[uint32]bool),
			SymbolUses:        nsExportSymbolUses,
			DeclaredSymbols:   declaredSymbols,

			// This can be removed if nothing uses it. Except if we're a CommonJS
			// module, in which case it's always necessary.
			CanBeRemovedIfUnused: !fileMeta.cjsStyleExports,

			// Put the export definitions first before anything else gets evaluated
			IsNamespaceExport: true,

			// Make sure this is trimmed if unused even if tree shaking is disabled
			ForceTreeShaking: true,
		}
		fileMeta.partMeta[fileMeta.nsExportPartIndex].nonLocalDependencies = nsExportNonLocalDependencies

		// Pull in the "__export" symbol if it was used
		if exportRef != ast.InvalidRef {
			c.generateUseOfSymbolForInclude(exportPart, fileMeta, 1, exportRef, runtime.SourceIndex)
		}
	}

	if len(entryPointES6ExportItems) > 0 {
		entryPointExportStmts = append(entryPointExportStmts,
			ast.Stmt{Data: &ast.SExportClause{Items: entryPointES6ExportItems}})
	}

	// If we're an entry point, call the require function at the end of the
	// bundle right before bundle evaluation ends
	var cjsWrapStmt ast.Stmt
	if fileMeta.isEntryPoint && fileMeta.cjsWrap {
		switch c.options.OutputFormat {
		case config.FormatPreserve:
			// "require_foo();"
			cjsWrapStmt = ast.Stmt{Data: &ast.SExpr{Value: ast.Expr{Data: &ast.ECall{
				Target: ast.Expr{Data: &ast.EIdentifier{Ref: file.ast.WrapperRef}},
			}}}}

		case config.FormatIIFE:
			if c.options.ModuleName != "" {
				// "return require_foo();"
				cjsWrapStmt = ast.Stmt{Data: &ast.SReturn{Value: &ast.Expr{Data: &ast.ECall{
					Target: ast.Expr{Data: &ast.EIdentifier{Ref: file.ast.WrapperRef}},
				}}}}
			} else {
				// "require_foo();"
				cjsWrapStmt = ast.Stmt{Data: &ast.SExpr{Value: ast.Expr{Data: &ast.ECall{
					Target: ast.Expr{Data: &ast.EIdentifier{Ref: file.ast.WrapperRef}},
				}}}}
			}

		case config.FormatCommonJS:
			// "module.exports = require_foo();"
			cjsWrapStmt = ast.AssignStmt(
				ast.Expr{Data: &ast.EDot{
					Target: ast.Expr{Data: &ast.EIdentifier{Ref: c.unboundModuleRef}},
					Name:   "exports",
				}},
				ast.Expr{Data: &ast.ECall{
					Target: ast.Expr{Data: &ast.EIdentifier{Ref: file.ast.WrapperRef}},
				}},
			)

		case config.FormatESModule:
			// "export default require_foo();"
			cjsWrapStmt = ast.Stmt{Data: &ast.SExportDefault{Value: ast.ExprOrStmt{Expr: &ast.Expr{Data: &ast.ECall{
				Target: ast.Expr{Data: &ast.EIdentifier{Ref: file.ast.WrapperRef}},
			}}}}}
		}
	}

	if len(entryPointExportStmts) > 0 || cjsWrapStmt.Data != nil {
		// Trigger evaluation of the CommonJS wrapper
		if cjsWrapStmt.Data != nil {
			entryPointExportSymbolUses[file.ast.WrapperRef] = ast.SymbolUse{CountEstimate: 1}
			entryPointExportStmts = append(entryPointExportStmts, cjsWrapStmt)
		}

		// Add a part for this export clause
		file := &c.files[sourceIndex]
		partIndex := uint32(len(file.ast.Parts))
		fileMeta.entryPointExportPartIndex = &partIndex
		file.ast.Parts = append(file.ast.Parts, ast.Part{
			Stmts:             entryPointExportStmts,
			LocalDependencies: make(map[uint32]bool),
			SymbolUses:        entryPointExportSymbolUses,
		})
		fileMeta.partMeta = append(fileMeta.partMeta, partMeta{
			entryBits:            newBitSet(uint(len(c.entryPoints))),
			nonLocalDependencies: append([]partRef{}, entryPointExportNonLocalDependencies...),
		})
	}
}

func (c *linkerContext) matchImportsWithExportsForFile(sourceIndex uint32) {
	file := c.files[sourceIndex]

	// Sort imports for determinism. Otherwise our unit tests will randomly
	// fail sometimes when error messages are reordered.
	sortedImportRefs := make([]int, 0, len(file.ast.NamedImports))
	for ref := range file.ast.NamedImports {
		sortedImportRefs = append(sortedImportRefs, int(ref.InnerIndex))
	}
	sort.Ints(sortedImportRefs)

	// Pair imports with their matching exports
	for _, innerIndex := range sortedImportRefs {
		importRef := ast.Ref{OuterIndex: sourceIndex, InnerIndex: uint32(innerIndex)}
		tracker := importTracker{sourceIndex, importRef}
		cycleDetector := tracker
		checkCycle := false
		for {
			// Make sure we avoid infinite loops trying to resolve cycles:
			//
			//   // foo.js
			//   export {a as b} from './foo.js'
			//   export {b as c} from './foo.js'
			//   export {c as a} from './foo.js'
			//
			if !checkCycle {
				checkCycle = true
			} else {
				checkCycle = false
				if cycleDetector == tracker {
					source := c.sources[sourceIndex]
					namedImport := c.files[sourceIndex].ast.NamedImports[importRef]
					c.addRangeError(source, lexer.RangeOfIdentifier(source, namedImport.AliasLoc),
						fmt.Sprintf("Detected cycle while resolving import %q", namedImport.Alias))
					break
				}
				cycleDetector, _ = c.advanceImportTracker(cycleDetector)
			}

			// Resolve the import by one step
			nextTracker, status := c.advanceImportTracker(tracker)
			switch status {
			case importCommonJS, importCommonJSWithoutExports, importExternal:
				if status == importExternal && c.options.OutputFormat.KeepES6ImportExportSyntax() {
					// Imports from external modules should not be converted to CommonJS
					// if the output format preserves the original ES6 import statements
					break
				}

				// If it's a CommonJS or external file, rewrite the import to a
				// property access. Don't do this if the namespace reference is invalid
				// though. This is the case for star imports, where the import is the
				// namespace.
				namedImport := c.files[tracker.sourceIndex].ast.NamedImports[tracker.importRef]
				if namedImport.NamespaceRef != ast.InvalidRef {
					c.symbols.Get(importRef).NamespaceAlias = &ast.NamespaceAlias{
						NamespaceRef: namedImport.NamespaceRef,
						Alias:        namedImport.Alias,
					}
				}

				// Warn about importing from a file that is known to not have any exports
				if status == importCommonJSWithoutExports {
					source := c.sources[tracker.sourceIndex]
					c.log.AddRangeWarning(&source, lexer.RangeOfIdentifier(source, namedImport.AliasLoc),
						fmt.Sprintf("Import %q will always be undefined", namedImport.Alias))
				}

			case importNoMatch:
				symbol := c.symbols.Get(tracker.importRef)
				if symbol.ImportItemStatus == ast.ImportItemGenerated {
					symbol.ImportItemStatus = ast.ImportItemMissing
				} else {
					// Report mismatched imports and exports
					source := c.sources[tracker.sourceIndex]
					namedImport := c.files[tracker.sourceIndex].ast.NamedImports[tracker.importRef]
					c.addRangeError(source, lexer.RangeOfIdentifier(source, namedImport.AliasLoc),
						fmt.Sprintf("No matching export for import %q", namedImport.Alias))
				}

			case importAmbiguous:
				source := c.sources[tracker.sourceIndex]
				namedImport := c.files[tracker.sourceIndex].ast.NamedImports[tracker.importRef]
				c.addRangeError(source, lexer.RangeOfIdentifier(source, namedImport.AliasLoc),
					fmt.Sprintf("Ambiguous import %q has multiple matching exports", namedImport.Alias))

			case importProbablyTypeScriptType:
				// Omit this import from any namespace export code we generate for
				// import star statements (i.e. "import * as ns from 'path'")
				c.fileMeta[sourceIndex].isProbablyTypeScriptType[importRef] = true

			case importFound:
				// Defer the actual binding of this import until after we generate
				// namespace export code for all files. This has to be done for all
				// import-to-export matches, not just the initial import to the final
				// export, since all imports and re-exports must be merged together
				// for correctness.
				fileMeta := &c.fileMeta[sourceIndex]
				fileMeta.importsToBind[importRef] = importToBind{
					sourceIndex: nextTracker.sourceIndex,
					ref:         nextTracker.importRef,
				}

				// If this is a re-export of another import, continue for another
				// iteration of the loop to resolve that import as well
				if _, ok := c.files[nextTracker.sourceIndex].ast.NamedImports[nextTracker.importRef]; ok {
					tracker = nextTracker
					continue
				}

			default:
				panic("Internal error")
			}

			// Stop now if we didn't explicitly "continue" above
			break
		}
	}
}

func (c *linkerContext) isCommonJSDueToExportStar(sourceIndex uint32, visited map[uint32]bool) bool {
	// Terminate the traversal now if this file is CommonJS
	fileMeta := &c.fileMeta[sourceIndex]
	if fileMeta.cjsStyleExports {
		return true
	}

	// Avoid infinite loops due to cycles in the export star graph
	if visited[sourceIndex] {
		return false
	}
	visited[sourceIndex] = true

	// Scan over the export star graph
	file := &c.files[sourceIndex]
	for _, importRecordIndex := range file.ast.ExportStarImportRecords {
		record := &file.ast.ImportRecords[importRecordIndex]

		// This file is CommonJS if the exported imports are from a file that is
		// either CommonJS directly or transitively by itself having an export star
		// from a CommonJS file.
		if record.SourceIndex == nil || (*record.SourceIndex != sourceIndex &&
			c.isCommonJSDueToExportStar(*record.SourceIndex, visited)) {
			fileMeta.cjsStyleExports = true
			return true
		}
	}

	return false
}

func (c *linkerContext) addExportsForExportStar(
	resolvedExports map[string]exportData,
	sourceIndex uint32,
	topLevelPathLoc *ast.Loc,
	visited map[uint32]bool,
) {
	// Avoid infinite loops due to cycles in the export star graph
	if visited[sourceIndex] {
		return
	}
	visited[sourceIndex] = true
	file := &c.files[sourceIndex]
	fileMeta := &c.fileMeta[sourceIndex]

	for _, importRecordIndex := range file.ast.ExportStarImportRecords {
		record := &file.ast.ImportRecords[importRecordIndex]
		if record.SourceIndex == nil {
			// This will be resolved at run time instead
			continue
		}
		otherSourceIndex := *record.SourceIndex

		// We need a location for error messages, but it must be in the top-level
		// file, not in any nested file. This will be passed to nested files.
		pathLoc := record.Loc
		if topLevelPathLoc != nil {
			pathLoc = *topLevelPathLoc
		}

		// Export stars from a CommonJS module don't work because they can't be
		// statically discovered. Just silently ignore them in this case.
		//
		// We could attempt to check whether the imported file still has ES6
		// exports even though it still uses CommonJS features. However, when
		// doing this we'd also have to rewrite any imports of these export star
		// re-exports as property accesses off of a generated require() call.
		if c.fileMeta[otherSourceIndex].cjsStyleExports {
			// This will be resolved at run time instead
			continue
		}

		// Accumulate this file's exports
		for name, ref := range c.files[otherSourceIndex].ast.NamedExports {
			// ES6 export star statements ignore exports named "default"
			if name == "default" {
				continue
			}

			existing, ok := resolvedExports[name]

			// Don't overwrite real exports, which shadow export stars
			if ok && !existing.isFromExportStar {
				continue
			}

			if !ok {
				// Initialize the re-export
				resolvedExports[name] = exportData{
					ref:              ref,
					sourceIndex:      otherSourceIndex,
					pathLoc:          &pathLoc,
					isFromExportStar: true,
				}

				// Make sure the symbol is marked as imported so that code splitting
				// imports it correctly if it ends up being shared with another chunk
				fileMeta.importsToBind[ref] = importToBind{
					ref:         ref,
					sourceIndex: otherSourceIndex,
				}
			} else if existing.sourceIndex != otherSourceIndex {
				// Two different re-exports colliding makes it ambiguous
				existing.isAmbiguous = true
				resolvedExports[name] = existing
			}
		}

		// Search further through this file's export stars
		c.addExportsForExportStar(resolvedExports, otherSourceIndex, &pathLoc, visited)
	}
}

type importTracker struct {
	sourceIndex uint32
	importRef   ast.Ref
}

type importStatus uint8

const (
	// The imported file has no matching export
	importNoMatch importStatus = iota

	// The imported file has a matching export
	importFound

	// The imported file is CommonJS and has unknown exports
	importCommonJS

	// The imported file was treated as CommonJS but is known to have no exports
	importCommonJSWithoutExports

	// The imported file is external and has unknown exports
	importExternal

	// There are multiple re-exports with the same name due to "export * from"
	importAmbiguous

	// This is a missing re-export in a TypeScript file, so it's probably a type
	importProbablyTypeScriptType
)

func (c *linkerContext) advanceImportTracker(tracker importTracker) (importTracker, importStatus) {
	file := &c.files[tracker.sourceIndex]
	namedImport := file.ast.NamedImports[tracker.importRef]

	// Is this an external file?
	record := &file.ast.ImportRecords[namedImport.ImportRecordIndex]
	if record.SourceIndex == nil {
		return importTracker{}, importExternal
	}

	// Is this a CommonJS file?
	otherFileMeta := &c.fileMeta[*record.SourceIndex]
	if otherFileMeta.cjsStyleExports {
		otherFile := &c.files[*record.SourceIndex]
		if !otherFile.ast.UsesCommonJSExports() && !otherFile.ast.HasES6Syntax() {
			return importTracker{}, importCommonJSWithoutExports
		}
		return importTracker{}, importCommonJS
	}

	// Match this import up with an export from the imported file
	if matchingExport, ok := otherFileMeta.resolvedExports[namedImport.Alias]; ok {
		if matchingExport.isAmbiguous {
			return importTracker{}, importAmbiguous
		}

		// Check to see if this is a re-export of another import
		return importTracker{matchingExport.sourceIndex, matchingExport.ref}, importFound
	}

	// Missing re-exports in TypeScript files are indistinguishable from types
	if file.ast.WasTypeScript && namedImport.IsExported {
		return importTracker{}, importProbablyTypeScriptType
	}

	return importTracker{}, importNoMatch
}

func (c *linkerContext) markPartsReachableFromEntryPoints() {
	// Allocate bit sets
	bitCount := uint(len(c.entryPoints))
	for _, sourceIndex := range c.reachableFiles {
		fileMeta := &c.fileMeta[sourceIndex]
		fileMeta.entryBits = newBitSet(bitCount)
		for partIndex := range fileMeta.partMeta {
			fileMeta.partMeta[partIndex].entryBits = newBitSet(bitCount)
		}

		// If this is a CommonJS file, we're going to need to generate a wrapper
		// for the CommonJS closure. That will end up looking something like this:
		//
		//   var require_foo = __commonJS((exports, module) => {
		//     ...
		//   });
		//
		// However, that generation is special-cased for various reasons and is
		// done later on. Still, we're going to need to ensure that this file
		// both depends on the "__commonJS" symbol and declares the "require_foo"
		// symbol. Instead of special-casing this during the reachablity analysis
		// below, we just append a dummy part to the end of the file with these
		// dependencies and let the general-purpose reachablity analysis take care
		// of it.
		if fileMeta.cjsWrap {
			file := &c.files[sourceIndex]
			runtimeFile := &c.files[runtime.SourceIndex]
			commonJSRef := runtimeFile.ast.NamedExports["__commonJS"]
			commonJSParts := runtimeFile.ast.TopLevelSymbolToParts[commonJSRef]

			// Generate the dummy part
			partIndex := uint32(len(file.ast.Parts))
			fileMeta.cjsWrapperPartIndex = &partIndex
			file.ast.TopLevelSymbolToParts[file.ast.WrapperRef] = []uint32{partIndex}
			file.ast.Parts = append(file.ast.Parts, ast.Part{
				LocalDependencies: make(map[uint32]bool),
				SymbolUses: map[ast.Ref]ast.SymbolUse{
					file.ast.WrapperRef: {CountEstimate: 1},
					commonJSRef:         {CountEstimate: 1},
				},
				DeclaredSymbols: []ast.DeclaredSymbol{
					{Ref: file.ast.ExportsRef, IsTopLevel: true},
					{Ref: file.ast.ModuleRef, IsTopLevel: true},
					{Ref: file.ast.WrapperRef, IsTopLevel: true},
				},
			})
			nonLocalDependencies := make([]partRef, len(commonJSParts))
			for i, partIndex := range commonJSParts {
				nonLocalDependencies[i] = partRef{sourceIndex: runtime.SourceIndex, partIndex: partIndex}
			}
			fileMeta.partMeta = append(fileMeta.partMeta, partMeta{
				entryBits:            newBitSet(uint(len(c.entryPoints))),
				nonLocalDependencies: nonLocalDependencies,
			})
			fileMeta.importsToBind[commonJSRef] = importToBind{
				ref:         commonJSRef,
				sourceIndex: runtime.SourceIndex,
			}
		}
	}

	// Each entry point marks all files reachable from itself
	for i, entryPoint := range c.entryPoints {
		c.includeFile(entryPoint, uint(i), 0)
	}

	c.handleCrossChunkAssignments()
}

// Code splitting may cause an assignment to a local variable to end up in a
// separate chunk from the variable. This is bad because that will generate
// an assignment to an import, which will fail. Make sure these parts end up
// in the same chunk in these cases.
func (c *linkerContext) handleCrossChunkAssignments() {
	if len(c.entryPoints) < 2 {
		// No need to do this if there cannot be cross-chunk assignments
		return
	}
	neverReachedEntryBits := newBitSet(uint(len(c.entryPoints)))

	for _, sourceIndex := range c.reachableFiles {
		file := &c.files[sourceIndex]
		partMeta := c.fileMeta[sourceIndex].partMeta

		// Initialize a union-find data structure to merge parts together
		unionFind := make([]uint32, len(file.ast.Parts))
		for partIndex := range file.ast.Parts {
			unionFind[partIndex] = uint32(partIndex)
		}

		// Look up a merged label
		var find func(uint32) uint32
		find = func(label uint32) uint32 {
			next := unionFind[label]
			if next != label {
				next = find(next)
				unionFind[label] = next
			}
			return next
		}

		for partIndex, part := range file.ast.Parts {
			// Ignore this part if it's dead code
			if partMeta[partIndex].entryBits.equals(neverReachedEntryBits) {
				continue
			}

			// If this part assigns to a local variable, make sure the parts for the
			// variable's declaration are in the same chunk as this part
			for ref, use := range part.SymbolUses {
				if use.IsAssigned {
					if otherParts, ok := file.ast.TopLevelSymbolToParts[ref]; ok {
						for _, otherPartIndex := range otherParts {
							// Union the two labels together
							a := find(uint32(partIndex))
							b := find(otherPartIndex)
							unionFind[a] = b
							partMeta[b].entryBits.bitwiseOrWith(partMeta[a].entryBits)
						}
					}
				}
			}
		}

		// Update the entry bits of parts that were merged
		for partIndex := range file.ast.Parts {
			label := find(uint32(partIndex))
			if label != uint32(partIndex) {
				partMeta[partIndex].entryBits.copyFrom(partMeta[label].entryBits)
			}
		}
	}
}

func (c *linkerContext) accumulateSymbolCount(ref ast.Ref, count uint32) {
	ref = ast.FollowSymbols(c.symbols, ref)
	c.symbols.Get(ref).UseCountEstimate += count
}

func (c *linkerContext) includeFile(sourceIndex uint32, entryPointBit uint, distanceFromEntryPoint uint32) {
	fileMeta := &c.fileMeta[sourceIndex]

	// Track the minimum distance to an entry point
	if distanceFromEntryPoint < fileMeta.distanceFromEntryPoint {
		fileMeta.distanceFromEntryPoint = distanceFromEntryPoint
	}
	distanceFromEntryPoint++

	// Don't mark this file more than once
	if fileMeta.entryBits.hasBit(entryPointBit) {
		return
	}
	fileMeta.entryBits.setBit(entryPointBit)

	// Accumulate symbol usage counts
	file := &c.files[sourceIndex]
	if file.ast.UsesExportsRef {
		c.accumulateSymbolCount(file.ast.ExportsRef, 1)
	}
	if file.ast.UsesModuleRef {
		c.accumulateSymbolCount(file.ast.ModuleRef, 1)
	}

	for partIndex, part := range file.ast.Parts {
		canBeRemovedIfUnused := part.CanBeRemovedIfUnused

		// Don't include the entry point part if we're not the entry point
		if fileMeta.entryPointExportPartIndex != nil && uint32(partIndex) == *fileMeta.entryPointExportPartIndex &&
			sourceIndex != c.entryPoints[entryPointBit] {
			continue
		}

		// Also include any statement-level imports
		for _, importRecordIndex := range part.ImportRecordIndices {
			record := &file.ast.ImportRecords[importRecordIndex]
			if record.Kind != ast.ImportStmt {
				continue
			}

			if record.SourceIndex != nil {
				otherSourceIndex := *record.SourceIndex

				// Don't include this module for its side effects if it can be
				// considered to have no side effects
				if c.files[otherSourceIndex].ignoreIfUnused {
					continue
				}

				// Otherwise, include this module for its side effects
				c.includeFile(otherSourceIndex, entryPointBit, distanceFromEntryPoint)
			}

			// If we get here then the import was included for its side effects, so
			// we must also keep this part
			canBeRemovedIfUnused = false
		}

		// Include all parts in this file with side effects, or just include
		// everything if tree-shaking is disabled. Note that we still want to
		// perform tree-shaking on the runtime even if tree-shaking is disabled.
		if !canBeRemovedIfUnused || (!part.ForceTreeShaking && !c.options.IsBundling && sourceIndex != runtime.SourceIndex) {
			c.includePart(sourceIndex, uint32(partIndex), entryPointBit, distanceFromEntryPoint)
		}
	}

	// If this is an entry point, include all exports
	if fileMeta.isEntryPoint {
		for _, alias := range fileMeta.sortedAndFilteredExportAliases {
			export := fileMeta.resolvedExports[alias]
			targetSourceIndex := export.sourceIndex
			targetRef := export.ref

			// If this is an import, then target what the import points to
			if importToBind, ok := c.fileMeta[targetSourceIndex].importsToBind[targetRef]; ok {
				targetSourceIndex = importToBind.sourceIndex
				targetRef = importToBind.ref
			}

			// Pull in all declarations of this symbol
			for _, partIndex := range c.files[targetSourceIndex].ast.TopLevelSymbolToParts[targetRef] {
				c.includePart(targetSourceIndex, partIndex, entryPointBit, distanceFromEntryPoint)
			}
		}
	}
}

func (c *linkerContext) includePartsForRuntimeSymbol(
	part *ast.Part, fileMeta *fileMeta, useCount uint32,
	name string, entryPointBit uint, distanceFromEntryPoint uint32,
) {
	if useCount > 0 {
		file := &c.files[runtime.SourceIndex]
		ref := file.ast.NamedExports[name]

		// Depend on the symbol from the runtime
		c.generateUseOfSymbolForInclude(part, fileMeta, useCount, ref, runtime.SourceIndex)

		// Since this part was included, also include the parts from the runtime
		// that declare this symbol
		for _, partIndex := range file.ast.TopLevelSymbolToParts[ref] {
			c.includePart(runtime.SourceIndex, partIndex, entryPointBit, distanceFromEntryPoint)
		}
	}
}

func (c *linkerContext) generateUseOfSymbolForInclude(
	part *ast.Part, fileMeta *fileMeta, useCount uint32,
	ref ast.Ref, otherSourceIndex uint32,
) {
	use := part.SymbolUses[ref]
	use.CountEstimate += useCount
	part.SymbolUses[ref] = use
	fileMeta.importsToBind[ref] = importToBind{
		sourceIndex: otherSourceIndex,
		ref:         ref,
	}
}

func (c *linkerContext) isExternalDynamicImport(record *ast.ImportRecord) bool {
	return record.Kind == ast.ImportDynamic && c.fileMeta[*record.SourceIndex].isEntryPoint
}

func (c *linkerContext) includePart(sourceIndex uint32, partIndex uint32, entryPointBit uint, distanceFromEntryPoint uint32) {
	partMeta := &c.fileMeta[sourceIndex].partMeta[partIndex]

	// Don't mark this part more than once
	if partMeta.entryBits.hasBit(entryPointBit) {
		return
	}
	partMeta.entryBits.setBit(entryPointBit)

	file := &c.files[sourceIndex]
	part := &file.ast.Parts[partIndex]
	fileMeta := &c.fileMeta[sourceIndex]

	// Include the file containing this part
	c.includeFile(sourceIndex, entryPointBit, distanceFromEntryPoint)

	// Also include any local dependencies
	for otherPartIndex := range part.LocalDependencies {
		c.includePart(sourceIndex, otherPartIndex, entryPointBit, distanceFromEntryPoint)
	}

	// Also include any non-local dependencies
	for _, nonLocalDependency := range partMeta.nonLocalDependencies {
		c.includePart(nonLocalDependency.sourceIndex, nonLocalDependency.partIndex, entryPointBit, distanceFromEntryPoint)
	}

	// Also include any require() imports
	toModuleUses := uint32(0)
	for _, importRecordIndex := range part.ImportRecordIndices {
		record := &file.ast.ImportRecords[importRecordIndex]

		// Don't follow external imports (this includes import() expressions)
		if record.SourceIndex == nil || c.isExternalDynamicImport(record) {
			// This is an external import, so it needs the "__toModule" wrapper as
			// long as it's not a bare "require()"
			if record.Kind != ast.ImportRequire && !c.options.OutputFormat.KeepES6ImportExportSyntax() {
				record.WrapWithToModule = true
				toModuleUses++
			}
			continue
		}

		otherSourceIndex := *record.SourceIndex
		if record.Kind == ast.ImportStmt && !c.fileMeta[otherSourceIndex].cjsStyleExports {
			// Skip this since it's not a require() import
			continue
		}

		// This is a require() import
		c.includeFile(otherSourceIndex, entryPointBit, distanceFromEntryPoint)

		// Depend on the automatically-generated require wrapper symbol
		wrapperRef := c.files[otherSourceIndex].ast.WrapperRef
		c.generateUseOfSymbolForInclude(part, fileMeta, 1, wrapperRef, otherSourceIndex)

		// This is an ES6 import of a CommonJS module, so it needs the
		// "__toModule" wrapper as long as it's not a bare "require()"
		if record.Kind != ast.ImportRequire {
			record.WrapWithToModule = true
			toModuleUses++
		}
	}

	// If there's an ES6 import of a non-ES6 module, then we're going to need the
	// "__toModule" symbol from the runtime to wrap the result of "require()"
	c.includePartsForRuntimeSymbol(part, fileMeta, toModuleUses, "__toModule", entryPointBit, distanceFromEntryPoint)

	// If there's an ES6 export star statement of a non-ES6 module, then we're
	// going to need the "__exportStar" symbol from the runtime
	exportStarUses := uint32(0)
	for _, importRecordIndex := range file.ast.ExportStarImportRecords {
		record := &file.ast.ImportRecords[importRecordIndex]

		// Is this export star evaluated at run time?
		if record.SourceIndex == nil || (*record.SourceIndex != sourceIndex && c.fileMeta[*record.SourceIndex].cjsStyleExports) {
			record.IsExportStarRunTimeEval = true
			file.ast.UsesExportsRef = true
			exportStarUses++
		}
	}
	c.includePartsForRuntimeSymbol(part, fileMeta, exportStarUses, "__exportStar", entryPointBit, distanceFromEntryPoint)

	// Accumulate symbol usage counts. Do this last to also include
	// automatically-generated usages from the code above.
	for ref, use := range part.SymbolUses {
		c.accumulateSymbolCount(ref, use.CountEstimate)
	}
	for _, declared := range part.DeclaredSymbols {
		// Make sure to also count the declaration in addition to the uses
		c.accumulateSymbolCount(declared.Ref, 1)
	}
}

func (c *linkerContext) computeChunks() []chunkMeta {
	chunks := make(map[string]chunkMeta)
	neverReachedKey := string(newBitSet(uint(len(c.entryPoints))).entries)

	// Compute entry point names
	for i, entryPoint := range c.entryPoints {
		var chunkRelPath string
		if c.options.AbsOutputFile != "" && c.fileMeta[entryPoint].isEntryPoint {
			chunkRelPath = c.fs.Base(c.options.AbsOutputFile)
		} else {
			source := c.sources[entryPoint]
			if !source.KeyPath.IsAbsolute {
				chunkRelPath = source.IdentifierName
			} else if relPath, ok := c.fs.Rel(c.lcaAbsPath, source.KeyPath.Text); ok {
				chunkRelPath = relPath
			} else {
				chunkRelPath = c.fs.Base(source.KeyPath.Text)
			}

			// Swap the extension for ".js"
			ext := c.fs.Ext(chunkRelPath)
			if ext != ".js" {
				chunkRelPath = chunkRelPath[:len(chunkRelPath)-len(ext)] + ".js"
			}
		}

		// Always use cross-platform path separators to avoid problems with Windows
		c.fileMeta[entryPoint].entryPointRelPath = strings.ReplaceAll(chunkRelPath, "\\", "/")

		// Create a chunk for the entry point here to ensure that the chunk is
		// always generated even if the resulting file is empty
		entryBits := newBitSet(uint(len(c.entryPoints)))
		entryBits.setBit(uint(i))
		chunks[string(entryBits.entries)] = chunkMeta{
			entryBits:             entryBits,
			isEntryPoint:          true,
			sourceIndex:           entryPoint,
			entryPointBit:         uint(i),
			relPath:               chunkRelPath,
			filesWithPartsInChunk: make(map[uint32]bool),
		}
	}

	// Figure out which files are in which chunk
	for _, sourceIndex := range c.reachableFiles {
		for _, partMeta := range c.fileMeta[sourceIndex].partMeta {
			key := string(partMeta.entryBits.entries)
			if key == neverReachedKey {
				// Ignore this part if it was never reached
				continue
			}
			chunk, ok := chunks[key]
			if !ok {
				// Initialize the chunk for the first time
				isMultiPart := false
				for i, entryPoint := range c.entryPoints {
					if partMeta.entryBits.hasBit(uint(i)) {
						if chunk.relPath != "" {
							chunk.relPath += "\x00"
							isMultiPart = true
						}
						chunk.relPath += c.fileMeta[entryPoint].entryPointRelPath
					}
				}

				// Avoid really long automatically-generated chunk names
				if isMultiPart {
					// Always hash lowercase with forward slashes to avoid OS-specific
					// path problems, specifically on Windows
					bytes := []byte(lowerCaseAbsPathForWindows(chunk.relPath))
					hashBytes := sha1.Sum(bytes)
					hash := base64.URLEncoding.EncodeToString(hashBytes[:])[:8]
					chunk.relPath = "chunk." + hash + ".js"
				}

				chunk.entryBits = partMeta.entryBits
				chunk.filesWithPartsInChunk = make(map[uint32]bool)
				chunks[key] = chunk
			}
			chunk.filesWithPartsInChunk[uint32(sourceIndex)] = true
		}
	}

	// Sort the chunks for determinism. This mostly doesn't matter because each
	// chunk is a separate file, but it matters for error messages in tests since
	// tests stop on the first output mismatch.
	sortedKeys := make([]string, 0, len(chunks))
	for key := range chunks {
		sortedKeys = append(sortedKeys, key)
	}
	sort.Strings(sortedKeys)
	sortedChunks := make([]chunkMeta, len(chunks))
	for i, key := range sortedKeys {
		sortedChunks[i] = chunks[key]
	}
	return sortedChunks
}

type chunkOrder struct {
	sourceIndex uint32
	distance    uint32
	path        ast.Path
}

// This type is just so we can use Go's native sort function
type chunkOrderArray []chunkOrder

func (a chunkOrderArray) Len() int          { return len(a) }
func (a chunkOrderArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }

func (a chunkOrderArray) Less(i int, j int) bool {
	return a[i].distance < a[j].distance || (a[i].distance == a[j].distance && a[i].path.ComesBeforeInSortedOrder(a[j].path))
}

func (c *linkerContext) chunkFileOrder(chunk chunkMeta) []uint32 {
	sorted := make(chunkOrderArray, 0, len(chunk.filesWithPartsInChunk))

	// Attach information to the files for use with sorting
	for sourceIndex := range chunk.filesWithPartsInChunk {
		sorted = append(sorted, chunkOrder{
			sourceIndex: sourceIndex,
			distance:    c.fileMeta[sourceIndex].distanceFromEntryPoint,
			path:        c.sources[sourceIndex].KeyPath,
		})
	}

	// Sort so files closest to an entry point come first. If two files are
	// equidistant to an entry point, then break the tie by sorting on the
	// absolute path.
	sort.Sort(sorted)

	visited := make(map[uint32]bool)
	prefixOrder := []uint32{}
	suffixOrder := []uint32{}

	// Traverse the graph using this stable order and linearize the files with
	// dependencies before dependents
	var visit func(uint32)
	visit = func(sourceIndex uint32) {
		if visited[sourceIndex] {
			return
		}

		visited[sourceIndex] = true
		file := &c.files[sourceIndex]
		fileMeta := &c.fileMeta[sourceIndex]
		isFileInThisChunk := chunk.entryBits.equals(fileMeta.entryBits)

		for partIndex, part := range file.ast.Parts {
			isPartInThisChunk := chunk.entryBits.equals(fileMeta.partMeta[partIndex].entryBits)
			if isPartInThisChunk {
				isFileInThisChunk = true
			}

			// Also traverse any files imported by this part
			for _, importRecordIndex := range part.ImportRecordIndices {
				record := &file.ast.ImportRecords[importRecordIndex]
				if record.SourceIndex != nil && (record.Kind == ast.ImportStmt || isPartInThisChunk) {
					if c.isExternalDynamicImport(record) {
						// Don't follow import() dependencies
						continue
					}
					visit(*record.SourceIndex)
				}
			}
		}

		// Always put all CommonJS wrappers before any other non-runtime code to
		// deal with cycles in the module graph. CommonJS wrapper declarations
		// aren't hoisted so starting to evaluate them before they are all declared
		// could end up with one being used before being declared. These wrappers
		// don't have side effects so an easy fix is to just declare them all
		// before starting to evaluate them.
		if isFileInThisChunk {
			if sourceIndex == runtime.SourceIndex || fileMeta.cjsWrap {
				prefixOrder = append(prefixOrder, sourceIndex)
			} else {
				suffixOrder = append(suffixOrder, sourceIndex)
			}
		}
	}

	// Always put the runtime code first before anything else
	visit(runtime.SourceIndex)
	for _, data := range sorted {
		visit(data.sourceIndex)
	}
	return append(prefixOrder, suffixOrder...)
}

func (c *linkerContext) shouldRemoveImportExportStmt(
	sourceIndex uint32,
	stmtList *stmtList,
	partStmts []ast.Stmt,
	loc ast.Loc,
	namespaceRef ast.Ref,
	importRecordIndex uint32,
) bool {
	// Is this an import from another module inside this bundle?
	record := &c.files[sourceIndex].ast.ImportRecords[importRecordIndex]
	if record.SourceIndex != nil {
		if !c.fileMeta[*record.SourceIndex].cjsStyleExports {
			// Remove the statement entirely if this is not a CommonJS module
			return true
		}
	} else if c.options.OutputFormat.KeepES6ImportExportSyntax() {
		// If this is an external module and the output format allows ES6
		// import/export syntax, then just keep the statement
		return false
	}

	// We don't need a call to "require()" if this is a self-import inside a
	// CommonJS-style module, since we can just reference the exports directly.
	if c.fileMeta[sourceIndex].cjsStyleExports &&
		ast.FollowSymbols(c.symbols, namespaceRef) == c.files[sourceIndex].ast.ExportsRef {
		return true
	}

	// Replace the statement with a call to "require()"
	stmtList.prefixStmts = append(stmtList.prefixStmts, ast.Stmt{
		Loc: loc,
		Data: &ast.SLocal{Kind: ast.LocalConst, Decls: []ast.Decl{{
			Binding: ast.Binding{Loc: loc, Data: &ast.BIdentifier{Ref: namespaceRef}},
			Value:   &ast.Expr{Loc: record.Loc, Data: &ast.ERequire{ImportRecordIndex: importRecordIndex}},
		}}},
	})
	return true
}

func (c *linkerContext) convertStmtsForChunk(sourceIndex uint32, stmtList *stmtList, partStmts []ast.Stmt) {
	shouldStripExports := c.options.IsBundling || sourceIndex == runtime.SourceIndex
	shouldExtractES6StmtsForCJSWrap := c.fileMeta[sourceIndex].cjsWrap

	for _, stmt := range partStmts {
		switch s := stmt.Data.(type) {
		case *ast.SImport:
			// "import * as ns from 'path'"
			// "import {foo} from 'path'"
			if c.shouldRemoveImportExportStmt(sourceIndex, stmtList, partStmts, stmt.Loc, s.NamespaceRef, s.ImportRecordIndex) {
				continue
			}

			// Make sure these don't end up in a CommonJS wrapper
			if shouldExtractES6StmtsForCJSWrap {
				stmtList.es6StmtsForCJSWrap = append(stmtList.es6StmtsForCJSWrap, stmt)
				continue
			}

		case *ast.SExportStar:
			if s.Alias == nil {
				// "export * from 'path'"
				if shouldStripExports {
					record := &c.files[sourceIndex].ast.ImportRecords[s.ImportRecordIndex]

					// Is this export star evaluated at run time?
					if record.SourceIndex == nil && c.options.OutputFormat.KeepES6ImportExportSyntax() {
						// Turn this statement into "import * as ns from 'path'"
						stmt.Data = &ast.SImport{
							NamespaceRef:      s.NamespaceRef,
							StarNameLoc:       &stmt.Loc,
							ImportRecordIndex: s.ImportRecordIndex,
						}

						// Prefix this module with "__exportStar(exports, ns)"
						exportStarRef := c.files[runtime.SourceIndex].ast.ModuleScope.Members["__exportStar"]
						stmtList.prefixStmts = append(stmtList.prefixStmts, ast.Stmt{
							Loc: stmt.Loc,
							Data: &ast.SExpr{Value: ast.Expr{Loc: stmt.Loc, Data: &ast.ECall{
								Target: ast.Expr{Loc: stmt.Loc, Data: &ast.EIdentifier{Ref: exportStarRef}},
								Args: []ast.Expr{
									{Loc: stmt.Loc, Data: &ast.EIdentifier{Ref: c.files[sourceIndex].ast.ExportsRef}},
									{Loc: stmt.Loc, Data: &ast.EIdentifier{Ref: s.NamespaceRef}},
								},
							}}},
						})

						// Make sure these don't end up in a CommonJS wrapper
						if shouldExtractES6StmtsForCJSWrap {
							stmtList.es6StmtsForCJSWrap = append(stmtList.es6StmtsForCJSWrap, stmt)
							continue
						}
					} else {
						if record.IsExportStarRunTimeEval {
							// Prefix this module with "__exportStar(exports, require(path))"
							exportStarRef := c.files[runtime.SourceIndex].ast.ModuleScope.Members["__exportStar"]
							stmtList.prefixStmts = append(stmtList.prefixStmts, ast.Stmt{
								Loc: stmt.Loc,
								Data: &ast.SExpr{Value: ast.Expr{Loc: stmt.Loc, Data: &ast.ECall{
									Target: ast.Expr{Loc: stmt.Loc, Data: &ast.EIdentifier{Ref: exportStarRef}},
									Args: []ast.Expr{
										{Loc: stmt.Loc, Data: &ast.EIdentifier{Ref: c.files[sourceIndex].ast.ExportsRef}},
										{Loc: record.Loc, Data: &ast.ERequire{ImportRecordIndex: s.ImportRecordIndex}},
									},
								}}},
							})
						}

						// Remove the export star statement
						continue
					}
				}
			} else {
				// "export * as ns from 'path'"
				if c.shouldRemoveImportExportStmt(sourceIndex, stmtList, partStmts, stmt.Loc, s.NamespaceRef, s.ImportRecordIndex) {
					continue
				}

				if shouldStripExports {
					// Turn this statement into "import * as ns from 'path'"
					stmt.Data = &ast.SImport{
						NamespaceRef:      s.NamespaceRef,
						StarNameLoc:       &s.Alias.Loc,
						ImportRecordIndex: s.ImportRecordIndex,
					}
				}

				// Make sure these don't end up in a CommonJS wrapper
				if shouldExtractES6StmtsForCJSWrap {
					stmtList.es6StmtsForCJSWrap = append(stmtList.es6StmtsForCJSWrap, stmt)
					continue
				}
			}

		case *ast.SExportFrom:
			// "export {foo} from 'path'"
			if c.shouldRemoveImportExportStmt(sourceIndex, stmtList, partStmts, stmt.Loc, s.NamespaceRef, s.ImportRecordIndex) {
				continue
			}

			if shouldStripExports {
				// Turn this statement into "import {foo} from 'path'"
				for i, item := range s.Items {
					s.Items[i].Alias = item.OriginalName
				}
				stmt.Data = &ast.SImport{
					NamespaceRef:      s.NamespaceRef,
					Items:             &s.Items,
					ImportRecordIndex: s.ImportRecordIndex,
					IsSingleLine:      s.IsSingleLine,
				}
			}

			// Make sure these don't end up in a CommonJS wrapper
			if shouldExtractES6StmtsForCJSWrap {
				stmtList.es6StmtsForCJSWrap = append(stmtList.es6StmtsForCJSWrap, stmt)
				continue
			}

		case *ast.SExportClause:
			if shouldStripExports {
				// Remove export statements entirely
				continue
			}

			// Make sure these don't end up in a CommonJS wrapper
			if shouldExtractES6StmtsForCJSWrap {
				stmtList.es6StmtsForCJSWrap = append(stmtList.es6StmtsForCJSWrap, stmt)
				continue
			}

		case *ast.SFunction:
			// Strip the "export" keyword while bundling
			if shouldStripExports && s.IsExport {
				// Be careful to not modify the original statement
				clone := *s
				clone.IsExport = false
				stmt.Data = &clone
			}

		case *ast.SClass:
			// Strip the "export" keyword while bundling
			if shouldStripExports && s.IsExport {
				// Be careful to not modify the original statement
				clone := *s
				clone.IsExport = false
				stmt.Data = &clone
			}

		case *ast.SLocal:
			// Strip the "export" keyword while bundling
			if shouldStripExports && s.IsExport {
				// Be careful to not modify the original statement
				clone := *s
				clone.IsExport = false
				stmt.Data = &clone
			}

		case *ast.SExportDefault:
			// If we're bundling, convert "export default" into a normal declaration
			if shouldStripExports {
				if s.Value.Expr != nil {
					// "export default foo;" => "var default = foo;"
					stmt = ast.Stmt{Loc: stmt.Loc, Data: &ast.SLocal{Decls: []ast.Decl{
						{Binding: ast.Binding{Loc: s.DefaultName.Loc, Data: &ast.BIdentifier{Ref: s.DefaultName.Ref}}, Value: s.Value.Expr},
					}}}
				} else {
					switch s2 := s.Value.Stmt.Data.(type) {
					case *ast.SFunction:
						// "export default function() {}" => "function default() {}"
						// "export default function foo() {}" => "function foo() {}"

						// Be careful to not modify the original statement
						s2 = &ast.SFunction{Fn: s2.Fn}
						s2.Fn.Name = &s.DefaultName

						stmt = ast.Stmt{Loc: s.Value.Stmt.Loc, Data: s2}

					case *ast.SClass:
						// "export default class {}" => "class default {}"
						// "export default class Foo {}" => "class Foo {}"

						// Be careful to not modify the original statement
						s2 = &ast.SClass{Class: s2.Class}
						s2.Class.Name = &s.DefaultName

						stmt = ast.Stmt{Loc: s.Value.Stmt.Loc, Data: s2}

					default:
						panic("Internal error")
					}
				}
			}
		}

		stmtList.normalStmts = append(stmtList.normalStmts, stmt)
	}
}

// "var a = 1; var b = 2;" => "var a = 1, b = 2;"
func mergeAdjacentLocalStmts(stmts []ast.Stmt) []ast.Stmt {
	if len(stmts) == 0 {
		return stmts
	}

	didMergeWithPreviousLocal := false
	end := 1

	for _, stmt := range stmts[1:] {
		// Try to merge with the previous variable statement
		if after, ok := stmt.Data.(*ast.SLocal); ok {
			if before, ok := stmts[end-1].Data.(*ast.SLocal); ok {
				// It must be the same kind of variable statement (i.e. let/var/const)
				if before.Kind == after.Kind && before.IsExport == after.IsExport {
					if didMergeWithPreviousLocal {
						// Avoid O(n^2) behavior for repeated variable declarations
						before.Decls = append(before.Decls, after.Decls...)
					} else {
						// Be careful to not modify the original statement
						didMergeWithPreviousLocal = true
						clone := *before
						clone.Decls = make([]ast.Decl, 0, len(before.Decls)+len(after.Decls))
						clone.Decls = append(clone.Decls, before.Decls...)
						clone.Decls = append(clone.Decls, after.Decls...)
						stmts[end-1].Data = &clone
					}
					continue
				}
			}
		}

		// Otherwise, append a normal statement
		didMergeWithPreviousLocal = false
		stmts[end] = stmt
		end++
	}

	return stmts[:end]
}

type stmtList struct {
	// These statements come first, and can be inside the CommonJS wrapper
	prefixStmts []ast.Stmt

	// These statements come last, and can be inside the CommonJS wrapper
	normalStmts []ast.Stmt

	// Order doesn't matter for these statements, but they must be outside any
	// CommonJS wrapper since they are top-level ES6 import/export statements
	es6StmtsForCJSWrap []ast.Stmt

	// These statements are for an entry point and come at the end of the chunk
	entryPointTail []ast.Stmt
}

func (c *linkerContext) generateCodeForFileInChunk(
	waitGroup *sync.WaitGroup,
	sourceIndex uint32,
	entryBits bitSet,
	commonJSRef ast.Ref,
	toModuleRef ast.Ref,
	result *compileResult,
) {
	file := &c.files[sourceIndex]
	fileMeta := &c.fileMeta[sourceIndex]
	needsWrapper := false
	stmtList := stmtList{}

	// Make sure the generated call to "__export(exports, ...)" comes first
	// before anything else.
	if entryBits.equals(fileMeta.partMeta[fileMeta.nsExportPartIndex].entryBits) {
		c.convertStmtsForChunk(sourceIndex, &stmtList, file.ast.Parts[fileMeta.nsExportPartIndex].Stmts)

		// Move everything to the prefix list
		stmtList.prefixStmts = append(stmtList.prefixStmts, stmtList.normalStmts...)
		stmtList.normalStmts = nil
	}

	// Add all other parts in this chunk
	for partIndex, part := range file.ast.Parts {
		if !entryBits.equals(fileMeta.partMeta[partIndex].entryBits) {
			// Skip the part if it's not in this chunk
			continue
		}

		if uint32(partIndex) == fileMeta.nsExportPartIndex {
			// Skip the generated call to "__export()" that was extracted above
			continue
		}

		// Mark if we hit the dummy part representing the CommonJS wrapper
		if fileMeta.cjsWrapperPartIndex != nil && uint32(partIndex) == *fileMeta.cjsWrapperPartIndex {
			needsWrapper = true
			continue
		}

		// Emit export statements in the entry point part verbatim
		if fileMeta.entryPointExportPartIndex != nil && uint32(partIndex) == *fileMeta.entryPointExportPartIndex {
			stmtList.entryPointTail = append(stmtList.entryPointTail, part.Stmts...)
			continue
		}

		c.convertStmtsForChunk(sourceIndex, &stmtList, part.Stmts)
	}

	// Hoist all import statements before any normal statements. ES6 imports
	// are different than CommonJS imports. All modules imported via ES6 import
	// statements are evaluated before the module doing the importing is
	// evaluated (well, except for cyclic import scenarios). We need to preserve
	// these semantics even when modules imported via ES6 import statements end
	// up being CommonJS modules.
	stmts := stmtList.normalStmts
	if len(stmtList.prefixStmts) > 0 {
		stmts = append(stmtList.prefixStmts, stmts...)
	}
	if c.options.MangleSyntax {
		stmts = mergeAdjacentLocalStmts(stmts)
	}

	// Optionally wrap all statements in a closure for CommonJS
	if needsWrapper {
		// Only include the arguments that are actually used
		args := []ast.Arg{}
		if file.ast.UsesExportsRef || file.ast.UsesModuleRef {
			args = append(args, ast.Arg{Binding: ast.Binding{Data: &ast.BIdentifier{Ref: file.ast.ExportsRef}}})
			if file.ast.UsesModuleRef {
				args = append(args, ast.Arg{Binding: ast.Binding{Data: &ast.BIdentifier{Ref: file.ast.ModuleRef}}})
			}
		}

		// "__commonJS((exports, module) => { ... })"
		value := ast.Expr{Data: &ast.ECall{
			Target: ast.Expr{Data: &ast.EIdentifier{Ref: commonJSRef}},
			Args:   []ast.Expr{{Data: &ast.EArrow{Args: args, Body: ast.FnBody{Stmts: stmts}}}},
		}}

		// "var require_foo = __commonJS((exports, module) => { ... });"
		stmts = append(stmtList.es6StmtsForCJSWrap, ast.Stmt{Data: &ast.SLocal{
			Decls: []ast.Decl{{
				Binding: ast.Binding{Data: &ast.BIdentifier{Ref: file.ast.WrapperRef}},
				Value:   &value,
			}},
		}})
	}

	// Only generate a source map if needed
	sourceForSourceMap := &c.sources[sourceIndex]
	if c.options.SourceMap == config.SourceMapNone {
		sourceForSourceMap = nil
	}

	// Indent the file if everything is wrapped in an IIFE
	indent := 0
	if c.options.OutputFormat == config.FormatIIFE {
		indent++
	}

	// Convert the AST to JavaScript code
	printOptions := printer.PrintOptions{
		Indent:              indent,
		OutputFormat:        c.options.OutputFormat,
		RemoveWhitespace:    c.options.RemoveWhitespace,
		ToModuleRef:         toModuleRef,
		ExtractComments:     c.options.IsBundling && c.options.RemoveWhitespace,
		UnsupportedFeatures: c.options.UnsupportedFeatures,
		SourceForSourceMap:  sourceForSourceMap,
		InputSourceMap:      file.ast.SourceMap,
	}
	tree := file.ast
	tree.Parts = []ast.Part{{Stmts: stmts}}
	*result = compileResult{
		PrintResult: printer.Print(tree, printOptions),
		sourceIndex: sourceIndex,
	}

	// Write this separately as the entry point tail so it can be split off
	// from the main entry point code. This is sometimes required to deal with
	// CommonJS import cycles.
	if len(stmtList.entryPointTail) > 0 {
		tree := file.ast
		tree.Parts = []ast.Part{{Stmts: stmtList.entryPointTail}}
		entryPointTail := printer.Print(tree, printOptions)
		result.entryPointTail = &entryPointTail
	}

	waitGroup.Done()
}

func (c *linkerContext) generateChunk(chunk chunkMeta) (results []OutputFile) {
	filesInChunkInOrder := c.chunkFileOrder(chunk)
	compileResults := make([]compileResult, 0, len(filesInChunkInOrder))
	runtimeMembers := c.files[runtime.SourceIndex].ast.ModuleScope.Members
	commonJSRef := ast.FollowSymbols(c.symbols, runtimeMembers["__commonJS"])
	toModuleRef := ast.FollowSymbols(c.symbols, runtimeMembers["__toModule"])

	// Generate JavaScript for each file in parallel
	waitGroup := sync.WaitGroup{}
	for _, sourceIndex := range filesInChunkInOrder {
		// Skip the runtime in test output
		if sourceIndex == runtime.SourceIndex && c.options.OmitRuntimeForTests {
			continue
		}

		// Each file may optionally contain an additional file to be copied to the
		// output directory. This is used by the "file" loader.
		if additionalFile := c.files[sourceIndex].additionalFile; additionalFile != nil {
			results = append(results, *additionalFile)
		}

		// Create a goroutine for this file
		compileResults = append(compileResults, compileResult{})
		compileResult := &compileResults[len(compileResults)-1]
		waitGroup.Add(1)
		go c.generateCodeForFileInChunk(
			&waitGroup,
			sourceIndex,
			chunk.entryBits,
			commonJSRef,
			toModuleRef,
			compileResult,
		)
	}

	// Also generate the cross-chunk binding code
	var crossChunkPrefix []byte
	var crossChunkSuffix []byte
	{
		// Indent the file if everything is wrapped in an IIFE
		indent := 0
		if c.options.OutputFormat == config.FormatIIFE {
			indent++
		}
		printOptions := printer.PrintOptions{
			Indent:           indent,
			OutputFormat:     c.options.OutputFormat,
			RemoveWhitespace: c.options.RemoveWhitespace,
		}
		crossChunkPrefix = printer.Print(ast.AST{
			ImportRecords: chunk.crossChunkImportRecords,
			Parts:         []ast.Part{{Stmts: chunk.crossChunkPrefixStmts}},
			Symbols:       c.symbols,
		}, printOptions).JS
		crossChunkSuffix = printer.Print(ast.AST{
			Parts:   []ast.Part{{Stmts: chunk.crossChunkSuffixStmts}},
			Symbols: c.symbols,
		}, printOptions).JS
	}
	waitGroup.Wait()

	j := printer.Joiner{}
	prevOffset := lineColumnOffset{}

	// Optionally strip whitespace
	indent := ""
	space := " "
	newline := "\n"
	if c.options.RemoveWhitespace {
		space = ""
		newline = ""
	}
	newlineBeforeComment := false

	if chunk.isEntryPoint {
		file := &c.files[chunk.sourceIndex]

		// Start with the hashbang if there is one
		if file.ast.Hashbang != "" {
			hashbang := file.ast.Hashbang + "\n"
			prevOffset.advanceString(hashbang)
			j.AddString(hashbang)
			newlineBeforeComment = true
		}

		// Add the top-level directive if present
		if file.ast.Directive != "" {
			quoted := printer.Quote(file.ast.Directive) + ";" + newline
			prevOffset.advanceString(quoted)
			j.AddString(quoted)
			newlineBeforeComment = true
		}
	}

	// Optionally wrap with an IIFE
	if c.options.OutputFormat == config.FormatIIFE {
		var text string
		indent = "  "
		if c.options.UnsupportedFeatures.Has(compat.Arrow) {
			text = "(function()" + space + "{" + newline
		} else {
			text = "(()" + space + "=>" + space + "{" + newline
		}
		if c.options.ModuleName != "" {
			text = "var " + c.options.ModuleName + space + "=" + space + text
		}
		prevOffset.advanceString(text)
		j.AddString(text)
		newlineBeforeComment = false
	}

	// Put the cross-chunk prefix inside the IIFE
	if len(crossChunkPrefix) > 0 {
		newlineBeforeComment = true
		j.AddBytes(crossChunkPrefix)
	}

	// Start the metadata
	jMeta := printer.Joiner{}
	if c.options.AbsMetadataFile != "" {
		isFirstMeta := true
		jMeta.AddString("{\n      \"imports\": [")
		for _, record := range chunk.crossChunkImportRecords {
			if isFirstMeta {
				isFirstMeta = false
			} else {
				jMeta.AddString(",")
			}
			chunkAbsPath := c.fs.Join(c.options.AbsOutputDir, chunk.relPath)
			importAbsPath := c.fs.Join(c.fs.Dir(chunkAbsPath), record.Path.Text)
			jMeta.AddString(fmt.Sprintf("\n        {\n          \"path\": %s\n        }",
				printer.QuoteForJSON(c.res.PrettyPath(importAbsPath))))
		}
		if !isFirstMeta {
			jMeta.AddString("\n      ")
		}
		jMeta.AddString("],\n      \"inputs\": {")
	}
	isFirstMeta := true

	// Concatenate the generated JavaScript chunks together
	var compileResultsForSourceMap []compileResult
	var entryPointTail *printer.PrintResult
	var commentList []string
	commentSet := make(map[string]bool)
	for _, compileResult := range compileResults {
		isRuntime := compileResult.sourceIndex == runtime.SourceIndex
		for text := range compileResult.ExtractedComments {
			if !commentSet[text] {
				commentSet[text] = true
				commentList = append(commentList, text)
			}
		}

		// If this is the entry point, it may have some extra code to stick at the
		// end of the chunk after all modules have evaluated
		if compileResult.entryPointTail != nil {
			entryPointTail = compileResult.entryPointTail
		}

		// Don't add a file name comment for the runtime
		if c.options.IsBundling && !c.options.RemoveWhitespace && !isRuntime {
			if newlineBeforeComment {
				prevOffset.advanceString("\n")
				j.AddString("\n")
			}

			text := fmt.Sprintf("%s// %s\n", indent, c.sources[compileResult.sourceIndex].PrettyPath)
			prevOffset.advanceString(text)
			j.AddString(text)
		}

		// Omit the trailing semicolon when minifying the last file in IIFE mode
		if !isRuntime || len(compileResult.JS) > 0 {
			newlineBeforeComment = true
		}

		// Don't include the runtime in source maps
		if isRuntime {
			prevOffset.advanceString(string(compileResult.JS))
			j.AddBytes(compileResult.JS)
		} else {
			// Save the offset to the start of the stored JavaScript
			compileResult.generatedOffset = prevOffset
			j.AddBytes(compileResult.JS)

			// Ignore empty source map chunks
			if compileResult.SourceMapChunk.ShouldIgnore {
				prevOffset.advanceBytes(compileResult.JS)
			} else {
				prevOffset = lineColumnOffset{}
			}

			// Include this file in the source map
			if c.options.SourceMap != config.SourceMapNone {
				compileResultsForSourceMap = append(compileResultsForSourceMap, compileResult)
			}

			// Include this file in the metadata
			if c.options.AbsMetadataFile != "" {
				if isFirstMeta {
					isFirstMeta = false
				} else {
					jMeta.AddString(",")
				}
				jMeta.AddString(fmt.Sprintf("\n        %s: {\n          \"bytesInOutput\": %d\n        }",
					printer.QuoteForJSON(c.sources[compileResult.sourceIndex].PrettyPath),
					len(compileResult.JS)))
			}
		}
	}

	// Stick the entry point tail at the end of the file. Deliberately don't
	// include any source mapping information for this because it's automatically
	// generated and doesn't correspond to a location in the input file.
	if entryPointTail != nil {
		j.AddBytes(entryPointTail.JS)
	}

	// Put the cross-chunk suffix inside the IIFE
	if len(crossChunkSuffix) > 0 {
		if newlineBeforeComment {
			j.AddString(newline)
		}
		j.AddBytes(crossChunkSuffix)
	}

	// Optionally wrap with an IIFE
	if c.options.OutputFormat == config.FormatIIFE {
		j.AddString("})();" + newline)
	}

	// Make sure the file ends with a newline
	if j.Length() > 0 && j.LastByte() != '\n' {
		j.AddString("\n")
	}

	// Add all unique license comments to the end of the file. These are
	// deduplicated because some projects have thousands of files with the same
	// comment. The comment must be preserved in the output for legal reasons but
	// at the same time we want to generate a small bundle when minifying.
	sort.Strings(commentList)
	for _, text := range commentList {
		j.AddString(text)
		j.AddString("\n")
	}

	jsAbsPath := c.fs.Join(c.options.AbsOutputDir, chunk.relPath)

	if c.options.SourceMap != config.SourceMapNone {
		sourceMap := c.generateSourceMapForChunk(compileResultsForSourceMap)

		// Store the generated source map
		switch c.options.SourceMap {
		case config.SourceMapInline:
			j.AddString("//# sourceMappingURL=data:application/json;base64,")
			j.AddString(base64.StdEncoding.EncodeToString(sourceMap))
			j.AddString("\n")

		case config.SourceMapLinkedWithComment, config.SourceMapExternalWithoutComment:
			// Optionally add metadata about the file
			var jsonMetadataChunk []byte
			if c.options.AbsMetadataFile != "" {
				jsonMetadataChunk = []byte(fmt.Sprintf(
					"{\n      \"imports\": [],\n      \"inputs\": {},\n      \"bytes\": %d\n    }", len(sourceMap)))
			}

			results = append(results, OutputFile{
				AbsPath:           jsAbsPath + ".map",
				Contents:          sourceMap,
				jsonMetadataChunk: jsonMetadataChunk,
			})

			// Add a comment linking the source to its map
			if c.options.SourceMap == config.SourceMapLinkedWithComment {
				j.AddString("//# sourceMappingURL=")
				j.AddString(c.fs.Base(jsAbsPath + ".map"))
				j.AddString("\n")
			}
		}
	}

	jsContents := j.Done()

	// End the metadata
	var jsonMetadataChunk []byte
	if c.options.AbsMetadataFile != "" {
		if !isFirstMeta {
			jMeta.AddString("\n      ")
		}
		jMeta.AddString(fmt.Sprintf("},\n      \"bytes\": %d\n    }", len(jsContents)))
		jsonMetadataChunk = jMeta.Done()
	}

	results = append(results, OutputFile{
		AbsPath:           jsAbsPath,
		Contents:          jsContents,
		jsonMetadataChunk: jsonMetadataChunk,
	})
	return
}

func (offset *lineColumnOffset) advanceBytes(bytes []byte) {
	for i, n := 0, len(bytes); i < n; i++ {
		if bytes[i] == '\n' {
			offset.lines++
			offset.columns = 0
		} else {
			offset.columns++
		}
	}
}

func (offset *lineColumnOffset) advanceString(text string) {
	for i, n := 0, len(text); i < n; i++ {
		if text[i] == '\n' {
			offset.lines++
			offset.columns = 0
		} else {
			offset.columns++
		}
	}
}

func markBindingAsUnbound(binding ast.Binding, symbols ast.SymbolMap) {
	switch b := binding.Data.(type) {
	case *ast.BMissing:

	case *ast.BIdentifier:
		symbols.Get(b.Ref).Kind = ast.SymbolUnbound

	case *ast.BArray:
		for _, i := range b.Items {
			markBindingAsUnbound(i.Binding, symbols)
		}

	case *ast.BObject:
		for _, p := range b.Properties {
			markBindingAsUnbound(p.Value, symbols)
		}

	default:
		panic(fmt.Sprintf("Unexpected binding of type %T", binding.Data))
	}
}

// Marking a symbol as unbound prevents it from being renamed or minified.
// This is only used when a module is compiled independently. We use a very
// different way of handling exports and renaming/minifying when bundling.
func (c *linkerContext) markExportsAsUnbound(sourceIndex uint32) {
	file := &c.files[sourceIndex]
	hasImportOrExport := false

	for _, part := range file.ast.Parts {
		for _, stmt := range part.Stmts {
			switch s := stmt.Data.(type) {
			case *ast.SImport:
				// Ignore imports from the internal runtime code. These are generated
				// automatically and aren't part of the original source code. We
				// shouldn't consider the file a module if the only ES6 import or
				// export is the automatically generated one.
				record := &file.ast.ImportRecords[s.ImportRecordIndex]
				if record.SourceIndex != nil && *record.SourceIndex == runtime.SourceIndex {
					continue
				}

				hasImportOrExport = true

			case *ast.SLocal:
				if s.IsExport {
					for _, decl := range s.Decls {
						markBindingAsUnbound(decl.Binding, c.symbols)
					}
					hasImportOrExport = true
				}

			case *ast.SFunction:
				if s.IsExport {
					c.symbols.Get(s.Fn.Name.Ref).Kind = ast.SymbolUnbound
					hasImportOrExport = true
				}

			case *ast.SClass:
				if s.IsExport {
					c.symbols.Get(s.Class.Name.Ref).Kind = ast.SymbolUnbound
					hasImportOrExport = true
				}

			case *ast.SExportClause, *ast.SExportDefault, *ast.SExportStar:
				hasImportOrExport = true

			case *ast.SExportFrom:
				hasImportOrExport = true
			}
		}
	}

	// Heuristic: If this module has top-level import or export statements, we
	// consider this an ES6 module and only preserve the names of the exported
	// symbols. Everything else is minified since the names are private.
	//
	// Otherwise, we consider this potentially a script-type file instead of an
	// ES6 module. In that case, preserve the names of all top-level symbols
	// since they are all potentially exported (e.g. if this is used in a
	// <script> tag). All symbols in nested scopes are still minified.
	if !hasImportOrExport {
		for _, ref := range file.ast.ModuleScope.Members {
			c.symbols.Get(ref).Kind = ast.SymbolUnbound
		}
	}
}

func (c *linkerContext) renameOrMinifyAllSymbols() {
	topLevelScopes := make([]*ast.Scope, 0, len(c.files))
	moduleScopes := make([]*ast.Scope, 0, len(c.files))

	// Combine all file scopes
	for _, sourceIndex := range c.reachableFiles {
		file := &c.files[sourceIndex]
		fileMeta := &c.fileMeta[sourceIndex]
		moduleScopes = append(moduleScopes, file.ast.ModuleScope)
		if fileMeta.cjsWrap {
			// Modules wrapped in a CommonJS closure look like this:
			//
			//   // foo.js
			//   var require_foo = __commonJS((exports, module) => {
			//     ...
			//   });
			//
			// The symbol "require_foo" is stored in "file.ast.WrapperRef". We want
			// to be able to minify everything inside the closure without worrying
			// about collisions with other CommonJS modules. Set up the scopes such
			// that it appears as if the file was structured this way all along. It's
			// not completely accurate (e.g. we don't set the parent of the module
			// scope to this new top-level scope) but it's good enough for the
			// renaming code.
			//
			// Note: make sure to not mutate the original scope since it's supposed
			// to be immutable.
			fakeTopLevelScope := &ast.Scope{
				Members:   make(map[string]ast.Ref),
				Generated: []ast.Ref{file.ast.WrapperRef},
				Children:  []*ast.Scope{file.ast.ModuleScope},
			}

			// The unbound symbols are stored in the module scope. We need them for
			// computing reserved names. Avoid needing to copy them all into the new
			// fake top-level scope by still scanning the real module scope for
			// unbound symbols.
			topLevelScopes = append(topLevelScopes, fakeTopLevelScope)
		} else {
			topLevelScopes = append(topLevelScopes, file.ast.ModuleScope)

			// If this isn't CommonJS, then rename the unused "exports" and "module"
			// variables to avoid them causing the identically-named variables in
			// actual CommonJS files from being renamed. This is purely about
			// aesthetics and is not about correctness.
			if !fileMeta.cjsStyleExports {
				name := c.sources[sourceIndex].IdentifierName
				c.symbols.Get(file.ast.ExportsRef).Name = name + "_exports"
				c.symbols.Get(file.ast.ModuleRef).Name = name + "_module"
			}
		}
	}

	// Avoid collisions with any unbound symbols in this module group
	reservedNames := computeReservedNames(moduleScopes, c.symbols)
	if c.options.IsBundling {
		// These are used to implement bundling, and need to be free for use
		reservedNames["require"] = true
		reservedNames["Promise"] = true
	}

	if c.options.MinifyIdentifiers {
		minifyAllSymbols(reservedNames, topLevelScopes, c.symbols)
	} else {
		renameAllSymbols(reservedNames, topLevelScopes, c.symbols)
	}
}

func (c *linkerContext) generateSourceMapForChunk(results []compileResult) []byte {
	j := printer.Joiner{}
	j.AddString("{\n  \"version\": 3")

	// Write the sources
	j.AddString(",\n  \"sources\": [")
	needComma := false
	for _, result := range results {
		for _, source := range result.SourceMapChunk.QuotedSources {
			if needComma {
				j.AddString(", ")
			} else {
				needComma = true
			}
			j.AddString(source.QuotedPath)
		}
	}
	j.AddString("]")

	// Write the sourcesContent
	j.AddString(",\n  \"sourcesContent\": [")
	needComma = false
	for _, result := range results {
		for _, source := range result.SourceMapChunk.QuotedSources {
			if needComma {
				j.AddString(", ")
			} else {
				needComma = true
			}
			j.AddString(source.QuotedContents)
		}
	}
	j.AddString("]")

	// Write the mappings
	j.AddString(",\n  \"mappings\": \"")
	prevEndState := printer.SourceMapState{}
	prevColumnOffset := 0
	sourceMapIndex := 0
	for _, result := range results {
		chunk := result.SourceMapChunk
		offset := result.generatedOffset

		// Ignore empty source map chunks
		if chunk.ShouldIgnore {
			continue
		}

		// Because each file for the bundle is converted to a source map once,
		// the source maps are shared between all entry points in the bundle.
		// The easiest way of getting this to work is to have all source maps
		// generate as if their source index is 0. We then adjust the source
		// index per entry point by modifying the first source mapping. This
		// is done by AppendSourceMapChunk() using the source index passed
		// here.
		startState := printer.SourceMapState{
			SourceIndex:     sourceMapIndex,
			GeneratedLine:   offset.lines,
			GeneratedColumn: offset.columns,
		}
		if offset.lines == 0 {
			startState.GeneratedColumn += prevColumnOffset
		}

		// Append the precomputed source map chunk
		printer.AppendSourceMapChunk(&j, prevEndState, startState, chunk.Buffer)

		// Generate the relative offset to start from next time
		prevEndState = chunk.EndState
		prevEndState.SourceIndex += sourceMapIndex
		prevColumnOffset = chunk.FinalGeneratedColumn

		// If this was all one line, include the column offset from the start
		if prevEndState.GeneratedLine == 0 {
			prevEndState.GeneratedColumn += startState.GeneratedColumn
			prevColumnOffset += startState.GeneratedColumn
		}

		sourceMapIndex += len(result.SourceMapChunk.QuotedSources)
	}
	j.AddString("\"")

	// Finish the source map
	j.AddString(",\n  \"names\": []\n}\n")
	return j.Done()
}
