vendor tsgo
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user