vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View 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
}

View 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
}
}
}

View 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"})
})
}

View 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
}

View 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"`
}

View 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")
}
}

View 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
}

View 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
}

View 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
)

View File

@@ -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]]
}

View 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]
}

View 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) + ")"
}
}

View 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
}

View 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"`
}

View 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
}

View 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)
}
}

View 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")
}

View 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
)

View 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]]
}

View 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) + ")"
}
}

View 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
}
}

View 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)
}

View 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)
}

View 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()
}

View 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
}

View 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]]
}

View 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)
}

View 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
}

View 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
}

View 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 &parallelWorkGroup{}
}
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()
}