vendor tsgo
This commit is contained in:
66
tools/tsgo/internal/core/arena.go
Normal file
66
tools/tsgo/internal/core/arena.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package core
|
||||
|
||||
import "slices"
|
||||
|
||||
// Arena allocator
|
||||
|
||||
type Arena[T any] struct {
|
||||
data []T
|
||||
}
|
||||
|
||||
// Allocate a single element in the arena and return a pointer to the element. If the arena is at capacity,
|
||||
// a new arena of the next size up is allocated.
|
||||
func (a *Arena[T]) New() *T {
|
||||
if len(a.data) == cap(a.data) {
|
||||
nextSize := nextArenaSize(len(a.data))
|
||||
// Use the same trick as slices.Concat; Grow rounds up to the next size class.
|
||||
a.data = slices.Grow[[]T](nil, nextSize)
|
||||
}
|
||||
index := len(a.data)
|
||||
a.data = a.data[:index+1]
|
||||
return &a.data[index]
|
||||
}
|
||||
|
||||
// Allocate a slice of the given size in the arena. If the requested size is beyond the capacity of the arena
|
||||
// and an arena of the next size up still wouldn't fit the slice, make a separate memory allocation for the slice.
|
||||
// Otherwise, grow the arena if necessary and allocate a slice out of it. The length and capacity of the resulting
|
||||
// slice are equal to the given size.
|
||||
func (a *Arena[T]) NewSlice(size int) []T {
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(a.data)+size > cap(a.data) {
|
||||
nextSize := nextArenaSize(len(a.data))
|
||||
if size > nextSize {
|
||||
return make([]T, size)
|
||||
}
|
||||
// Use the same trick as slices.Concat; Grow rounds up to the next size class.
|
||||
a.data = slices.Grow[[]T](nil, nextSize)
|
||||
}
|
||||
newLen := len(a.data) + size
|
||||
slice := a.data[len(a.data):newLen:newLen]
|
||||
a.data = a.data[:newLen]
|
||||
return slice
|
||||
}
|
||||
|
||||
func (a *Arena[T]) NewSlice1(t T) []T {
|
||||
slice := a.NewSlice(1)
|
||||
slice[0] = t
|
||||
return slice
|
||||
}
|
||||
|
||||
func (a *Arena[T]) Clone(t []T) []T {
|
||||
if len(t) == 0 {
|
||||
return nil
|
||||
}
|
||||
slice := a.NewSlice(len(t))
|
||||
copy(slice, t)
|
||||
return slice
|
||||
}
|
||||
|
||||
func nextArenaSize(size int) int {
|
||||
// This compiles down branch-free.
|
||||
size = max(size, 1)
|
||||
size = min(size*2, 256)
|
||||
return size
|
||||
}
|
||||
206
tools/tsgo/internal/core/bfs.go
Normal file
206
tools/tsgo/internal/core/bfs.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
)
|
||||
|
||||
type BreadthFirstSearchResult[N any] struct {
|
||||
Stopped bool
|
||||
Path []N
|
||||
}
|
||||
|
||||
type breadthFirstSearchJob[N any] struct {
|
||||
node N
|
||||
parent *breadthFirstSearchJob[N]
|
||||
}
|
||||
|
||||
type BreadthFirstSearchLevel[K comparable, N any] struct {
|
||||
jobs *collections.OrderedMap[K, *breadthFirstSearchJob[N]]
|
||||
}
|
||||
|
||||
func (l *BreadthFirstSearchLevel[K, N]) Has(key K) bool {
|
||||
return l.jobs.Has(key)
|
||||
}
|
||||
|
||||
func (l *BreadthFirstSearchLevel[K, N]) Delete(key K) {
|
||||
l.jobs.Delete(key)
|
||||
}
|
||||
|
||||
func (l *BreadthFirstSearchLevel[K, N]) Range(f func(node N) bool) {
|
||||
for job := range l.jobs.Values() {
|
||||
if !f(job.node) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BreadthFirstSearchOptions[K comparable, N any] struct {
|
||||
// Visited is a set of nodes that have already been visited.
|
||||
// If nil, a new set will be created.
|
||||
Visited *collections.SyncSet[K]
|
||||
// PreprocessLevel is a function that, if provided, will be called
|
||||
// before each level, giving the caller an opportunity to remove nodes.
|
||||
PreprocessLevel func(*BreadthFirstSearchLevel[K, N])
|
||||
}
|
||||
|
||||
// BreadthFirstSearchParallel performs a breadth-first search on a graph
|
||||
// starting from the given node. It processes nodes in parallel and returns the path
|
||||
// from the first node that satisfies the `visit` function back to the start node.
|
||||
func BreadthFirstSearchParallel[N comparable](
|
||||
start N,
|
||||
neighbors func(N) []N,
|
||||
visit func(node N) (isResult bool, stop bool),
|
||||
) BreadthFirstSearchResult[N] {
|
||||
return BreadthFirstSearchParallelEx(start, neighbors, visit, BreadthFirstSearchOptions[N, N]{}, Identity)
|
||||
}
|
||||
|
||||
// BreadthFirstSearchParallelEx is an extension of BreadthFirstSearchParallel that allows
|
||||
// the caller to pass a pre-seeded set of already-visited nodes and a preprocessing function
|
||||
// that can be used to remove nodes from each level before parallel processing.
|
||||
func BreadthFirstSearchParallelEx[K comparable, N any](
|
||||
start N,
|
||||
neighbors func(N) []N,
|
||||
visit func(node N) (isResult bool, stop bool),
|
||||
options BreadthFirstSearchOptions[K, N],
|
||||
getKey func(N) K,
|
||||
) BreadthFirstSearchResult[N] {
|
||||
visited := options.Visited
|
||||
if visited == nil {
|
||||
visited = &collections.SyncSet[K]{}
|
||||
}
|
||||
|
||||
type result struct {
|
||||
stop bool
|
||||
job *breadthFirstSearchJob[N]
|
||||
next *collections.OrderedMap[K, *breadthFirstSearchJob[N]]
|
||||
}
|
||||
|
||||
var fallback *breadthFirstSearchJob[N]
|
||||
// processLevel processes each node at the current level in parallel.
|
||||
// It produces either a list of jobs to be processed in the next level,
|
||||
// or a result if the visit function returns true for any node.
|
||||
processLevel := func(index int, jobs *collections.OrderedMap[K, *breadthFirstSearchJob[N]]) result {
|
||||
var lowestFallback atomic.Int64
|
||||
var lowestGoal atomic.Int64
|
||||
var nextJobCount atomic.Int64
|
||||
lowestGoal.Store(math.MaxInt64)
|
||||
lowestFallback.Store(math.MaxInt64)
|
||||
if options.PreprocessLevel != nil {
|
||||
options.PreprocessLevel(&BreadthFirstSearchLevel[K, N]{jobs: jobs})
|
||||
}
|
||||
next := make([][]*breadthFirstSearchJob[N], jobs.Size())
|
||||
var wg sync.WaitGroup
|
||||
i := 0
|
||||
for j := range jobs.Values() {
|
||||
wg.Add(1)
|
||||
go func(i int, j *breadthFirstSearchJob[N]) {
|
||||
defer wg.Done()
|
||||
if int64(i) >= lowestGoal.Load() {
|
||||
return // Stop processing if we already found a lower result
|
||||
}
|
||||
|
||||
// If we have already visited this node, skip it.
|
||||
if !visited.AddIfAbsent(getKey(j.node)) {
|
||||
// Note that if we are here, we already visited this node at a
|
||||
// previous *level*, which means `visit` must have returned false,
|
||||
// so we don't need to update our result indices. This holds true
|
||||
// because we deduplicated jobs before queuing the level.
|
||||
return
|
||||
}
|
||||
|
||||
isResult, stop := visit(j.node)
|
||||
if isResult {
|
||||
// We found a result, so we will stop at this level, but an
|
||||
// earlier job may still find a true result at a lower index.
|
||||
if stop {
|
||||
updateMin(&lowestGoal, int64(i))
|
||||
return
|
||||
}
|
||||
if fallback == nil {
|
||||
updateMin(&lowestFallback, int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
if int64(i) >= lowestGoal.Load() {
|
||||
// If `visit` is expensive, it's likely that by the time we get here,
|
||||
// a different job has already found a lower index result, so we
|
||||
// don't even need to collect the next jobs.
|
||||
return
|
||||
}
|
||||
// Add the next level jobs
|
||||
neighborNodes := neighbors(j.node)
|
||||
if len(neighborNodes) > 0 {
|
||||
nextJobCount.Add(int64(len(neighborNodes)))
|
||||
next[i] = Map(neighborNodes, func(child N) *breadthFirstSearchJob[N] {
|
||||
return &breadthFirstSearchJob[N]{node: child, parent: j}
|
||||
})
|
||||
}
|
||||
}(i, j)
|
||||
i++
|
||||
}
|
||||
wg.Wait()
|
||||
if index := lowestGoal.Load(); index != math.MaxInt64 {
|
||||
// If we found a result, return it immediately.
|
||||
_, job, _ := jobs.EntryAt(int(index))
|
||||
return result{stop: true, job: job}
|
||||
}
|
||||
if fallback == nil {
|
||||
if index := lowestFallback.Load(); index != math.MaxInt64 {
|
||||
_, fallback, _ = jobs.EntryAt(int(index))
|
||||
}
|
||||
}
|
||||
nextJobs := collections.NewOrderedMapWithSizeHint[K, *breadthFirstSearchJob[N]](int(nextJobCount.Load()))
|
||||
for _, jobs := range next {
|
||||
for _, j := range jobs {
|
||||
if !nextJobs.Has(getKey(j.node)) {
|
||||
// Deduplicate synchronously to avoid messy locks and spawning
|
||||
// unnecessary goroutines.
|
||||
nextJobs.Set(getKey(j.node), j)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result{next: nextJobs}
|
||||
}
|
||||
|
||||
createPath := func(job *breadthFirstSearchJob[N]) []N {
|
||||
var path []N
|
||||
for job != nil {
|
||||
path = append(path, job.node)
|
||||
job = job.parent
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
levelIndex := 0
|
||||
level := collections.NewOrderedMapFromList([]collections.MapEntry[K, *breadthFirstSearchJob[N]]{
|
||||
{Key: getKey(start), Value: &breadthFirstSearchJob[N]{node: start}},
|
||||
})
|
||||
for level.Size() > 0 {
|
||||
result := processLevel(levelIndex, level)
|
||||
if result.stop {
|
||||
return BreadthFirstSearchResult[N]{Stopped: true, Path: createPath(result.job)}
|
||||
} else if result.job != nil && fallback == nil {
|
||||
fallback = result.job
|
||||
}
|
||||
level = result.next
|
||||
levelIndex++
|
||||
}
|
||||
return BreadthFirstSearchResult[N]{Stopped: false, Path: createPath(fallback)}
|
||||
}
|
||||
|
||||
// updateMin updates the atomic integer `a` to the candidate value if it is less than the current value.
|
||||
func updateMin(a *atomic.Int64, candidate int64) bool {
|
||||
for {
|
||||
current := a.Load()
|
||||
if current < candidate {
|
||||
return false
|
||||
}
|
||||
if a.CompareAndSwap(current, candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
152
tools/tsgo/internal/core/bfs_test.go
Normal file
152
tools/tsgo/internal/core/bfs_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestBreadthFirstSearchParallel(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("basic functionality", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test basic functionality with a simple DAG
|
||||
// Graph: A -> B, A -> C, B -> D, C -> D
|
||||
graph := map[string][]string{
|
||||
"A": {"B", "C"},
|
||||
"B": {"D"},
|
||||
"C": {"D"},
|
||||
"D": {},
|
||||
}
|
||||
|
||||
children := func(node string) []string {
|
||||
return graph[node]
|
||||
}
|
||||
|
||||
t.Run("find specific node", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := core.BreadthFirstSearchParallel("A", children, func(node string) (bool, bool) {
|
||||
return node == "D", true
|
||||
})
|
||||
assert.Equal(t, result.Stopped, true, "Expected search to stop at D")
|
||||
assert.DeepEqual(t, result.Path, []string{"D", "B", "A"})
|
||||
})
|
||||
|
||||
t.Run("visit all nodes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var mu sync.Mutex
|
||||
var visitedNodes []string
|
||||
result := core.BreadthFirstSearchParallel("A", children, func(node string) (bool, bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
visitedNodes = append(visitedNodes, node)
|
||||
return false, false // Never stop early
|
||||
})
|
||||
|
||||
// Should return nil since we never return true
|
||||
assert.Equal(t, result.Stopped, false, "Expected search to not stop early")
|
||||
assert.Assert(t, result.Path == nil, "Expected nil path when visit function never returns true")
|
||||
|
||||
// Should visit all nodes exactly once
|
||||
sort.Strings(visitedNodes)
|
||||
expected := []string{"A", "B", "C", "D"}
|
||||
assert.DeepEqual(t, visitedNodes, expected)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("early termination", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test that nodes below the target level are not visited
|
||||
graph := map[string][]string{
|
||||
"Root": {"L1A", "L1B"},
|
||||
"L1A": {"L2A", "L2B"},
|
||||
"L1B": {"L2C"},
|
||||
"L2A": {"L3A"},
|
||||
"L2B": {},
|
||||
"L2C": {},
|
||||
"L3A": {},
|
||||
}
|
||||
|
||||
children := func(node string) []string {
|
||||
return graph[node]
|
||||
}
|
||||
|
||||
var visited collections.SyncSet[string]
|
||||
core.BreadthFirstSearchParallelEx("Root", children, func(node string) (bool, bool) {
|
||||
return node == "L2B", true // Stop at level 2
|
||||
}, core.BreadthFirstSearchOptions[string, string]{
|
||||
Visited: &visited,
|
||||
},
|
||||
core.Identity)
|
||||
|
||||
assert.Assert(t, visited.Has("Root"), "Expected to visit Root")
|
||||
assert.Assert(t, visited.Has("L1A"), "Expected to visit L1A")
|
||||
assert.Assert(t, visited.Has("L1B"), "Expected to visit L1B")
|
||||
assert.Assert(t, visited.Has("L2A"), "Expected to visit L2A")
|
||||
assert.Assert(t, visited.Has("L2B"), "Expected to visit L2B")
|
||||
// L2C is non-deterministic
|
||||
assert.Assert(t, !visited.Has("L3A"), "Expected not to visit L3A")
|
||||
})
|
||||
|
||||
t.Run("returns fallback when no other result found", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test that fallback behavior works correctly
|
||||
graph := map[string][]string{
|
||||
"A": {"B", "C"},
|
||||
"B": {"D"},
|
||||
"C": {"D"},
|
||||
"D": {},
|
||||
}
|
||||
|
||||
children := func(node string) []string {
|
||||
return graph[node]
|
||||
}
|
||||
|
||||
var visited collections.SyncSet[string]
|
||||
result := core.BreadthFirstSearchParallelEx("A", children, func(node string) (bool, bool) {
|
||||
return node == "A", false // Record A as a fallback, but do not stop
|
||||
}, core.BreadthFirstSearchOptions[string, string]{
|
||||
Visited: &visited,
|
||||
},
|
||||
core.Identity)
|
||||
|
||||
assert.Equal(t, result.Stopped, false, "Expected search to not stop early")
|
||||
assert.DeepEqual(t, result.Path, []string{"A"})
|
||||
assert.Assert(t, visited.Has("B"), "Expected to visit B")
|
||||
assert.Assert(t, visited.Has("C"), "Expected to visit C")
|
||||
assert.Assert(t, visited.Has("D"), "Expected to visit D")
|
||||
})
|
||||
|
||||
t.Run("returns a stop result over a fallback", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test that a stop result is preferred over a fallback
|
||||
graph := map[string][]string{
|
||||
"A": {"B", "C"},
|
||||
"B": {"D"},
|
||||
"C": {"D"},
|
||||
"D": {},
|
||||
}
|
||||
|
||||
children := func(node string) []string {
|
||||
return graph[node]
|
||||
}
|
||||
|
||||
result := core.BreadthFirstSearchParallel("A", children, func(node string) (bool, bool) {
|
||||
switch node {
|
||||
case "A":
|
||||
return true, false // Record fallback
|
||||
case "D":
|
||||
return true, true // Stop at D
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
})
|
||||
|
||||
assert.Equal(t, result.Stopped, true, "Expected search to stop at D")
|
||||
assert.DeepEqual(t, result.Path, []string{"D", "B", "A"})
|
||||
})
|
||||
}
|
||||
26
tools/tsgo/internal/core/binarysearch.go
Normal file
26
tools/tsgo/internal/core/binarysearch.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package core
|
||||
|
||||
// BinarySearchUniqueFunc works like [slices.BinarySearchFunc], but avoids extra
|
||||
// invocations of the comparison function by assuming that only one element
|
||||
// in the slice could match the target. Also, unlike [slices.BinarySearchFunc],
|
||||
// the comparison function is passed the current index of the element being
|
||||
// compared, instead of the target element.
|
||||
func BinarySearchUniqueFunc[S ~[]E, E any](x S, cmp func(int, E) int) (int, bool) {
|
||||
n := len(x)
|
||||
if n == 0 {
|
||||
return 0, false
|
||||
}
|
||||
low, high := 0, n-1
|
||||
for low <= high {
|
||||
middle := low + ((high - low) >> 1)
|
||||
value := cmp(middle, x[middle])
|
||||
if value < 0 {
|
||||
low = middle + 1
|
||||
} else if value > 0 {
|
||||
high = middle - 1
|
||||
} else {
|
||||
return middle, true
|
||||
}
|
||||
}
|
||||
return low, false
|
||||
}
|
||||
16
tools/tsgo/internal/core/buildoptions.go
Normal file
16
tools/tsgo/internal/core/buildoptions.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
type BuildOptions struct {
|
||||
_ noCopy
|
||||
|
||||
Dry Tristate `json:"dry,omitzero"`
|
||||
Force Tristate `json:"force,omitzero"`
|
||||
Verbose Tristate `json:"verbose,omitzero"`
|
||||
Builders *int `json:"builders,omitzero"`
|
||||
StopBuildOnErrors Tristate `json:"stopBuildOnErrors,omitzero"`
|
||||
|
||||
// CompilerOptions are not parsed here and will be available on ParsedBuildCommandLine
|
||||
|
||||
// Internal fields
|
||||
Clean Tristate `json:"clean,omitzero"`
|
||||
}
|
||||
557
tools/tsgo/internal/core/compileroptions.go
Normal file
557
tools/tsgo/internal/core/compileroptions.go
Normal file
@@ -0,0 +1,557 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ModuleKind -trimprefix=ModuleKind -output=modulekind_stringer_generated.go
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptTarget -trimprefix=ScriptTarget -output=scripttarget_stringer_generated.go
|
||||
//go:generate npx dprint fmt modulekind_stringer_generated.go scripttarget_stringer_generated.go
|
||||
|
||||
// Keep in sync with the API's compilerOptions.ts
|
||||
|
||||
type CompilerOptions struct {
|
||||
_ noCopy
|
||||
|
||||
AllowJs Tristate `json:"allowJs,omitzero"`
|
||||
AllowArbitraryExtensions Tristate `json:"allowArbitraryExtensions,omitzero"`
|
||||
AllowImportingTsExtensions Tristate `json:"allowImportingTsExtensions,omitzero"`
|
||||
AllowNonTsExtensions Tristate `json:"allowNonTsExtensions,omitzero"`
|
||||
AllowUmdGlobalAccess Tristate `json:"allowUmdGlobalAccess,omitzero"`
|
||||
AllowUnreachableCode Tristate `json:"allowUnreachableCode,omitzero"`
|
||||
AllowUnusedLabels Tristate `json:"allowUnusedLabels,omitzero"`
|
||||
AssumeChangesOnlyAffectDirectDependencies Tristate `json:"assumeChangesOnlyAffectDirectDependencies,omitzero"`
|
||||
CheckJs Tristate `json:"checkJs,omitzero"`
|
||||
CustomConditions []string `json:"customConditions,omitzero"`
|
||||
Composite Tristate `json:"composite,omitzero"`
|
||||
EmitDeclarationOnly Tristate `json:"emitDeclarationOnly,omitzero"`
|
||||
EmitBOM Tristate `json:"emitBOM,omitzero"`
|
||||
EmitDecoratorMetadata Tristate `json:"emitDecoratorMetadata,omitzero"`
|
||||
Declaration Tristate `json:"declaration,omitzero"`
|
||||
DeclarationDir string `json:"declarationDir,omitzero"`
|
||||
DeclarationMap Tristate `json:"declarationMap,omitzero"`
|
||||
DeduplicatePackages Tristate `json:"deduplicatePackages,omitzero"`
|
||||
DisableSizeLimit Tristate `json:"disableSizeLimit,omitzero"`
|
||||
DisableSourceOfProjectReferenceRedirect Tristate `json:"disableSourceOfProjectReferenceRedirect,omitzero"`
|
||||
DisableSolutionSearching Tristate `json:"disableSolutionSearching,omitzero"`
|
||||
DisableReferencedProjectLoad Tristate `json:"disableReferencedProjectLoad,omitzero"`
|
||||
ErasableSyntaxOnly Tristate `json:"erasableSyntaxOnly,omitzero"`
|
||||
ExactOptionalPropertyTypes Tristate `json:"exactOptionalPropertyTypes,omitzero"`
|
||||
ExperimentalDecorators Tristate `json:"experimentalDecorators,omitzero"`
|
||||
ForceConsistentCasingInFileNames Tristate `json:"forceConsistentCasingInFileNames,omitzero"`
|
||||
IsolatedModules Tristate `json:"isolatedModules,omitzero"`
|
||||
IsolatedDeclarations Tristate `json:"isolatedDeclarations,omitzero"`
|
||||
IgnoreConfig Tristate `json:"ignoreConfig,omitzero"`
|
||||
IgnoreDeprecations string `json:"ignoreDeprecations,omitzero"`
|
||||
ImportHelpers Tristate `json:"importHelpers,omitzero"`
|
||||
InlineSourceMap Tristate `json:"inlineSourceMap,omitzero"`
|
||||
InlineSources Tristate `json:"inlineSources,omitzero"`
|
||||
Init Tristate `json:"init,omitzero"`
|
||||
Incremental Tristate `json:"incremental,omitzero"`
|
||||
Jsx JsxEmit `json:"jsx,omitzero"`
|
||||
JsxFactory string `json:"jsxFactory,omitzero"`
|
||||
JsxFragmentFactory string `json:"jsxFragmentFactory,omitzero"`
|
||||
JsxImportSource string `json:"jsxImportSource,omitzero"`
|
||||
Lib []string `json:"lib,omitzero"`
|
||||
LibReplacement Tristate `json:"libReplacement,omitzero"`
|
||||
Locale string `json:"locale,omitzero"`
|
||||
MapRoot string `json:"mapRoot,omitzero"`
|
||||
Module ModuleKind `json:"module,omitzero"`
|
||||
ModuleResolution ModuleResolutionKind `json:"moduleResolution,omitzero"`
|
||||
ModuleSuffixes []string `json:"moduleSuffixes,omitzero"`
|
||||
ModuleDetection ModuleDetectionKind `json:"moduleDetection,omitzero"`
|
||||
NewLine NewLineKind `json:"newLine,omitzero"`
|
||||
NoEmit Tristate `json:"noEmit,omitzero"`
|
||||
NoCheck Tristate `json:"noCheck,omitzero"`
|
||||
NoErrorTruncation Tristate `json:"noErrorTruncation,omitzero"`
|
||||
NoFallthroughCasesInSwitch Tristate `json:"noFallthroughCasesInSwitch,omitzero"`
|
||||
NoImplicitAny Tristate `json:"noImplicitAny,omitzero"`
|
||||
NoImplicitThis Tristate `json:"noImplicitThis,omitzero"`
|
||||
NoImplicitReturns Tristate `json:"noImplicitReturns,omitzero"`
|
||||
NoEmitHelpers Tristate `json:"noEmitHelpers,omitzero"`
|
||||
NoLib Tristate `json:"noLib,omitzero"`
|
||||
NoPropertyAccessFromIndexSignature Tristate `json:"noPropertyAccessFromIndexSignature,omitzero"`
|
||||
NoUncheckedIndexedAccess Tristate `json:"noUncheckedIndexedAccess,omitzero"`
|
||||
NoEmitOnError Tristate `json:"noEmitOnError,omitzero"`
|
||||
NoUnusedLocals Tristate `json:"noUnusedLocals,omitzero"`
|
||||
NoUnusedParameters Tristate `json:"noUnusedParameters,omitzero"`
|
||||
NoResolve Tristate `json:"noResolve,omitzero"`
|
||||
NoImplicitOverride Tristate `json:"noImplicitOverride,omitzero"`
|
||||
NoUncheckedSideEffectImports Tristate `json:"noUncheckedSideEffectImports,omitzero"`
|
||||
OutDir string `json:"outDir,omitzero"`
|
||||
Paths *collections.OrderedMap[string, []string] `json:"paths,omitzero"`
|
||||
PreserveConstEnums Tristate `json:"preserveConstEnums,omitzero"`
|
||||
PreserveSymlinks Tristate `json:"preserveSymlinks,omitzero"`
|
||||
Project string `json:"project,omitzero"`
|
||||
ResolveJsonModule Tristate `json:"resolveJsonModule,omitzero"`
|
||||
ResolvePackageJsonExports Tristate `json:"resolvePackageJsonExports,omitzero"`
|
||||
ResolvePackageJsonImports Tristate `json:"resolvePackageJsonImports,omitzero"`
|
||||
RemoveComments Tristate `json:"removeComments,omitzero"`
|
||||
RewriteRelativeImportExtensions Tristate `json:"rewriteRelativeImportExtensions,omitzero"`
|
||||
ReactNamespace string `json:"reactNamespace,omitzero"`
|
||||
RootDir string `json:"rootDir,omitzero"`
|
||||
RootDirs []string `json:"rootDirs,omitzero"`
|
||||
SkipLibCheck Tristate `json:"skipLibCheck,omitzero"`
|
||||
StableTypeOrdering Tristate `json:"stableTypeOrdering,omitzero"`
|
||||
Strict Tristate `json:"strict,omitzero"`
|
||||
StrictBindCallApply Tristate `json:"strictBindCallApply,omitzero"`
|
||||
StrictBuiltinIteratorReturn Tristate `json:"strictBuiltinIteratorReturn,omitzero"`
|
||||
StrictFunctionTypes Tristate `json:"strictFunctionTypes,omitzero"`
|
||||
StrictNullChecks Tristate `json:"strictNullChecks,omitzero"`
|
||||
StrictPropertyInitialization Tristate `json:"strictPropertyInitialization,omitzero"`
|
||||
StripInternal Tristate `json:"stripInternal,omitzero"`
|
||||
SkipDefaultLibCheck Tristate `json:"skipDefaultLibCheck,omitzero"`
|
||||
SourceMap Tristate `json:"sourceMap,omitzero"`
|
||||
SourceRoot string `json:"sourceRoot,omitzero"`
|
||||
SuppressOutputPathCheck Tristate `json:"suppressOutputPathCheck,omitzero"`
|
||||
Target ScriptTarget `json:"target,omitzero"`
|
||||
TraceResolution Tristate `json:"traceResolution,omitzero"`
|
||||
TsBuildInfoFile string `json:"tsBuildInfoFile,omitzero"`
|
||||
TypeRoots []string `json:"typeRoots,omitzero"`
|
||||
Types []string `json:"types,omitzero"`
|
||||
UseDefineForClassFields Tristate `json:"useDefineForClassFields,omitzero"`
|
||||
UseUnknownInCatchVariables Tristate `json:"useUnknownInCatchVariables,omitzero"`
|
||||
VerbatimModuleSyntax Tristate `json:"verbatimModuleSyntax,omitzero"`
|
||||
MaxNodeModuleJsDepth *int `json:"maxNodeModuleJsDepth,omitzero"`
|
||||
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
AllowSyntheticDefaultImports Tristate `json:"allowSyntheticDefaultImports,omitzero"`
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
AlwaysStrict Tristate `json:"alwaysStrict,omitzero"`
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
BaseUrl string `json:"baseUrl,omitzero"`
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
DownlevelIteration Tristate `json:"downlevelIteration,omitzero"`
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ESModuleInterop Tristate `json:"esModuleInterop,omitzero"`
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
OutFile string `json:"outFile,omitzero"`
|
||||
|
||||
// Internal fields
|
||||
ConfigFilePath string `json:"configFilePath,omitzero"`
|
||||
NoDtsResolution Tristate `json:"noDtsResolution,omitzero"`
|
||||
PathsBasePath string `json:"pathsBasePath,omitzero"`
|
||||
Diagnostics Tristate `json:"diagnostics,omitzero"`
|
||||
ExtendedDiagnostics Tristate `json:"extendedDiagnostics,omitzero"`
|
||||
GenerateCpuProfile string `json:"generateCpuProfile,omitzero"`
|
||||
GenerateTrace string `json:"generateTrace,omitzero"`
|
||||
ListEmittedFiles Tristate `json:"listEmittedFiles,omitzero"`
|
||||
ListFiles Tristate `json:"listFiles,omitzero"`
|
||||
ExplainFiles Tristate `json:"explainFiles,omitzero"`
|
||||
ListFilesOnly Tristate `json:"listFilesOnly,omitzero"`
|
||||
NoEmitForJsFiles Tristate `json:"noEmitForJsFiles,omitzero"`
|
||||
PreserveWatchOutput Tristate `json:"preserveWatchOutput,omitzero"`
|
||||
Pretty Tristate `json:"pretty,omitzero"`
|
||||
Version Tristate `json:"version,omitzero"`
|
||||
Watch Tristate `json:"watch,omitzero"`
|
||||
ShowConfig Tristate `json:"showConfig,omitzero"`
|
||||
Build Tristate `json:"build,omitzero"`
|
||||
Help Tristate `json:"help,omitzero"`
|
||||
All Tristate `json:"all,omitzero"`
|
||||
|
||||
PprofDir string `json:"pprofDir,omitzero"`
|
||||
SingleThreaded Tristate `json:"singleThreaded,omitzero"`
|
||||
Quiet Tristate `json:"quiet,omitzero"`
|
||||
Checkers *int `json:"checkers,omitzero"`
|
||||
}
|
||||
|
||||
// noCopy may be embedded into structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
||||
var EmptyCompilerOptions = &CompilerOptions{}
|
||||
|
||||
var optionsType = reflect.TypeFor[CompilerOptions]()
|
||||
|
||||
// Clone creates a shallow copy of the CompilerOptions.
|
||||
func (options *CompilerOptions) Clone() *CompilerOptions {
|
||||
// TODO: this could be generated code instead of reflection.
|
||||
target := &CompilerOptions{}
|
||||
|
||||
sourceValue := reflect.ValueOf(options).Elem()
|
||||
targetValue := reflect.ValueOf(target).Elem()
|
||||
|
||||
for i := range sourceValue.NumField() {
|
||||
if optionsType.Field(i).IsExported() {
|
||||
targetValue.Field(i).Set(sourceValue.Field(i))
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetEmitScriptTarget() ScriptTarget {
|
||||
if options.Target != ScriptTargetNone {
|
||||
return options.Target
|
||||
}
|
||||
return ScriptTargetLatestStandard
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetEmitModuleKind() ModuleKind {
|
||||
if options.Module != ModuleKindNone {
|
||||
return options.Module
|
||||
}
|
||||
|
||||
target := options.GetEmitScriptTarget()
|
||||
if target == ScriptTargetESNext {
|
||||
return ModuleKindESNext
|
||||
}
|
||||
if target >= ScriptTargetES2022 {
|
||||
return ModuleKindES2022
|
||||
}
|
||||
if target >= ScriptTargetES2020 {
|
||||
return ModuleKindES2020
|
||||
}
|
||||
if target >= ScriptTargetES2015 {
|
||||
return ModuleKindES2015
|
||||
}
|
||||
return ModuleKindCommonJS
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetModuleResolutionKind() ModuleResolutionKind {
|
||||
switch options.ModuleResolution {
|
||||
case ModuleResolutionKindUnknown, ModuleResolutionKindClassic, ModuleResolutionKindNode10:
|
||||
switch options.GetEmitModuleKind() {
|
||||
case ModuleKindNode16, ModuleKindNode18, ModuleKindNode20:
|
||||
return ModuleResolutionKindNode16
|
||||
case ModuleKindNodeNext:
|
||||
return ModuleResolutionKindNodeNext
|
||||
default:
|
||||
return ModuleResolutionKindBundler
|
||||
}
|
||||
default:
|
||||
return options.ModuleResolution
|
||||
}
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetEmitModuleDetectionKind() ModuleDetectionKind {
|
||||
if options.ModuleDetection != ModuleDetectionKindNone {
|
||||
return options.ModuleDetection
|
||||
}
|
||||
moduleKind := options.GetEmitModuleKind()
|
||||
if ModuleKindNode16 <= moduleKind && moduleKind <= ModuleKindNodeNext {
|
||||
return ModuleDetectionKindForce
|
||||
}
|
||||
return ModuleDetectionKindAuto
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetResolvePackageJsonExports() bool {
|
||||
return options.ResolvePackageJsonExports.IsTrueOrUnknown()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetResolvePackageJsonImports() bool {
|
||||
return options.ResolvePackageJsonImports.IsTrueOrUnknown()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetAllowImportingTsExtensions() bool {
|
||||
return options.AllowImportingTsExtensions.IsTrue() || options.RewriteRelativeImportExtensions.IsTrue()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) AllowImportingTsExtensionsFrom(fileName string) bool {
|
||||
return options.GetAllowImportingTsExtensions() || tspath.IsDeclarationFileName(fileName)
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetResolveJsonModule() bool {
|
||||
if options.ResolveJsonModule != TSUnknown {
|
||||
return options.ResolveJsonModule == TSTrue
|
||||
}
|
||||
switch options.GetEmitModuleKind() {
|
||||
// TODO in 6.0: add Node16/Node18
|
||||
case ModuleKindNode20, ModuleKindNodeNext:
|
||||
return true
|
||||
}
|
||||
return options.GetModuleResolutionKind() == ModuleResolutionKindBundler
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) ShouldPreserveConstEnums() bool {
|
||||
return options.PreserveConstEnums == TSTrue || options.GetIsolatedModules()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetAllowJS() bool {
|
||||
if options.AllowJs != TSUnknown {
|
||||
return options.AllowJs == TSTrue
|
||||
}
|
||||
return options.CheckJs == TSTrue
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetJSXTransformEnabled() bool {
|
||||
jsx := options.Jsx
|
||||
return jsx == JsxEmitReact || jsx == JsxEmitReactJSX || jsx == JsxEmitReactJSXDev
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetStrictOptionValue(value Tristate) bool {
|
||||
if value != TSUnknown {
|
||||
return value == TSTrue
|
||||
}
|
||||
return options.Strict != TSFalse
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetEffectiveTypeRoots(currentDirectory string) (result []string, fromConfig bool) {
|
||||
if options.TypeRoots != nil {
|
||||
return options.TypeRoots, true
|
||||
}
|
||||
var baseDir string
|
||||
if options.ConfigFilePath != "" {
|
||||
baseDir = tspath.GetDirectoryPath(options.ConfigFilePath)
|
||||
} else {
|
||||
baseDir = currentDirectory
|
||||
if baseDir == "" {
|
||||
// This was accounted for in the TS codebase, but only for third-party API usage
|
||||
// where the module resolution host does not provide a getCurrentDirectory().
|
||||
panic("cannot get effective type roots without a config file path or current directory")
|
||||
}
|
||||
}
|
||||
|
||||
typeRoots := make([]string, 0, strings.Count(baseDir, "/"))
|
||||
tspath.ForEachAncestorDirectory(baseDir, func(dir string) (any, bool) {
|
||||
typeRoots = append(typeRoots, tspath.CombinePaths(dir, "node_modules", "@types"))
|
||||
return nil, false
|
||||
})
|
||||
return typeRoots, false
|
||||
}
|
||||
|
||||
// UsesWildcardTypes returns true if this option's types array includes "*"
|
||||
func (options *CompilerOptions) UsesWildcardTypes() bool {
|
||||
return slices.Contains(options.Types, "*")
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetIsolatedModules() bool {
|
||||
return options.IsolatedModules == TSTrue || options.VerbatimModuleSyntax == TSTrue
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) IsIncremental() bool {
|
||||
return options.Incremental.IsTrue() || options.Composite.IsTrue()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetEmitStandardClassFields() bool {
|
||||
return options.UseDefineForClassFields != TSFalse && options.GetEmitScriptTarget() >= ScriptTargetES2022
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetUseDefineForClassFields() bool {
|
||||
if options.UseDefineForClassFields == TSUnknown {
|
||||
return options.GetEmitScriptTarget() >= ScriptTargetES2022
|
||||
}
|
||||
return options.UseDefineForClassFields == TSTrue
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetEmitDeclarations() bool {
|
||||
return options.Declaration.IsTrue() || options.Composite.IsTrue()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetAreDeclarationMapsEnabled() bool {
|
||||
return options.DeclarationMap == TSTrue && options.GetEmitDeclarations()
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) HasJsonModuleEmitEnabled() bool {
|
||||
switch options.GetEmitModuleKind() {
|
||||
case ModuleKindSystem, ModuleKindUMD:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (options *CompilerOptions) GetPathsBasePath(currentDirectory string) string {
|
||||
if options.Paths.Size() == 0 {
|
||||
return ""
|
||||
}
|
||||
if options.PathsBasePath != "" {
|
||||
return options.PathsBasePath
|
||||
}
|
||||
return currentDirectory
|
||||
}
|
||||
|
||||
type ModuleDetectionKind int32
|
||||
|
||||
const (
|
||||
ModuleDetectionKindNone ModuleDetectionKind = 0
|
||||
ModuleDetectionKindAuto ModuleDetectionKind = 1
|
||||
ModuleDetectionKindLegacy ModuleDetectionKind = 2
|
||||
ModuleDetectionKindForce ModuleDetectionKind = 3
|
||||
)
|
||||
|
||||
type ModuleKind int32
|
||||
|
||||
const (
|
||||
ModuleKindNone ModuleKind = 0
|
||||
ModuleKindCommonJS ModuleKind = 1
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ModuleKindAMD ModuleKind = 2
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ModuleKindUMD ModuleKind = 3
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ModuleKindSystem ModuleKind = 4
|
||||
// NOTE: ES module kinds should be contiguous to more easily check whether a module kind is *any* ES module kind.
|
||||
// Non-ES module kinds should not come between ES2015 (the earliest ES module kind) and ESNext (the last ES
|
||||
// module kind).
|
||||
ModuleKindES2015 ModuleKind = 5
|
||||
ModuleKindES2020 ModuleKind = 6
|
||||
ModuleKindES2022 ModuleKind = 7
|
||||
ModuleKindESNext ModuleKind = 99
|
||||
// Node16+ is an amalgam of commonjs (albeit updated) and es2022+, and represents a distinct module system from es2020/esnext
|
||||
ModuleKindNode16 ModuleKind = 100
|
||||
ModuleKindNode18 ModuleKind = 101
|
||||
ModuleKindNode20 ModuleKind = 102
|
||||
ModuleKindNodeNext ModuleKind = 199
|
||||
// Emit as written
|
||||
ModuleKindPreserve ModuleKind = 200
|
||||
)
|
||||
|
||||
func (moduleKind ModuleKind) IsNonNodeESM() bool {
|
||||
return moduleKind >= ModuleKindES2015 && moduleKind <= ModuleKindESNext
|
||||
}
|
||||
|
||||
func (moduleKind ModuleKind) SupportsImportAttributes() bool {
|
||||
return ModuleKindNode18 <= moduleKind && moduleKind <= ModuleKindNodeNext ||
|
||||
moduleKind == ModuleKindPreserve ||
|
||||
moduleKind == ModuleKindESNext
|
||||
}
|
||||
|
||||
type ResolutionMode = ModuleKind // ModuleKindNone | ModuleKindCommonJS | ModuleKindESNext
|
||||
|
||||
const (
|
||||
ResolutionModeNone = ModuleKindNone
|
||||
ResolutionModeCommonJS = ModuleKindCommonJS
|
||||
ResolutionModeESM = ModuleKindESNext
|
||||
)
|
||||
|
||||
type ModuleResolutionKind int32
|
||||
|
||||
const (
|
||||
ModuleResolutionKindUnknown ModuleResolutionKind = 0
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ModuleResolutionKindClassic ModuleResolutionKind = 1
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ModuleResolutionKindNode10 ModuleResolutionKind = 2
|
||||
// Starting with node16, node's module resolver has significant departures from traditional cjs resolution
|
||||
// to better support ECMAScript modules and their use within node - however more features are still being added.
|
||||
// TypeScript's Node ESM support was introduced after Node 12 went end-of-life, and Node 14 is the earliest stable
|
||||
// version that supports both pattern trailers - *but*, Node 16 is the first version that also supports ECMAScript 2022.
|
||||
// In turn, we offer both a `NodeNext` moving resolution target, and a `Node16` version-anchored resolution target
|
||||
ModuleResolutionKindNode16 ModuleResolutionKind = 3
|
||||
ModuleResolutionKindNodeNext ModuleResolutionKind = 99 // Not simply `Node16` so that compiled code linked against TS can use the `Next` value reliably (same as with `ModuleKind`)
|
||||
ModuleResolutionKindBundler ModuleResolutionKind = 100
|
||||
)
|
||||
|
||||
var ModuleKindToModuleResolutionKind = map[ModuleKind]ModuleResolutionKind{
|
||||
ModuleKindNode16: ModuleResolutionKindNode16,
|
||||
ModuleKindNodeNext: ModuleResolutionKindNodeNext,
|
||||
}
|
||||
|
||||
// We don't use stringer on this for now, because these values
|
||||
// are user-facing in --traceResolution, and stringer currently
|
||||
// lacks the ability to remove the "ModuleResolutionKind" prefix
|
||||
// when generating code for multiple types into the same output
|
||||
// file. Additionally, since there's no TS equivalent of
|
||||
// `ModuleResolutionKindUnknown`, we want to panic on that case,
|
||||
// as it probably represents a mistake when porting TS to Go.
|
||||
func (m ModuleResolutionKind) String() string {
|
||||
switch m {
|
||||
case ModuleResolutionKindUnknown:
|
||||
panic("should not use zero value of ModuleResolutionKind")
|
||||
case ModuleResolutionKindClassic:
|
||||
return "Classic"
|
||||
case ModuleResolutionKindNode10:
|
||||
return "Node10"
|
||||
case ModuleResolutionKindNode16:
|
||||
return "Node16"
|
||||
case ModuleResolutionKindNodeNext:
|
||||
return "NodeNext"
|
||||
case ModuleResolutionKindBundler:
|
||||
return "Bundler"
|
||||
default:
|
||||
panic("unhandled case in ModuleResolutionKind.String")
|
||||
}
|
||||
}
|
||||
|
||||
type NewLineKind int32
|
||||
|
||||
const (
|
||||
NewLineKindNone NewLineKind = 0
|
||||
NewLineKindCRLF NewLineKind = 1
|
||||
NewLineKindLF NewLineKind = 2
|
||||
)
|
||||
|
||||
func GetNewLineKind(s string) NewLineKind {
|
||||
switch s {
|
||||
case "\r\n":
|
||||
return NewLineKindCRLF
|
||||
case "\n":
|
||||
return NewLineKindLF
|
||||
default:
|
||||
return NewLineKindNone
|
||||
}
|
||||
}
|
||||
|
||||
func (newLine NewLineKind) GetNewLineCharacter() string {
|
||||
switch newLine {
|
||||
case NewLineKindCRLF:
|
||||
return "\r\n"
|
||||
default:
|
||||
return "\n"
|
||||
}
|
||||
}
|
||||
|
||||
type ScriptTarget int32
|
||||
|
||||
const (
|
||||
ScriptTargetNone ScriptTarget = 0
|
||||
// Deprecated: Do not use outside of options parsing and validation.
|
||||
ScriptTargetES5 ScriptTarget = 1
|
||||
ScriptTargetES2015 ScriptTarget = 2
|
||||
ScriptTargetES2016 ScriptTarget = 3
|
||||
ScriptTargetES2017 ScriptTarget = 4
|
||||
ScriptTargetES2018 ScriptTarget = 5
|
||||
ScriptTargetES2019 ScriptTarget = 6
|
||||
ScriptTargetES2020 ScriptTarget = 7
|
||||
ScriptTargetES2021 ScriptTarget = 8
|
||||
ScriptTargetES2022 ScriptTarget = 9
|
||||
ScriptTargetES2023 ScriptTarget = 10
|
||||
ScriptTargetES2024 ScriptTarget = 11
|
||||
ScriptTargetES2025 ScriptTarget = 12
|
||||
ScriptTargetESNext ScriptTarget = 99
|
||||
ScriptTargetJSON ScriptTarget = 100
|
||||
ScriptTargetLatest ScriptTarget = ScriptTargetESNext
|
||||
ScriptTargetLatestStandard ScriptTarget = ScriptTargetES2025
|
||||
)
|
||||
|
||||
type JsxEmit int32
|
||||
|
||||
const (
|
||||
JsxEmitNone JsxEmit = 0
|
||||
JsxEmitPreserve JsxEmit = 1
|
||||
JsxEmitReactNative JsxEmit = 2
|
||||
JsxEmitReact JsxEmit = 3
|
||||
JsxEmitReactJSX JsxEmit = 4
|
||||
JsxEmitReactJSXDev JsxEmit = 5
|
||||
)
|
||||
|
||||
func (j JsxEmit) String() string {
|
||||
switch j {
|
||||
case JsxEmitNone:
|
||||
panic("should not use zero value of JsxEmit")
|
||||
case JsxEmitPreserve:
|
||||
return "preserve"
|
||||
case JsxEmitReactNative:
|
||||
return "react-native"
|
||||
case JsxEmitReact:
|
||||
return "react"
|
||||
case JsxEmitReactJSX:
|
||||
return "react-jsx"
|
||||
case JsxEmitReactJSXDev:
|
||||
return "react-jsxdev"
|
||||
default:
|
||||
panic("unhandled case in JsxEmit.String")
|
||||
}
|
||||
}
|
||||
42
tools/tsgo/internal/core/context.go
Normal file
42
tools/tsgo/internal/core/context.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const (
|
||||
requestIDKey key = iota
|
||||
checkerLifetimeKey
|
||||
)
|
||||
|
||||
func WithRequestID(ctx context.Context, id string) context.Context {
|
||||
return context.WithValue(ctx, requestIDKey, id)
|
||||
}
|
||||
|
||||
func GetRequestID(ctx context.Context) string {
|
||||
if id, ok := ctx.Value(requestIDKey).(string); ok {
|
||||
return id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CheckerLifetime int
|
||||
|
||||
const (
|
||||
CheckerLifetimeTemporary CheckerLifetime = iota
|
||||
CheckerLifetimeDiagnostics
|
||||
CheckerLifetimeAPI
|
||||
)
|
||||
|
||||
func WithCheckerLifetime(ctx context.Context, lifetime CheckerLifetime) context.Context {
|
||||
return context.WithValue(ctx, checkerLifetimeKey, lifetime)
|
||||
}
|
||||
|
||||
func GetCheckerLifetime(ctx context.Context) CheckerLifetime {
|
||||
if lifetime, ok := ctx.Value(checkerLifetimeKey).(CheckerLifetime); ok {
|
||||
return lifetime
|
||||
}
|
||||
return CheckerLifetimeTemporary
|
||||
}
|
||||
837
tools/tsgo/internal/core/core.go
Normal file
837
tools/tsgo/internal/core/core.go
Normal file
@@ -0,0 +1,837 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"math"
|
||||
"os"
|
||||
rtdebug "runtime/debug"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/debug"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/stringutil"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
func ApplyDebugStackLimit() {
|
||||
v := os.Getenv("TS_GO_DEBUG_STACK_LIMIT") //nolint:forbidigo
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n <= 0 {
|
||||
return
|
||||
}
|
||||
rtdebug.SetMaxStack(n)
|
||||
}
|
||||
|
||||
func Filter[T any](slice []T, f func(T) bool) []T {
|
||||
for i, value := range slice {
|
||||
if !f(value) {
|
||||
result := slices.Clone(slice[:i])
|
||||
for i++; i < len(slice); i++ {
|
||||
value = slice[i]
|
||||
if f(value) {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func FilterSeq[T any](slice []T, f func(T) bool) iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for _, value := range slice {
|
||||
if f(value) {
|
||||
if !yield(value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FilterIndex[T any](slice []T, f func(T, int, []T) bool) []T {
|
||||
for i, value := range slice {
|
||||
if !f(value, i, slice) {
|
||||
result := slices.Clone(slice[:i])
|
||||
for i++; i < len(slice); i++ {
|
||||
value = slice[i]
|
||||
if f(value, i, slice) {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func Map[T, U any](slice []T, f func(T) U) []U {
|
||||
if slice == nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]U, len(slice))
|
||||
for i, value := range slice {
|
||||
result[i] = f(value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TryMap[T, U any](slice []T, f func(T) (U, error)) ([]U, error) {
|
||||
if len(slice) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
result := make([]U, len(slice))
|
||||
for i, value := range slice {
|
||||
mapped, err := f(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = mapped
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func MapIndex[T, U any](slice []T, f func(T, int) U) []U {
|
||||
if slice == nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]U, len(slice))
|
||||
for i, value := range slice {
|
||||
result[i] = f(value, i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MapNonNil[T any, U comparable](slice []T, f func(T) U) []U {
|
||||
var result []U
|
||||
for _, value := range slice {
|
||||
mapped := f(value)
|
||||
if mapped != *new(U) {
|
||||
result = append(result, mapped)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MapFiltered[T any, U any](slice []T, f func(T) (U, bool)) []U {
|
||||
var result []U
|
||||
for _, value := range slice {
|
||||
mapped, ok := f(value)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
result = append(result, mapped)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func FlatMap[T any, U any](slice []T, f func(T) []U) []U {
|
||||
var result []U
|
||||
for _, value := range slice {
|
||||
mapped := f(value)
|
||||
if len(mapped) != 0 {
|
||||
result = append(result, mapped...)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func SameMap[T comparable](slice []T, f func(T) T) []T {
|
||||
for i, value := range slice {
|
||||
mapped := f(value)
|
||||
if mapped != value {
|
||||
result := make([]T, len(slice))
|
||||
copy(result, slice[:i])
|
||||
result[i] = mapped
|
||||
for j := i + 1; j < len(slice); j++ {
|
||||
result[j] = f(slice[j])
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func SameMapIndex[T comparable](slice []T, f func(T, int) T) []T {
|
||||
for i, value := range slice {
|
||||
mapped := f(value, i)
|
||||
if mapped != value {
|
||||
result := make([]T, len(slice))
|
||||
copy(result, slice[:i])
|
||||
result[i] = mapped
|
||||
for j := i + 1; j < len(slice); j++ {
|
||||
result[j] = f(slice[j], j)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func Same[T any](s1 []T, s2 []T) bool {
|
||||
if len(s1) == len(s2) {
|
||||
return len(s1) == 0 || &s1[0] == &s2[0]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Some[T any](slice []T, f func(T) bool) bool {
|
||||
for _, value := range slice { //nolint:modernize
|
||||
if f(value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Every[T any](slice []T, f func(T) bool) bool {
|
||||
for _, value := range slice {
|
||||
if !f(value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Or[T any](funcs ...func(T) bool) func(T) bool {
|
||||
return func(input T) bool {
|
||||
for _, f := range funcs {
|
||||
if f(input) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func Find[T any](slice []T, f func(T) bool) T {
|
||||
for _, value := range slice {
|
||||
if f(value) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func FindLast[T any](slice []T, f func(T) bool) T {
|
||||
for i := len(slice) - 1; i >= 0; i-- {
|
||||
value := slice[i]
|
||||
if f(value) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func FindIndex[T any](slice []T, f func(T) bool) int {
|
||||
for i, value := range slice {
|
||||
if f(value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func FindLastIndex[T any](slice []T, f func(T) bool) int {
|
||||
for i := len(slice) - 1; i >= 0; i-- {
|
||||
value := slice[i]
|
||||
if f(value) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func FirstOrNil[T any](slice []T) T {
|
||||
if len(slice) != 0 {
|
||||
return slice[0]
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func LastOrNil[T any](slice []T) T {
|
||||
if len(slice) != 0 {
|
||||
return slice[len(slice)-1]
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func ElementOrNil[T any](slice []T, index int) T {
|
||||
if index < len(slice) {
|
||||
return slice[index]
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func FirstOrNilSeq[T any](seq iter.Seq[T]) T {
|
||||
if seq != nil {
|
||||
for value := range seq {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func FirstNonNil[T any, U comparable](slice []T, f func(T) U) U {
|
||||
for _, value := range slice {
|
||||
mapped := f(value)
|
||||
if mapped != *new(U) {
|
||||
return mapped
|
||||
}
|
||||
}
|
||||
return *new(U)
|
||||
}
|
||||
|
||||
func FirstNonZero[T comparable](values ...T) T {
|
||||
var zero T
|
||||
for _, value := range values {
|
||||
if value != zero {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return zero
|
||||
}
|
||||
|
||||
func Concatenate[T any](s1 []T, s2 []T) []T {
|
||||
if len(s2) == 0 {
|
||||
return s1
|
||||
}
|
||||
if len(s1) == 0 {
|
||||
return s2
|
||||
}
|
||||
return slices.Concat(s1, s2)
|
||||
}
|
||||
|
||||
func Splice[T any](s1 []T, start int, deleteCount int, items ...T) []T {
|
||||
if start < 0 {
|
||||
start = len(s1) + start
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > len(s1) {
|
||||
start = len(s1)
|
||||
}
|
||||
if deleteCount < 0 {
|
||||
deleteCount = 0
|
||||
}
|
||||
end := min(start+max(deleteCount, 0), len(s1))
|
||||
if start == end && len(items) == 0 {
|
||||
return s1
|
||||
}
|
||||
return slices.Concat(s1[:start], items, s1[end:])
|
||||
}
|
||||
|
||||
func CountWhere[T any](slice []T, f func(T) bool) int {
|
||||
count := 0
|
||||
for _, value := range slice {
|
||||
if f(value) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func ReplaceElement[T any](slice []T, i int, t T) []T {
|
||||
result := slices.Clone(slice)
|
||||
result[i] = t
|
||||
return result
|
||||
}
|
||||
|
||||
func InsertSorted[T any](slice []T, element T, cmp func(T, T) int) []T {
|
||||
i, _ := slices.BinarySearchFunc(slice, element, cmp)
|
||||
return slices.Insert(slice, i, element)
|
||||
}
|
||||
|
||||
// MinAllFunc returns all minimum elements from xs according to the comparison function cmp.
|
||||
func MinAllFunc[T any](xs []T, cmp func(a, b T) int) []T {
|
||||
if len(xs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := xs[0]
|
||||
mins := []T{m}
|
||||
|
||||
for _, x := range xs[1:] {
|
||||
c := cmp(x, m)
|
||||
switch {
|
||||
case c < 0:
|
||||
m = x
|
||||
mins = mins[:0]
|
||||
mins = append(mins, x)
|
||||
case c == 0:
|
||||
mins = append(mins, x)
|
||||
}
|
||||
}
|
||||
|
||||
return mins
|
||||
}
|
||||
|
||||
func AppendIfUnique[T comparable](slice []T, element T) []T {
|
||||
if slices.Contains(slice, element) {
|
||||
return slice
|
||||
}
|
||||
return append(slice, element)
|
||||
}
|
||||
|
||||
func Memoize[T any](create func() T) func() T {
|
||||
var value T
|
||||
return func() T {
|
||||
if create != nil {
|
||||
value = create()
|
||||
create = nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whenTrue if b is true; otherwise, returns whenFalse. IfElse should only be used when branches are either
|
||||
// constant or precomputed as both branches will be evaluated regardless as to the value of b.
|
||||
func IfElse[T any](b bool, whenTrue T, whenFalse T) T {
|
||||
if b {
|
||||
return whenTrue
|
||||
}
|
||||
return whenFalse
|
||||
}
|
||||
|
||||
// Returns value if value is not the zero value of T; Otherwise, returns defaultValue. OrElse should only be used when
|
||||
// defaultValue is constant or precomputed as its argument will be evaluated regardless as to the content of value.
|
||||
func OrElse[T comparable](value T, defaultValue T) T {
|
||||
if value != *new(T) {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Returns `a` if `a` is not `nil`; Otherwise, returns `b`. Coalesce is roughly analogous to `??` in JS, except that it
|
||||
// non-shortcutting, so it is advised to only use a constant or precomputed value for `b`
|
||||
func Coalesce[T *U, U any](a T, b T) T {
|
||||
if a == nil {
|
||||
return b
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
type ECMALineStarts []TextPos
|
||||
|
||||
func ComputeECMALineStarts(text string) ECMALineStarts {
|
||||
result := make([]TextPos, 0, strings.Count(text, "\n")+1)
|
||||
return slices.AppendSeq(result, ComputeECMALineStartsSeq(text))
|
||||
}
|
||||
|
||||
func ComputeECMALineStartsSeq(text string) iter.Seq[TextPos] {
|
||||
return func(yield func(TextPos) bool) {
|
||||
textLen := TextPos(len(text))
|
||||
var pos TextPos
|
||||
var lineStart TextPos
|
||||
for pos < textLen {
|
||||
b := text[pos]
|
||||
if b < utf8.RuneSelf {
|
||||
pos++
|
||||
switch b {
|
||||
case '\r':
|
||||
if pos < textLen && text[pos] == '\n' {
|
||||
pos++
|
||||
}
|
||||
fallthrough
|
||||
case '\n':
|
||||
if !yield(lineStart) {
|
||||
return
|
||||
}
|
||||
lineStart = pos
|
||||
}
|
||||
} else {
|
||||
ch, size := utf8.DecodeRuneInString(text[pos:])
|
||||
pos += TextPos(size)
|
||||
if stringutil.IsLineBreak(ch) {
|
||||
if !yield(lineStart) {
|
||||
return
|
||||
}
|
||||
lineStart = pos
|
||||
}
|
||||
}
|
||||
}
|
||||
yield(lineStart)
|
||||
}
|
||||
}
|
||||
|
||||
// PositionToLineAndByteOffset returns the 0-based line and byte offset from the
|
||||
// start of that line for the given byte position, using the provided line starts.
|
||||
// The byte offset is a raw UTF-8 byte offset from the line start, not a UTF-16 code unit count.
|
||||
func PositionToLineAndByteOffset(position int, lineStarts []TextPos) (line int, byteOffset int) {
|
||||
line = max(sort.Search(len(lineStarts), func(i int) bool {
|
||||
return int(lineStarts[i]) > position
|
||||
})-1, 0)
|
||||
return line, position - int(lineStarts[line])
|
||||
}
|
||||
|
||||
// UTF16Offset represents a character offset measured in UTF-16 code units.
|
||||
type UTF16Offset int
|
||||
|
||||
// UTF16Len returns the number of UTF-16 code units needed to
|
||||
// represent the given UTF-8 encoded string.
|
||||
func UTF16Len(s string) UTF16Offset {
|
||||
// Fast path: scan for non-ASCII bytes. For ASCII-only strings,
|
||||
// each byte is one UTF-16 code unit, so we can return len(s) directly.
|
||||
for i := range len(s) {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
// Found non-ASCII; count the ASCII prefix, then decode the rest.
|
||||
n := UTF16Offset(i)
|
||||
for _, r := range s[i:] {
|
||||
n += UTF16Offset(utf16.RuneLen(r))
|
||||
}
|
||||
return n
|
||||
}
|
||||
}
|
||||
return UTF16Offset(len(s))
|
||||
}
|
||||
|
||||
func Flatten[T any](array [][]T) []T {
|
||||
var result []T
|
||||
for _, subArray := range array {
|
||||
result = append(result, subArray...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Extracts the first value of a multi-value return.
|
||||
func FirstResult[T1 any](t1 T1, _ ...any) T1 {
|
||||
return t1
|
||||
}
|
||||
|
||||
func StringifyJson(input any, prefix string, indent string) (string, error) {
|
||||
output, err := json.MarshalIndent(input, prefix, indent)
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
func GetScriptKindFromFileName(fileName string) ScriptKind {
|
||||
dotPos := strings.LastIndex(fileName, ".")
|
||||
if dotPos >= 0 {
|
||||
switch strings.ToLower(fileName[dotPos:]) {
|
||||
case tspath.ExtensionJs, tspath.ExtensionCjs, tspath.ExtensionMjs:
|
||||
return ScriptKindJS
|
||||
case tspath.ExtensionJsx:
|
||||
return ScriptKindJSX
|
||||
case tspath.ExtensionTs, tspath.ExtensionCts, tspath.ExtensionMts:
|
||||
return ScriptKindTS
|
||||
case tspath.ExtensionTsx:
|
||||
return ScriptKindTSX
|
||||
case tspath.ExtensionJson:
|
||||
return ScriptKindJSON
|
||||
}
|
||||
}
|
||||
return ScriptKindUnknown
|
||||
}
|
||||
|
||||
// Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
|
||||
// Names less than length 3 only check for case-insensitive equality.
|
||||
//
|
||||
// find the candidate with the smallest Levenshtein distance,
|
||||
//
|
||||
// except for candidates:
|
||||
// * With no name
|
||||
// * Whose length differs from the target name by more than 0.34 of the length of the name.
|
||||
// * Whose levenshtein distance is more than 0.4 of the length of the name
|
||||
// (0.4 allows 1 substitution/transposition for every 5 characters,
|
||||
// and 1 insertion/deletion at 3 characters)
|
||||
//
|
||||
// @internal
|
||||
func GetSpellingSuggestion[T any](name string, candidates iter.Seq[T], getName func(T) string, compare func(T, T) int) T {
|
||||
runeName := []rune(name)
|
||||
maximumLengthDifference := max(2, int(float64(len(runeName))*0.34))
|
||||
bestDistance := math.Floor(float64(len(runeName))*0.4) + 0.9 // If the best result is worse than this, don't bother.
|
||||
buffers := levenshteinBuffersPool.Get().(*levenshteinBuffers)
|
||||
defer levenshteinBuffersPool.Put(buffers)
|
||||
var bestCandidate T
|
||||
hasBest := false
|
||||
for candidate := range candidates {
|
||||
candidateName := getName(candidate)
|
||||
maxLen := max(len(candidateName), len(runeName))
|
||||
minLen := min(len(candidateName), len(runeName))
|
||||
if candidateName != "" && maxLen-minLen <= maximumLengthDifference {
|
||||
if candidateName == name {
|
||||
continue
|
||||
}
|
||||
// Only consider candidates less than 3 characters long when they differ by case.
|
||||
// Otherwise, don't bother, since a user would usually notice differences of a 2-character name.
|
||||
if len(candidateName) < 3 && !strings.EqualFold(candidateName, name) {
|
||||
continue
|
||||
}
|
||||
distance := levenshteinWithMax(buffers, runeName, []rune(candidateName), bestDistance)
|
||||
if distance < 0 {
|
||||
continue
|
||||
}
|
||||
debug.Assert(distance <= bestDistance) // Else `levenshteinWithMax` should return undefined
|
||||
if distance < bestDistance {
|
||||
bestDistance = distance
|
||||
bestCandidate = candidate
|
||||
hasBest = true
|
||||
} else if !hasBest || compare(candidate, bestCandidate) < 0 {
|
||||
bestCandidate = candidate
|
||||
hasBest = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestCandidate
|
||||
}
|
||||
|
||||
func GetSpellingSuggestionForStrings(name string, candidates iter.Seq[string]) string {
|
||||
return GetSpellingSuggestion(name, candidates, Identity, strings.Compare)
|
||||
}
|
||||
|
||||
type levenshteinBuffers struct {
|
||||
previous []float64
|
||||
current []float64
|
||||
}
|
||||
|
||||
var levenshteinBuffersPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &levenshteinBuffers{}
|
||||
},
|
||||
}
|
||||
|
||||
func levenshteinWithMax(buffers *levenshteinBuffers, s1 []rune, s2 []rune, maxValue float64) float64 {
|
||||
bufferSize := len(s2) + 1
|
||||
buffers.previous = slices.Grow(buffers.previous[:0], bufferSize)[:bufferSize]
|
||||
buffers.current = slices.Grow(buffers.current[:0], bufferSize)[:bufferSize]
|
||||
|
||||
previous := buffers.previous
|
||||
current := buffers.current
|
||||
|
||||
big := maxValue + 0.01
|
||||
for i := range previous {
|
||||
previous[i] = float64(i)
|
||||
}
|
||||
for i := 1; i <= len(s1); i++ {
|
||||
c1 := s1[i-1]
|
||||
minJ := max(int(math.Ceil(float64(i)-maxValue)), 1)
|
||||
maxJ := min(int(math.Floor(maxValue+float64(i))), len(s2))
|
||||
colMin := float64(i)
|
||||
current[0] = colMin
|
||||
for j := 1; j < minJ; j++ {
|
||||
current[j] = big
|
||||
}
|
||||
for j := minJ; j <= maxJ; j++ {
|
||||
var substitutionDistance, dist float64
|
||||
if unicode.ToLower(s1[i-1]) == unicode.ToLower(s2[j-1]) {
|
||||
substitutionDistance = previous[j-1] + 0.1
|
||||
} else {
|
||||
substitutionDistance = previous[j-1] + 2
|
||||
}
|
||||
if c1 == s2[j-1] {
|
||||
dist = previous[j-1]
|
||||
} else {
|
||||
dist = math.Min(previous[j]+1, math.Min(current[j-1]+1, substitutionDistance))
|
||||
}
|
||||
current[j] = dist
|
||||
colMin = math.Min(colMin, dist)
|
||||
}
|
||||
for j := maxJ + 1; j <= len(s2); j++ {
|
||||
current[j] = big
|
||||
}
|
||||
if colMin > maxValue {
|
||||
// Give up -- everything in this column is > max and it can't get better in future columns.
|
||||
return -1
|
||||
}
|
||||
previous, current = current, previous
|
||||
}
|
||||
res := previous[len(s2)]
|
||||
if res > maxValue {
|
||||
return -1
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func Identity[T any](t T) T {
|
||||
return t
|
||||
}
|
||||
|
||||
func CheckEachDefined[S any](s []*S, msg string) []*S {
|
||||
for _, value := range s {
|
||||
if value == nil {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func IndexAfter(s string, pattern string, startIndex int) int {
|
||||
matched := strings.Index(s[startIndex:], pattern)
|
||||
if matched == -1 {
|
||||
return -1
|
||||
} else {
|
||||
return matched + startIndex
|
||||
}
|
||||
}
|
||||
|
||||
func ShouldRewriteModuleSpecifier(specifier string, compilerOptions *CompilerOptions) bool {
|
||||
return compilerOptions.RewriteRelativeImportExtensions.IsTrue() && tspath.PathIsRelative(specifier) && !tspath.IsDeclarationFileName(specifier) && tspath.HasTSFileExtension(specifier)
|
||||
}
|
||||
|
||||
func SingleElementSlice[T any](element *T) []*T {
|
||||
if element == nil {
|
||||
return nil
|
||||
}
|
||||
return []*T{element}
|
||||
}
|
||||
|
||||
func ConcatenateSeq[T any](seqs ...iter.Seq[T]) iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for _, seq := range seqs {
|
||||
if seq == nil {
|
||||
continue
|
||||
}
|
||||
for e := range seq {
|
||||
if !yield(e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate returns a sequence of (index, value) pairs from the input sequence.
|
||||
func Enumerate[T any](seq iter.Seq[T]) iter.Seq2[int, T] {
|
||||
return func(yield func(int, T) bool) {
|
||||
i := 0
|
||||
for v := range seq {
|
||||
if !yield(i, v) {
|
||||
return
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func comparableValuesEqual[T comparable](a, b T) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// DiffMaps compares two maps m1 and m2 and calls the provided callbacks for added, removed, and changed entries.
|
||||
// onAdded is called for each key-value pair that is in m2 but not in m1.
|
||||
// onRemoved is called for each key-value pair that is in m1 but not in m2.
|
||||
// onChanged is called for each key where the value in m1 differs from the value in m2.
|
||||
func DiffMaps[K comparable, V comparable](m1 map[K]V, m2 map[K]V, onAdded func(K, V), onRemoved func(K, V), onChanged func(K, V, V)) {
|
||||
DiffMapsFunc(m1, m2, comparableValuesEqual, onAdded, onRemoved, onChanged)
|
||||
}
|
||||
|
||||
// DiffMapsFunc compares two maps m1 and m2 and calls the provided callbacks for added, removed, and changed entries.
|
||||
// onAdded is called for each key-value pair that is in m2 but not in m1.
|
||||
// onRemoved is called for each key-value pair that is in m1 but not in m2.
|
||||
// onChanged is called for each key where the value in m1 differs from the value in m2.
|
||||
func DiffMapsFunc[K comparable, V1 any, V2 any](m1 map[K]V1, m2 map[K]V2, equalValues func(V1, V2) bool, onAdded func(K, V2), onRemoved func(K, V1), onChanged func(K, V1, V2)) {
|
||||
if onAdded != nil {
|
||||
for k, v2 := range m2 {
|
||||
if _, ok := m1[k]; !ok {
|
||||
onAdded(k, v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
if onChanged == nil && onRemoved == nil {
|
||||
return
|
||||
}
|
||||
for k, v1 := range m1 {
|
||||
if v2, ok := m2[k]; ok {
|
||||
if onChanged != nil && !equalValues(v1, v2) {
|
||||
onChanged(k, v1, v2)
|
||||
}
|
||||
} else {
|
||||
onRemoved(k, v1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CopyMapInto is maps.Copy, unless dst is nil, in which case it clones and returns src.
|
||||
// Use CopyMapInto anywhere you would use maps.Copy preceded by a nil check and map initialization.
|
||||
func CopyMapInto[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) map[K]V {
|
||||
if dst == nil {
|
||||
return maps.Clone(src)
|
||||
}
|
||||
maps.Copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
// UnorderedEqual returns true if s1 and s2 contain the same elements, regardless of order.
|
||||
func UnorderedEqual[T comparable](s1 []T, s2 []T) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
counts := make(map[T]int)
|
||||
for _, v := range s1 {
|
||||
counts[v]++
|
||||
}
|
||||
for _, v := range s2 {
|
||||
counts[v]--
|
||||
if counts[v] < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Deduplicate[T comparable](slice []T) []T {
|
||||
if len(slice) > 1 {
|
||||
for i, value := range slice {
|
||||
if slices.Contains(slice[:i], value) {
|
||||
result := slices.Clone(slice[:i])
|
||||
for i++; i < len(slice); i++ {
|
||||
value = slice[i]
|
||||
if !slices.Contains(result, value) {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func DeduplicateSorted[T any](slice []T, isEqual func(a, b T) bool) []T {
|
||||
if len(slice) == 0 {
|
||||
return slice
|
||||
}
|
||||
last := slice[0]
|
||||
deduplicated := slice[:1]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
next := slice[i]
|
||||
if isEqual(last, next) {
|
||||
continue
|
||||
}
|
||||
|
||||
deduplicated = append(deduplicated, next)
|
||||
last = next
|
||||
}
|
||||
|
||||
return deduplicated
|
||||
}
|
||||
|
||||
// CompareBooleans treats true as greater than false.
|
||||
func CompareBooleans(a, b bool) int {
|
||||
if a && !b {
|
||||
return 1
|
||||
} else if !a && b {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
11
tools/tsgo/internal/core/languagevariant.go
Normal file
11
tools/tsgo/internal/core/languagevariant.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package core
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=LanguageVariant -output=languagevariant_stringer_generated.go
|
||||
//go:generate npx dprint fmt languagevariant_stringer_generated.go
|
||||
|
||||
type LanguageVariant int32
|
||||
|
||||
const (
|
||||
LanguageVariantStandard LanguageVariant = iota
|
||||
LanguageVariantJSX
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
// Code generated by "stringer -type=LanguageVariant -output=languagevariant_stringer_generated.go"; DO NOT EDIT.
|
||||
|
||||
package core
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[LanguageVariantStandard-0]
|
||||
_ = x[LanguageVariantJSX-1]
|
||||
}
|
||||
|
||||
const _LanguageVariant_name = "LanguageVariantStandardLanguageVariantJSX"
|
||||
|
||||
var _LanguageVariant_index = [...]uint8{0, 23, 41}
|
||||
|
||||
func (i LanguageVariant) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_LanguageVariant_index)-1 {
|
||||
return "LanguageVariant(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _LanguageVariant_name[_LanguageVariant_index[idx]:_LanguageVariant_index[idx+1]]
|
||||
}
|
||||
30
tools/tsgo/internal/core/linkstore.go
Normal file
30
tools/tsgo/internal/core/linkstore.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package core
|
||||
|
||||
// Links store
|
||||
|
||||
type LinkStore[K comparable, V any] struct {
|
||||
entries map[K]*V
|
||||
arena Arena[V]
|
||||
}
|
||||
|
||||
func (s *LinkStore[K, V]) Get(key K) *V {
|
||||
value := s.entries[key]
|
||||
if value != nil {
|
||||
return value
|
||||
}
|
||||
if s.entries == nil {
|
||||
s.entries = make(map[K]*V)
|
||||
}
|
||||
value = s.arena.New()
|
||||
s.entries[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *LinkStore[K, V]) Has(key K) bool {
|
||||
_, ok := s.entries[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *LinkStore[K, V]) TryGet(key K) *V {
|
||||
return s.entries[key]
|
||||
}
|
||||
52
tools/tsgo/internal/core/modulekind_stringer_generated.go
Normal file
52
tools/tsgo/internal/core/modulekind_stringer_generated.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Code generated by "stringer -type=ModuleKind -trimprefix=ModuleKind -output=modulekind_stringer_generated.go"; DO NOT EDIT.
|
||||
|
||||
package core
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ModuleKindNone-0]
|
||||
_ = x[ModuleKindCommonJS-1]
|
||||
_ = x[ModuleKindAMD-2]
|
||||
_ = x[ModuleKindUMD-3]
|
||||
_ = x[ModuleKindSystem-4]
|
||||
_ = x[ModuleKindES2015-5]
|
||||
_ = x[ModuleKindES2020-6]
|
||||
_ = x[ModuleKindES2022-7]
|
||||
_ = x[ModuleKindESNext-99]
|
||||
_ = x[ModuleKindNode16-100]
|
||||
_ = x[ModuleKindNode18-101]
|
||||
_ = x[ModuleKindNode20-102]
|
||||
_ = x[ModuleKindNodeNext-199]
|
||||
_ = x[ModuleKindPreserve-200]
|
||||
}
|
||||
|
||||
const (
|
||||
_ModuleKind_name_0 = "NoneCommonJSAMDUMDSystemES2015ES2020ES2022"
|
||||
_ModuleKind_name_1 = "ESNextNode16Node18Node20"
|
||||
_ModuleKind_name_2 = "NodeNextPreserve"
|
||||
)
|
||||
|
||||
var (
|
||||
_ModuleKind_index_0 = [...]uint8{0, 4, 12, 15, 18, 24, 30, 36, 42}
|
||||
_ModuleKind_index_1 = [...]uint8{0, 6, 12, 18, 24}
|
||||
_ModuleKind_index_2 = [...]uint8{0, 8, 16}
|
||||
)
|
||||
|
||||
func (i ModuleKind) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 7:
|
||||
return _ModuleKind_name_0[_ModuleKind_index_0[i]:_ModuleKind_index_0[i+1]]
|
||||
case 99 <= i && i <= 102:
|
||||
i -= 99
|
||||
return _ModuleKind_name_1[_ModuleKind_index_1[i]:_ModuleKind_index_1[i+1]]
|
||||
case 199 <= i && i <= 200:
|
||||
i -= 199
|
||||
return _ModuleKind_name_2[_ModuleKind_index_2[i]:_ModuleKind_index_2[i+1]]
|
||||
default:
|
||||
return "ModuleKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
90
tools/tsgo/internal/core/nodemodules.go
Normal file
90
tools/tsgo/internal/core/nodemodules.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// require('module').builtinModules.filter(x => !x.match(/^(?:_|node:)/))
|
||||
var UnprefixedNodeCoreModules = map[string]bool{
|
||||
"assert": true,
|
||||
"assert/strict": true,
|
||||
"async_hooks": true,
|
||||
"buffer": true,
|
||||
"child_process": true,
|
||||
"cluster": true,
|
||||
"console": true,
|
||||
"constants": true,
|
||||
"crypto": true,
|
||||
"dgram": true,
|
||||
"diagnostics_channel": true,
|
||||
"dns": true,
|
||||
"dns/promises": true,
|
||||
"domain": true,
|
||||
"events": true,
|
||||
"fs": true,
|
||||
"fs/promises": true,
|
||||
"http": true,
|
||||
"http2": true,
|
||||
"https": true,
|
||||
"inspector": true,
|
||||
"inspector/promises": true,
|
||||
"module": true,
|
||||
"net": true,
|
||||
"os": true,
|
||||
"path": true,
|
||||
"path/posix": true,
|
||||
"path/win32": true,
|
||||
"perf_hooks": true,
|
||||
"process": true,
|
||||
"punycode": true,
|
||||
"querystring": true,
|
||||
"readline": true,
|
||||
"readline/promises": true,
|
||||
"repl": true,
|
||||
"stream": true,
|
||||
"stream/consumers": true,
|
||||
"stream/promises": true,
|
||||
"stream/web": true,
|
||||
"string_decoder": true,
|
||||
"sys": true,
|
||||
"timers": true,
|
||||
"timers/promises": true,
|
||||
"tls": true,
|
||||
"trace_events": true,
|
||||
"tty": true,
|
||||
"url": true,
|
||||
"util": true,
|
||||
"util/types": true,
|
||||
"v8": true,
|
||||
"vm": true,
|
||||
"wasi": true,
|
||||
"worker_threads": true,
|
||||
"zlib": true,
|
||||
}
|
||||
|
||||
// require('module').builtinModules.filter(x => x.startsWith('node:'))
|
||||
var ExclusivelyPrefixedNodeCoreModules = map[string]bool{
|
||||
"node:quic": true,
|
||||
"node:sea": true,
|
||||
"node:sqlite": true,
|
||||
"node:test": true,
|
||||
"node:test/reporters": true,
|
||||
}
|
||||
|
||||
var NodeCoreModules = sync.OnceValue(func() map[string]bool {
|
||||
nodeCoreModules := make(map[string]bool, len(UnprefixedNodeCoreModules)*2+len(ExclusivelyPrefixedNodeCoreModules))
|
||||
for unprefixed := range UnprefixedNodeCoreModules {
|
||||
nodeCoreModules[unprefixed] = true
|
||||
nodeCoreModules["node:"+unprefixed] = true
|
||||
}
|
||||
maps.Copy(nodeCoreModules, ExclusivelyPrefixedNodeCoreModules)
|
||||
return nodeCoreModules
|
||||
})
|
||||
|
||||
func NonRelativeModuleNameForTypingCache(moduleName string) string {
|
||||
if NodeCoreModules()[moduleName] {
|
||||
return "node"
|
||||
}
|
||||
return moduleName
|
||||
}
|
||||
10
tools/tsgo/internal/core/parsedoptions.go
Normal file
10
tools/tsgo/internal/core/parsedoptions.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package core
|
||||
|
||||
type ParsedOptions struct {
|
||||
CompilerOptions *CompilerOptions `json:"compilerOptions"`
|
||||
WatchOptions *WatchOptions `json:"watchOptions"`
|
||||
TypeAcquisition *TypeAcquisition `json:"typeAcquisition"`
|
||||
|
||||
FileNames []string `json:"fileNames"`
|
||||
ProjectReferences []*ProjectReference `json:"projectReferences"`
|
||||
}
|
||||
52
tools/tsgo/internal/core/pattern.go
Normal file
52
tools/tsgo/internal/core/pattern.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package core
|
||||
|
||||
import "strings"
|
||||
|
||||
type Pattern struct {
|
||||
Text string
|
||||
StarIndex int // -1 for exact match
|
||||
}
|
||||
|
||||
func TryParsePattern(pattern string) Pattern {
|
||||
starIndex := strings.Index(pattern, "*")
|
||||
if starIndex == -1 || !strings.Contains(pattern[starIndex+1:], "*") {
|
||||
return Pattern{Text: pattern, StarIndex: starIndex}
|
||||
}
|
||||
return Pattern{}
|
||||
}
|
||||
|
||||
func (p *Pattern) IsValid() bool {
|
||||
return p.StarIndex == -1 || p.StarIndex < len(p.Text)
|
||||
}
|
||||
|
||||
func (p *Pattern) Matches(candidate string) bool {
|
||||
if p.StarIndex == -1 {
|
||||
return p.Text == candidate
|
||||
}
|
||||
return len(candidate) >= len(p.Text)-1 &&
|
||||
strings.HasPrefix(candidate, p.Text[:p.StarIndex]) &&
|
||||
strings.HasSuffix(candidate, p.Text[p.StarIndex+1:])
|
||||
}
|
||||
|
||||
func (p *Pattern) MatchedText(candidate string) string {
|
||||
if !p.Matches(candidate) {
|
||||
panic("candidate does not match pattern")
|
||||
}
|
||||
if p.StarIndex == -1 {
|
||||
return ""
|
||||
}
|
||||
return candidate[p.StarIndex : len(candidate)-len(p.Text)+p.StarIndex+1]
|
||||
}
|
||||
|
||||
func FindBestPatternMatch[T any](values []T, getPattern func(v T) Pattern, candidate string) T {
|
||||
var bestPattern T
|
||||
longestMatchPrefixLength := -1
|
||||
for _, value := range values {
|
||||
pattern := getPattern(value)
|
||||
if (pattern.StarIndex == -1 || pattern.StarIndex > longestMatchPrefixLength) && pattern.Matches(candidate) {
|
||||
bestPattern = value
|
||||
longestMatchPrefixLength = pattern.StarIndex
|
||||
}
|
||||
}
|
||||
return bestPattern
|
||||
}
|
||||
24
tools/tsgo/internal/core/pattern_test.go
Normal file
24
tools/tsgo/internal/core/pattern_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package core
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPatternOverlappingMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := TryParsePattern("ab*ab")
|
||||
if p.Matches("ab") {
|
||||
t.Errorf("expected 'ab' not to match 'ab*ab'")
|
||||
}
|
||||
if !p.Matches("abXab") {
|
||||
t.Errorf("expected 'abXab' to match 'ab*ab'")
|
||||
}
|
||||
if got := p.MatchedText("abXab"); got != "X" {
|
||||
t.Errorf("MatchedText = %q, want %q", got, "X")
|
||||
}
|
||||
if !p.Matches("abab") {
|
||||
t.Errorf("expected 'abab' to match 'ab*ab'")
|
||||
}
|
||||
if got := p.MatchedText("abab"); got != "" {
|
||||
t.Errorf("MatchedText = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
20
tools/tsgo/internal/core/projectreference.go
Normal file
20
tools/tsgo/internal/core/projectreference.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package core
|
||||
|
||||
import "github.com/microsoft/typescript-go/internal/tspath"
|
||||
|
||||
type ProjectReference struct {
|
||||
Path string
|
||||
OriginalPath string
|
||||
Circular bool
|
||||
}
|
||||
|
||||
func ResolveProjectReferencePath(ref *ProjectReference) string {
|
||||
return ResolveConfigFileNameOfProjectReference(ref.Path)
|
||||
}
|
||||
|
||||
func ResolveConfigFileNameOfProjectReference(path string) string {
|
||||
if tspath.FileExtensionIs(path, tspath.ExtensionJson) {
|
||||
return path
|
||||
}
|
||||
return tspath.CombinePaths(path, "tsconfig.json")
|
||||
}
|
||||
21
tools/tsgo/internal/core/scriptkind.go
Normal file
21
tools/tsgo/internal/core/scriptkind.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package core
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptKind -output=scriptkind_stringer_generated.go
|
||||
//go:generate npx dprint fmt scriptkind_stringer_generated.go
|
||||
|
||||
type ScriptKind int32
|
||||
|
||||
const (
|
||||
ScriptKindUnknown ScriptKind = iota
|
||||
ScriptKindJS
|
||||
ScriptKindJSX
|
||||
ScriptKindTS
|
||||
ScriptKindTSX
|
||||
ScriptKindExternal
|
||||
ScriptKindJSON
|
||||
/**
|
||||
* Used on extensions that doesn't define the ScriptKind but the content defines it.
|
||||
* Deferred extensions are going to be included in all project contexts.
|
||||
*/
|
||||
ScriptKindDeferred
|
||||
)
|
||||
31
tools/tsgo/internal/core/scriptkind_stringer_generated.go
Normal file
31
tools/tsgo/internal/core/scriptkind_stringer_generated.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Code generated by "stringer -type=ScriptKind -output=scriptkind_stringer_generated.go"; DO NOT EDIT.
|
||||
|
||||
package core
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ScriptKindUnknown-0]
|
||||
_ = x[ScriptKindJS-1]
|
||||
_ = x[ScriptKindJSX-2]
|
||||
_ = x[ScriptKindTS-3]
|
||||
_ = x[ScriptKindTSX-4]
|
||||
_ = x[ScriptKindExternal-5]
|
||||
_ = x[ScriptKindJSON-6]
|
||||
_ = x[ScriptKindDeferred-7]
|
||||
}
|
||||
|
||||
const _ScriptKind_name = "ScriptKindUnknownScriptKindJSScriptKindJSXScriptKindTSScriptKindTSXScriptKindExternalScriptKindJSONScriptKindDeferred"
|
||||
|
||||
var _ScriptKind_index = [...]uint8{0, 17, 29, 42, 54, 67, 85, 99, 117}
|
||||
|
||||
func (i ScriptKind) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_ScriptKind_index)-1 {
|
||||
return "ScriptKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ScriptKind_name[_ScriptKind_index[idx]:_ScriptKind_index[idx+1]]
|
||||
}
|
||||
50
tools/tsgo/internal/core/scripttarget_stringer_generated.go
Normal file
50
tools/tsgo/internal/core/scripttarget_stringer_generated.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Code generated by "stringer -type=ScriptTarget -trimprefix=ScriptTarget -output=scripttarget_stringer_generated.go"; DO NOT EDIT.
|
||||
|
||||
package core
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ScriptTargetNone-0]
|
||||
_ = x[ScriptTargetES5-1]
|
||||
_ = x[ScriptTargetES2015-2]
|
||||
_ = x[ScriptTargetES2016-3]
|
||||
_ = x[ScriptTargetES2017-4]
|
||||
_ = x[ScriptTargetES2018-5]
|
||||
_ = x[ScriptTargetES2019-6]
|
||||
_ = x[ScriptTargetES2020-7]
|
||||
_ = x[ScriptTargetES2021-8]
|
||||
_ = x[ScriptTargetES2022-9]
|
||||
_ = x[ScriptTargetES2023-10]
|
||||
_ = x[ScriptTargetES2024-11]
|
||||
_ = x[ScriptTargetES2025-12]
|
||||
_ = x[ScriptTargetESNext-99]
|
||||
_ = x[ScriptTargetJSON-100]
|
||||
_ = x[ScriptTargetLatest-99]
|
||||
_ = x[ScriptTargetLatestStandard-12]
|
||||
}
|
||||
|
||||
const (
|
||||
_ScriptTarget_name_0 = "NoneES5ES2015ES2016ES2017ES2018ES2019ES2020ES2021ES2022ES2023ES2024ES2025"
|
||||
_ScriptTarget_name_1 = "ESNextJSON"
|
||||
)
|
||||
|
||||
var (
|
||||
_ScriptTarget_index_0 = [...]uint8{0, 4, 7, 13, 19, 25, 31, 37, 43, 49, 55, 61, 67, 73}
|
||||
_ScriptTarget_index_1 = [...]uint8{0, 6, 10}
|
||||
)
|
||||
|
||||
func (i ScriptTarget) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 12:
|
||||
return _ScriptTarget_name_0[_ScriptTarget_index_0[i]:_ScriptTarget_index_0[i+1]]
|
||||
case 99 <= i && i <= 100:
|
||||
i -= 99
|
||||
return _ScriptTarget_name_1[_ScriptTarget_index_1[i]:_ScriptTarget_index_1[i+1]]
|
||||
default:
|
||||
return "ScriptTarget(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
52
tools/tsgo/internal/core/semaphore.go
Normal file
52
tools/tsgo/internal/core/semaphore.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package core
|
||||
|
||||
import "context"
|
||||
|
||||
type Semaphore interface {
|
||||
Acquire() (release func())
|
||||
TryAcquire(ctx context.Context) (release func(), acquired bool)
|
||||
}
|
||||
|
||||
var _ Semaphore = UnlimitedSemaphore{}
|
||||
|
||||
type UnlimitedSemaphore struct{}
|
||||
|
||||
func (s UnlimitedSemaphore) Acquire() (release func()) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
func (s UnlimitedSemaphore) TryAcquire(ctx context.Context) (release func(), acquired bool) {
|
||||
return func() {}, true
|
||||
}
|
||||
|
||||
var _ Semaphore = (*LimitedSemaphore)(nil)
|
||||
|
||||
type LimitedSemaphore struct {
|
||||
ch chan struct{}
|
||||
release func()
|
||||
}
|
||||
|
||||
func NewLimitedSemaphore(maxConcurrency int) *LimitedSemaphore {
|
||||
if maxConcurrency <= 0 {
|
||||
panic("maxConcurrency must be positive")
|
||||
}
|
||||
s := &LimitedSemaphore{
|
||||
ch: make(chan struct{}, maxConcurrency),
|
||||
}
|
||||
s.release = func() { <-s.ch }
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *LimitedSemaphore) Acquire() (release func()) {
|
||||
s.ch <- struct{}{}
|
||||
return s.release
|
||||
}
|
||||
|
||||
func (s *LimitedSemaphore) TryAcquire(ctx context.Context) (release func(), acquired bool) {
|
||||
select {
|
||||
case s.ch <- struct{}{}:
|
||||
return s.release, true
|
||||
case <-ctx.Done():
|
||||
return func() {}, false
|
||||
}
|
||||
}
|
||||
33
tools/tsgo/internal/core/stack.go
Normal file
33
tools/tsgo/internal/core/stack.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package core
|
||||
|
||||
type Stack[T any] struct {
|
||||
data []T
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Push(item T) {
|
||||
s.data = append(s.data, item)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Pop() T {
|
||||
l := len(s.data)
|
||||
if l == 0 {
|
||||
panic("stack is empty")
|
||||
}
|
||||
item := s.data[l-1]
|
||||
var zero T
|
||||
s.data[l-1] = zero
|
||||
s.data = s.data[:l-1]
|
||||
return item
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Peek() T {
|
||||
l := len(s.data)
|
||||
if l == 0 {
|
||||
panic("stack is empty")
|
||||
}
|
||||
return s.data[l-1]
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Len() int {
|
||||
return len(s.data)
|
||||
}
|
||||
82
tools/tsgo/internal/core/text.go
Normal file
82
tools/tsgo/internal/core/text.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package core
|
||||
|
||||
// TextPos
|
||||
|
||||
type TextPos int32
|
||||
|
||||
// TextRange
|
||||
|
||||
type TextRange struct {
|
||||
pos TextPos
|
||||
end TextPos
|
||||
}
|
||||
|
||||
func NewTextRange(pos int, end int) TextRange {
|
||||
return TextRange{pos: TextPos(pos), end: TextPos(end)}
|
||||
}
|
||||
|
||||
func UndefinedTextRange() TextRange {
|
||||
return TextRange{pos: TextPos(-1), end: TextPos(-1)}
|
||||
}
|
||||
|
||||
func (t TextRange) Pos() int {
|
||||
return int(t.pos)
|
||||
}
|
||||
|
||||
func (t TextRange) End() int {
|
||||
return int(t.end)
|
||||
}
|
||||
|
||||
func (t TextRange) Len() int {
|
||||
return int(t.end - t.pos)
|
||||
}
|
||||
|
||||
func (t TextRange) IsValid() bool {
|
||||
return t.pos >= 0 || t.end >= 0
|
||||
}
|
||||
|
||||
func (t TextRange) Contains(pos int) bool {
|
||||
return pos >= int(t.pos) && pos < int(t.end)
|
||||
}
|
||||
|
||||
func (t TextRange) ContainsInclusive(pos int) bool {
|
||||
return pos >= int(t.pos) && pos <= int(t.end)
|
||||
}
|
||||
|
||||
func (t TextRange) ContainsExclusive(pos int) bool {
|
||||
return int(t.pos) < pos && pos < int(t.end)
|
||||
}
|
||||
|
||||
func (t TextRange) WithPos(pos int) TextRange {
|
||||
return TextRange{pos: TextPos(pos), end: t.end}
|
||||
}
|
||||
|
||||
func (t TextRange) WithEnd(end int) TextRange {
|
||||
return TextRange{pos: t.pos, end: TextPos(end)}
|
||||
}
|
||||
|
||||
func (t TextRange) ContainedBy(t2 TextRange) bool {
|
||||
return t2.pos <= t.pos && t2.end >= t.end
|
||||
}
|
||||
|
||||
func (t TextRange) Overlaps(t2 TextRange) bool {
|
||||
start := max(t.pos, t2.pos)
|
||||
end := min(t.end, t2.end)
|
||||
return start < end
|
||||
}
|
||||
|
||||
// Similar to Overlaps, but treats touching ranges as intersecting.
|
||||
// For example, [0, 5) intersects [5, 10).
|
||||
func (t TextRange) Intersects(t2 TextRange) bool {
|
||||
start := max(t.pos, t2.pos)
|
||||
end := min(t.end, t2.end)
|
||||
return start <= end
|
||||
}
|
||||
|
||||
func CompareTextRanges(r1 TextRange, r2 TextRange) int {
|
||||
c := int(r1.pos) - int(r2.pos)
|
||||
if c != 0 {
|
||||
return c
|
||||
}
|
||||
return int(r1.end) - int(r2.end)
|
||||
}
|
||||
30
tools/tsgo/internal/core/textchange.go
Normal file
30
tools/tsgo/internal/core/textchange.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package core
|
||||
|
||||
import "strings"
|
||||
|
||||
type TextChange struct {
|
||||
TextRange
|
||||
NewText string
|
||||
}
|
||||
|
||||
func (t TextChange) ApplyTo(text string) string {
|
||||
return text[:t.Pos()] + t.NewText + text[t.End():]
|
||||
}
|
||||
|
||||
func ApplyBulkEdits(text string, edits []TextChange) string {
|
||||
b := strings.Builder{}
|
||||
b.Grow(len(text))
|
||||
lastEnd := 0
|
||||
for _, e := range edits {
|
||||
start := e.TextRange.Pos()
|
||||
if start != lastEnd {
|
||||
b.WriteString(text[lastEnd:e.TextRange.Pos()])
|
||||
}
|
||||
b.WriteString(e.NewText)
|
||||
|
||||
lastEnd = e.TextRange.End()
|
||||
}
|
||||
b.WriteString(text[lastEnd:])
|
||||
|
||||
return b.String()
|
||||
}
|
||||
71
tools/tsgo/internal/core/tristate.go
Normal file
71
tools/tsgo/internal/core/tristate.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package core
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Tristate -output=tristate_stringer_generated.go
|
||||
//go:generate npx dprint fmt tristate_stringer_generated.go
|
||||
|
||||
// Tristate
|
||||
|
||||
type Tristate byte
|
||||
|
||||
const (
|
||||
TSUnknown Tristate = iota
|
||||
TSFalse
|
||||
TSTrue
|
||||
)
|
||||
|
||||
func (t Tristate) IsTrue() bool {
|
||||
return t == TSTrue
|
||||
}
|
||||
|
||||
func (t Tristate) IsTrueOrUnknown() bool {
|
||||
return t == TSTrue || t == TSUnknown
|
||||
}
|
||||
|
||||
func (t Tristate) IsFalse() bool {
|
||||
return t == TSFalse
|
||||
}
|
||||
|
||||
func (t Tristate) IsFalseOrUnknown() bool {
|
||||
return t == TSFalse || t == TSUnknown
|
||||
}
|
||||
|
||||
func (t Tristate) IsUnknown() bool {
|
||||
return t == TSUnknown
|
||||
}
|
||||
|
||||
func (t Tristate) DefaultIfUnknown(value Tristate) Tristate {
|
||||
if t == TSUnknown {
|
||||
return value
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Tristate) UnmarshalJSON(data []byte) error {
|
||||
switch string(data) {
|
||||
case "true":
|
||||
*t = TSTrue
|
||||
case "false":
|
||||
*t = TSFalse
|
||||
default:
|
||||
*t = TSUnknown
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Tristate) MarshalJSON() ([]byte, error) {
|
||||
switch t {
|
||||
case TSTrue:
|
||||
return []byte("true"), nil
|
||||
case TSFalse:
|
||||
return []byte("false"), nil
|
||||
default:
|
||||
return []byte("null"), nil
|
||||
}
|
||||
}
|
||||
|
||||
func BoolToTristate(b bool) Tristate {
|
||||
if b {
|
||||
return TSTrue
|
||||
}
|
||||
return TSFalse
|
||||
}
|
||||
26
tools/tsgo/internal/core/tristate_stringer_generated.go
Normal file
26
tools/tsgo/internal/core/tristate_stringer_generated.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Code generated by "stringer -type=Tristate -output=tristate_stringer_generated.go"; DO NOT EDIT.
|
||||
|
||||
package core
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TSUnknown-0]
|
||||
_ = x[TSFalse-1]
|
||||
_ = x[TSTrue-2]
|
||||
}
|
||||
|
||||
const _Tristate_name = "TSUnknownTSFalseTSTrue"
|
||||
|
||||
var _Tristate_index = [...]uint8{0, 9, 16, 22}
|
||||
|
||||
func (i Tristate) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_Tristate_index)-1 {
|
||||
return "Tristate(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Tristate_name[_Tristate_index[idx]:_Tristate_index[idx+1]]
|
||||
}
|
||||
24
tools/tsgo/internal/core/typeacquisition.go
Normal file
24
tools/tsgo/internal/core/typeacquisition.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package core
|
||||
|
||||
import "slices"
|
||||
|
||||
type TypeAcquisition struct {
|
||||
Enable Tristate `json:"enable,omitzero"`
|
||||
Include []string `json:"include,omitzero"`
|
||||
Exclude []string `json:"exclude,omitzero"`
|
||||
DisableFilenameBasedTypeAcquisition Tristate `json:"disableFilenameBasedTypeAcquisition,omitzero"`
|
||||
}
|
||||
|
||||
func (ta *TypeAcquisition) Equals(other *TypeAcquisition) bool {
|
||||
if ta == other {
|
||||
return true
|
||||
}
|
||||
if ta == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return (ta.Enable == other.Enable &&
|
||||
slices.Equal(ta.Include, other.Include) &&
|
||||
slices.Equal(ta.Exclude, other.Exclude) &&
|
||||
ta.DisableFilenameBasedTypeAcquisition == other.DisableFilenameBasedTypeAcquisition)
|
||||
}
|
||||
33
tools/tsgo/internal/core/version.go
Normal file
33
tools/tsgo/internal/core/version.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This is a var so it can be overridden by ldflags.
|
||||
var version = "7.1.0-dev"
|
||||
|
||||
func Version() string {
|
||||
return version
|
||||
}
|
||||
|
||||
var versionMajorMinor = func() string {
|
||||
seenMajor := false
|
||||
i := strings.IndexFunc(version, func(r rune) bool {
|
||||
if r == '.' {
|
||||
if seenMajor {
|
||||
return true
|
||||
}
|
||||
seenMajor = true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if i == -1 {
|
||||
panic("invalid version string: " + version)
|
||||
}
|
||||
return version[:i]
|
||||
}()
|
||||
|
||||
func VersionMajorMinor() string {
|
||||
return versionMajorMinor
|
||||
}
|
||||
53
tools/tsgo/internal/core/watchoptions.go
Normal file
53
tools/tsgo/internal/core/watchoptions.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
type WatchOptions struct {
|
||||
Interval *int `json:"watchInterval"`
|
||||
FileKind WatchFileKind `json:"watchFile"`
|
||||
DirectoryKind WatchDirectoryKind `json:"watchDirectory"`
|
||||
FallbackPolling PollingKind `json:"fallbackPolling"`
|
||||
SyncWatchDir Tristate `json:"synchronousWatchDirectory"`
|
||||
ExcludeDir []string `json:"excludeDirectories"`
|
||||
ExcludeFiles []string `json:"excludeFiles"`
|
||||
}
|
||||
|
||||
type WatchFileKind int32
|
||||
|
||||
const (
|
||||
WatchFileKindNone WatchFileKind = 0
|
||||
WatchFileKindFixedPollingInterval WatchFileKind = 1
|
||||
WatchFileKindPriorityPollingInterval WatchFileKind = 2
|
||||
WatchFileKindDynamicPriorityPolling WatchFileKind = 3
|
||||
WatchFileKindFixedChunkSizePolling WatchFileKind = 4
|
||||
WatchFileKindUseFsEvents WatchFileKind = 5
|
||||
WatchFileKindUseFsEventsOnParentDirectory WatchFileKind = 6
|
||||
)
|
||||
|
||||
type WatchDirectoryKind int32
|
||||
|
||||
const (
|
||||
WatchDirectoryKindNone WatchDirectoryKind = 0
|
||||
WatchDirectoryKindUseFsEvents WatchDirectoryKind = 1
|
||||
WatchDirectoryKindFixedPollingInterval WatchDirectoryKind = 2
|
||||
WatchDirectoryKindDynamicPriorityPolling WatchDirectoryKind = 3
|
||||
WatchDirectoryKindFixedChunkSizePolling WatchDirectoryKind = 4
|
||||
)
|
||||
|
||||
type PollingKind int32
|
||||
|
||||
const (
|
||||
PollingKindNone PollingKind = 0
|
||||
PollingKindFixedInterval PollingKind = 1
|
||||
PollingKindPriorityInterval PollingKind = 2
|
||||
PollingKindDynamicPriority PollingKind = 3
|
||||
PollingKindFixedChunkSize PollingKind = 4
|
||||
)
|
||||
|
||||
func (w *WatchOptions) WatchInterval() time.Duration {
|
||||
watchInterval := 2000 * time.Millisecond
|
||||
if w != nil && w.Interval != nil {
|
||||
watchInterval = time.Duration(*w.Interval) * time.Millisecond
|
||||
}
|
||||
return watchInterval
|
||||
}
|
||||
123
tools/tsgo/internal/core/workgroup.go
Normal file
123
tools/tsgo/internal/core/workgroup.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type WorkGroup interface {
|
||||
// Queue queues a function to run. It may be invoked immediately, or deferred until RunAndWait.
|
||||
// It is not safe to call Queue after RunAndWait has returned.
|
||||
Queue(fn func())
|
||||
|
||||
// RunAndWait runs all queued functions, blocking until they have all completed.
|
||||
RunAndWait()
|
||||
}
|
||||
|
||||
func NewWorkGroup(singleThreaded bool) WorkGroup {
|
||||
if singleThreaded {
|
||||
return &singleThreadedWorkGroup{}
|
||||
}
|
||||
return ¶llelWorkGroup{}
|
||||
}
|
||||
|
||||
type parallelWorkGroup struct {
|
||||
done atomic.Bool
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
var _ WorkGroup = (*parallelWorkGroup)(nil)
|
||||
|
||||
func (w *parallelWorkGroup) Queue(fn func()) {
|
||||
if w.done.Load() {
|
||||
panic("Queue called after RunAndWait returned")
|
||||
}
|
||||
|
||||
w.wg.Go(func() {
|
||||
fn()
|
||||
})
|
||||
}
|
||||
|
||||
func (w *parallelWorkGroup) RunAndWait() {
|
||||
defer w.done.Store(true)
|
||||
w.wg.Wait()
|
||||
}
|
||||
|
||||
type singleThreadedWorkGroup struct {
|
||||
done atomic.Bool
|
||||
fnsMu sync.Mutex
|
||||
fns []func()
|
||||
}
|
||||
|
||||
var _ WorkGroup = (*singleThreadedWorkGroup)(nil)
|
||||
|
||||
func (w *singleThreadedWorkGroup) Queue(fn func()) {
|
||||
if w.done.Load() {
|
||||
panic("Queue called after RunAndWait returned")
|
||||
}
|
||||
|
||||
w.fnsMu.Lock()
|
||||
defer w.fnsMu.Unlock()
|
||||
w.fns = append(w.fns, fn)
|
||||
}
|
||||
|
||||
func (w *singleThreadedWorkGroup) RunAndWait() {
|
||||
defer w.done.Store(true)
|
||||
for {
|
||||
fn := w.pop()
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *singleThreadedWorkGroup) pop() func() {
|
||||
w.fnsMu.Lock()
|
||||
defer w.fnsMu.Unlock()
|
||||
if len(w.fns) == 0 {
|
||||
return nil
|
||||
}
|
||||
end := len(w.fns) - 1
|
||||
fn := w.fns[end]
|
||||
w.fns[end] = nil // Allow GC
|
||||
w.fns = w.fns[:end]
|
||||
return fn
|
||||
}
|
||||
|
||||
// ThrottleGroup is like errgroup.Group but with global concurrency limiting via a semaphore.
|
||||
type ThrottleGroup struct {
|
||||
semaphore chan struct{}
|
||||
group *errgroup.Group
|
||||
}
|
||||
|
||||
// NewThrottleGroup creates a new ThrottleGroup with the given context and semaphore for concurrency limiting.
|
||||
func NewThrottleGroup(ctx context.Context, semaphore chan struct{}) *ThrottleGroup {
|
||||
g, _ := errgroup.WithContext(ctx)
|
||||
return &ThrottleGroup{
|
||||
semaphore: semaphore,
|
||||
group: g,
|
||||
}
|
||||
}
|
||||
|
||||
// Go runs the given function in a new goroutine, but first acquires a slot from the semaphore.
|
||||
// The semaphore slot is released when the function completes.
|
||||
func (tg *ThrottleGroup) Go(fn func() error) {
|
||||
tg.group.Go(func() error {
|
||||
// Acquire semaphore slot - this will block until a slot is available
|
||||
tg.semaphore <- struct{}{}
|
||||
defer func() {
|
||||
// Release semaphore slot when done
|
||||
<-tg.semaphore
|
||||
}()
|
||||
return fn()
|
||||
})
|
||||
}
|
||||
|
||||
// Wait waits for all goroutines to complete and returns the first error encountered, if any.
|
||||
func (tg *ThrottleGroup) Wait() error {
|
||||
return tg.group.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user