Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion microsoft/typescript-go
Submodule typescript-go updated 252 files
2 changes: 1 addition & 1 deletion pkg/ast/parseoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func walkTreeForJSXTags(node *Node) *Node {
if node.SubtreeFacts()&SubtreeContainsJsx == 0 {
return false
}
if IsJsxOpeningElement(node) || IsJsxFragment(node) {
if IsJsxOpeningLikeElement(node) || IsJsxFragment(node) {
found = node
return true
}
Expand Down
50 changes: 48 additions & 2 deletions pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,8 +614,10 @@ type Checker struct {
evaluate evaluator.Evaluator
stringLiteralTypes map[string]*Type
numberLiteralTypes map[jsnum.Number]*Type
nanType *Type
bigintLiteralTypes map[jsnum.PseudoBigInt]*Type
enumLiteralTypes map[EnumLiteralKey]*Type
enumNaNLiteralTypes map[*ast.Symbol]*Type
indexedAccessTypes map[CacheHashKey]*Type
templateLiteralTypes map[CacheHashKey]*Type
stringMappingTypes map[StringMappingKey]*Type
Expand Down Expand Up @@ -924,6 +926,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) {
c.numberLiteralTypes = make(map[jsnum.Number]*Type)
c.bigintLiteralTypes = make(map[jsnum.PseudoBigInt]*Type)
c.enumLiteralTypes = make(map[EnumLiteralKey]*Type)
c.enumNaNLiteralTypes = make(map[*ast.Symbol]*Type)
c.indexedAccessTypes = make(map[CacheHashKey]*Type)
c.templateLiteralTypes = make(map[CacheHashKey]*Type)
c.stringMappingTypes = make(map[StringMappingKey]*Type)
Expand Down Expand Up @@ -10402,6 +10405,7 @@ func (c *Checker) checkCollisionsForDeclarationName(node *ast.Node, name *ast.No
return
}
c.checkCollisionWithRequireExportsInGeneratedCode(node, name)
c.checkCollisionWithGlobalObjectInGeneratedCode(node, name)
c.checkCollisionWithGlobalPromiseInGeneratedCode(node, name)
c.recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name)
c.recordPotentialCollisionWithReflectInGeneratedCode(node, name)
Expand Down Expand Up @@ -10435,6 +10439,22 @@ func (c *Checker) checkCollisionWithRequireExportsInGeneratedCode(node *ast.Node
}
}

func (c *Checker) checkCollisionWithGlobalObjectInGeneratedCode(node *ast.Node, name *ast.Node) {
if name == nil || ast.IsClassLike(node) || !c.needCollisionCheckForIdentifier(node, name, "Object") {
return
}
// Uninstantiated modules shouldn't do this check
if ast.IsModuleDeclaration(node) && ast.GetModuleInstanceState(node) != ast.ModuleInstanceStateInstantiated {
return
}
// In case of variable declaration, node.parent is variable statement so look at the variable statement's parent
parent := ast.GetDeclarationContainer(node)
if ast.IsSourceFile(parent) && ast.IsExternalOrCommonJSModule(parent.AsSourceFile()) && c.program.GetEmitModuleFormatOfFile(parent.AsSourceFile()) == core.ModuleKindCommonJS {
// If the declaration happens to be in external module, report error that Object is a reserved identifier.
c.errorSkippedOnNoEmit(name, diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, scanner.DeclarationNameToString(name), scanner.DeclarationNameToString(name))
}
}

func (c *Checker) needCollisionCheckForIdentifier(node *ast.Node, identifier *ast.Node, name string) bool {
if identifier != nil && identifier.Text() != name {
return false
Expand Down Expand Up @@ -25135,6 +25155,14 @@ func (c *Checker) getStringLiteralType(value string) *Type {
}

func (c *Checker) getNumberLiteralType(value jsnum.Number) *Type {
// NaN cannot be used as a Go map key because NaN != NaN in IEEE 754,
// so Go map lookups for NaN always miss. Cache NaN type separately.
if value.IsNaN() {
if c.nanType == nil {
c.nanType = c.newLiteralType(TypeFlagsNumberLiteral, value, nil)
}
return c.nanType
}
t := c.numberLiteralTypes[value]
if t == nil {
t = c.newLiteralType(TypeFlagsNumberLiteral, value, nil)
Expand Down Expand Up @@ -25176,11 +25204,22 @@ func getBooleanLiteralValue(t *Type) bool {

func (c *Checker) getEnumLiteralType(value any, enumSymbol *ast.Symbol, symbol *ast.Symbol) *Type {
var flags TypeFlags
switch value.(type) {
switch v := value.(type) {
case string:
flags = TypeFlagsEnumLiteral | TypeFlagsStringLiteral
case jsnum.Number:
flags = TypeFlagsEnumLiteral | TypeFlagsNumberLiteral
// NaN cannot be used as a Go map key because NaN != NaN in IEEE 754,
// so Go map lookups for NaN always miss. Cache NaN enum types separately by enum symbol.
if v.IsNaN() {
t := c.enumNaNLiteralTypes[enumSymbol]
if t == nil {
t = c.newLiteralType(flags, value, nil)
t.symbol = symbol
c.enumNaNLiteralTypes[enumSymbol] = t
}
return t
}
default:
panic("Unhandled case in getEnumLiteralType")
}
Expand Down Expand Up @@ -26430,7 +26469,14 @@ func (c *Checker) getCrossProductUnionSize(types []*Type) int {
for _, t := range types {
switch {
case t.flags&TypeFlagsUnion != 0:
size *= len(t.Types())
n := len(t.Types())
// Cap the result to avoid integer overflow when computing the cross product of many large unions.
// In TypeScript, number overflow produces Infinity which naturally exceeds the limit check;
// in Go, we must guard against int wrapping to zero or negative.
if n > 0 && size > math.MaxInt/n {
return math.MaxInt
}
size *= n
case t.flags&TypeFlagsNever != 0:
return 0
}
Expand Down
8 changes: 2 additions & 6 deletions pkg/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -1970,15 +1970,11 @@ func (c *Checker) getSwitchClauseTypeOfWitnesses(node *ast.Node) []string {
witnesses := make([]string, len(clauses))
for i, clause := range clauses {
if clause.Kind == ast.KindCaseClause {
var text string
if ast.IsStringLiteralLike(clause.Expression()) {
text = clause.Expression().Text()
}
if text == "" {
if !ast.IsStringLiteralLike(clause.Expression()) {
witnesses = nil
break
}
if !slices.Contains(witnesses, text) {
if text := clause.Expression().Text(); !slices.Contains(witnesses, text) {
witnesses[i] = text
}
}
Expand Down
29 changes: 14 additions & 15 deletions pkg/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -922,18 +922,20 @@ func (c *Checker) checkGrammarClassDeclarationHeritageClauses(node *ast.ClassLik
return c.grammarErrorOnFirstToken(typeNodes[1], diagnostics.Classes_can_only_extend_a_single_class)
}

for _, j := range node.EagerJSDoc(file) {
if j.AsJSDoc().Tags == nil {
continue
}
for _, tag := range j.AsJSDoc().Tags.Nodes {
if tag.Kind == ast.KindJSDocAugmentsTag {
target := typeNodes[0].AsExpressionWithTypeArguments()
source := tag.ClassName().AsExpressionWithTypeArguments()
targetName := getIdentifierFromEntityNameExpression(target.Expression)
sourceName := getIdentifierFromEntityNameExpression(source.Expression)
if targetName != nil && sourceName != nil && targetName.Text() != sourceName.Text() {
return c.grammarErrorOnNode(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text())
if len(typeNodes) > 0 {
for _, j := range node.EagerJSDoc(file) {
if j.AsJSDoc().Tags == nil {
continue
}
for _, tag := range j.AsJSDoc().Tags.Nodes {
if tag.Kind == ast.KindJSDocAugmentsTag {
target := typeNodes[0].AsExpressionWithTypeArguments()
source := tag.ClassName().AsExpressionWithTypeArguments()
targetName := getIdentifierFromEntityNameExpression(target.Expression)
sourceName := getIdentifierFromEntityNameExpression(source.Expression)
if targetName != nil && sourceName != nil && targetName.Text() != sourceName.Text() {
return c.grammarErrorOnNode(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text())
}
}
}
}
Expand Down Expand Up @@ -1246,9 +1248,6 @@ func (c *Checker) checkGrammarForInOrForOfStatement(forInOrOfStatement *ast.ForI
containingFunc := ast.GetContainingFunction(forInOrOfStatement.AsNode())
if containingFunc != nil && containingFunc.Kind != ast.KindConstructor {
debug.Assert((ast.GetFunctionFlags(containingFunc)&ast.FunctionFlagsAsync) == 0, "Enclosing function should never be an async function.")
if hasAsyncModifier(containingFunc) {
panic("Enclosing function should never be an async function.")
}
relatedInfo := createDiagnosticForNode(containingFunc, diagnostics.Did_you_mean_to_mark_this_function_as_async)
diagnostic.AddRelatedInfo(relatedInfo)
}
Expand Down
14 changes: 10 additions & 4 deletions pkg/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,9 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target *
if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 {
impliedArity := constraint.TargetTupleType().fixedLength
c.inferFromTypes(n, c.sliceTupleType(source, startLength, sourceArity-(startLength+impliedArity)), elementTypes[startLength])
c.inferFromTypes(n, c.getElementTypeOfSliceOfTupleType(source, startLength+impliedArity, endLength, false, false), elementTypes[startLength+1])
if restType := c.getElementTypeOfSliceOfTupleType(source, startLength+impliedArity, endLength, false, false); restType != nil {
c.inferFromTypes(n, restType, elementTypes[startLength+1])
}
}
}
} else if elementInfos[startLength].flags&ElementFlagsRest != 0 && elementInfos[startLength+1].flags&ElementFlagsVariadic != 0 {
Expand All @@ -725,9 +727,13 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target *
impliedArity := constraint.TargetTupleType().fixedLength
endIndex := sourceArity - getEndElementCount(target.TargetTupleType(), ElementFlagsFixed)
startIndex := endIndex - impliedArity
trailingSlice := c.createTupleTypeEx(c.getTypeArguments(source)[startIndex:endIndex], source.TargetTupleType().elementInfos[startIndex:endIndex], false /*readonly*/)
c.inferFromTypes(n, c.getElementTypeOfSliceOfTupleType(source, startLength, endLength+impliedArity, false, false), elementTypes[startLength])
c.inferFromTypes(n, trailingSlice, elementTypes[startLength+1])
if startIndex >= startLength {
trailingSlice := c.createTupleTypeEx(c.getTypeArguments(source)[startIndex:endIndex], source.TargetTupleType().elementInfos[startIndex:endIndex], false /*readonly*/)
if restType := c.getElementTypeOfSliceOfTupleType(source, startLength, endLength+impliedArity, false, false); restType != nil {
c.inferFromTypes(n, restType, elementTypes[startLength])
}
c.inferFromTypes(n, trailingSlice, elementTypes[startLength+1])
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/checker/jsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ func (c *Checker) instantiateAliasOrInterfaceWithDefaults(managedSym *ast.Symbol
return c.getTypeAliasInstantiation(managedSym, args, nil)
}
}
if len(declaredManagedType.AsInterfaceType().TypeParameters()) >= len(typeArguments) {
if declaredManagedType.objectFlags&ObjectFlagsClassOrInterface != 0 && len(declaredManagedType.AsInterfaceType().TypeParameters()) >= len(typeArguments) {
args := c.fillMissingTypeArguments(typeArguments, declaredManagedType.AsInterfaceType().TypeParameters(), len(typeArguments), inJavaScript)
return c.createTypeReference(declaredManagedType, args)
}
Expand Down
15 changes: 12 additions & 3 deletions pkg/checker/nodecopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,9 @@ func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *as
if node.Kind == ast.KindJSDocTypeLiteral {
var members []*ast.Node
for _, t := range node.AsJSDocTypeLiteral().JSDocPropertyTags {
if t.Kind != ast.KindJSDocPropertyTag && t.Kind != ast.KindJSDocParameterTag {
continue
}
n := t.Name()
var targetName *ast.Node
if ast.IsIdentifier(n) {
Expand Down Expand Up @@ -779,10 +782,16 @@ func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *as
return res
}

if ast.IsStringLiteral(node) && b.ctx.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0 && node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote == 0 {
// set single quote on string literals
if ast.IsStringLiteralLike(node) {
// Preserve the original characters of the literal (e.g. emojis) in declaration emit
// rather than escaping them as ASCII Unicode escapes. Mirrors TypeScript's behavior
// for synthesized string literal types in the node builder (checker.ts:6853).
c := node.Clone(b.f)
c.AsStringLiteral().TokenFlags ^= ast.TokenFlagsSingleQuote
if ast.IsStringLiteral(node) && b.ctx.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0 && node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote == 0 {
// set single quote on string literals
c.AsStringLiteral().TokenFlags ^= ast.TokenFlagsSingleQuote
}
b.e.AddEmitFlags(c, printer.EFNoAsciiEscaping)
return c
}

Expand Down
8 changes: 6 additions & 2 deletions pkg/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -1801,8 +1801,12 @@ func (c *Checker) getRestTypeAtPosition(source *Signature, pos int, readonly boo
return c.createArrayType(c.getIndexedAccessType(restType, c.numberType))
}
}
types := make([]*Type, parameterCount-pos)
infos := make([]TupleElementInfo, parameterCount-pos)
length := parameterCount - pos
if length <= 0 {
return c.createTupleTypeEx(nil, nil, readonly)
}
types := make([]*Type, length)
infos := make([]TupleElementInfo, length)
for i := range types {
var flags ElementFlags
if restType == nil || i < len(types)-1 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/execute/tsc/extendedconfigcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type extendedConfigCacheEntry struct {
var _ tsoptions.ExtendedConfigCache = (*ExtendedConfigCache)(nil)

// GetExtendedConfig implements tsoptions.ExtendedConfigCache.
func (e *ExtendedConfigCache) GetExtendedConfig(fileName string, path tspath.Path, resolutionStack []string, host tsoptions.ParseConfigHost) *tsoptions.ExtendedConfigCacheEntry {
func (e *ExtendedConfigCache) GetExtendedConfig(fileName string, path tspath.Path, resolutionStack []tspath.Path, host tsoptions.ParseConfigHost) *tsoptions.ExtendedConfigCacheEntry {
entry, loaded := e.loadOrStoreNewLockedEntry(path)
defer entry.mu.Unlock()
if !loaded {
Expand Down
97 changes: 97 additions & 0 deletions pkg/execute/tsc/extendedconfigcache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package tsc_test

import (
"testing"

"github.com/buke/typescript-go-internal/pkg/execute/tsc"
"github.com/buke/typescript-go-internal/pkg/tsoptions"
"github.com/buke/typescript-go-internal/pkg/vfs"
"github.com/buke/typescript-go-internal/pkg/vfs/vfstest"
)

type testParseConfigHost struct {
fs vfs.FS
cwd string
}

func (h *testParseConfigHost) FS() vfs.FS { return h.fs }
func (h *testParseConfigHost) GetCurrentDirectory() string { return h.cwd }

func TestExtendedConfigCacheExtendsCircularity(t *testing.T) {
t.Parallel()

t.Run("self-referencing extends", func(t *testing.T) {
t.Parallel()

// Regression test: a tsconfig extends cycle should produce an error,
// not a deadlock when using the tsc ExtendedConfigCache.
files := map[string]any{
"/project/tsconfig.json": `{"extends": "./base.json"}`,
"/project/base.json": `{"extends": "./base.json"}`,
"/project/main.ts": `// Hello World!`,
}

fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}

cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
assertHasCircularityDiagnostic(t, cmd)
})

t.Run("mutual extends cycle", func(t *testing.T) {
t.Parallel()

// Two config files that extend each other.
files := map[string]any{
"/project/tsconfig.json": `{"extends": "./other.json"}`,
"/project/other.json": `{"extends": "./tsconfig.json"}`,
"/project/main.ts": `// Hello World!`,
}

fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}

cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
assertHasCircularityDiagnostic(t, cmd)
})

t.Run("case-insensitive self-referencing extends", func(t *testing.T) {
t.Parallel()

// On a case-insensitive FS, ./Base.json and ./base.json resolve to the same
// cache entry. The cycle check must use canonical paths to avoid deadlock.
files := map[string]any{
"/project/tsconfig.json": `{"extends": "./Base.json"}`,
"/project/base.json": `{"extends": "./base.json"}`,
"/project/main.ts": `// Hello World!`,
}

fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
host := &testParseConfigHost{fs: fs, cwd: "/project"}
cache := &tsc.ExtendedConfigCache{}

cmd, _ := tsoptions.GetParsedCommandLineOfConfigFile("/project/tsconfig.json", nil, nil, host, cache)
if cmd == nil {
t.Fatal("expected non-nil ParsedCommandLine")
}
assertHasCircularityDiagnostic(t, cmd)
})
}

func assertHasCircularityDiagnostic(t *testing.T, cmd *tsoptions.ParsedCommandLine) {
t.Helper()
for _, d := range cmd.Errors {
if d != nil && d.Code() == 18000 {
return
}
}
t.Error("expected circularity diagnostic (code 18000), but none was found")
}
2 changes: 0 additions & 2 deletions pkg/fourslash/_scripts/failingTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ TestAutoImportSortCaseSensitivity1
TestAutoImportTypeImport4
TestAutoImportTypeOnlyPreferred3
TestAutoImportVerbatimTypeOnly1
TestCalledUnionsOfDissimilarTyeshaveGoodDisplay
TestCloduleTypeOf1
TestCodeCompletionEscaping
TestCodeFixAddParameterNames1
Expand Down Expand Up @@ -338,7 +337,6 @@ TestNoQuickInfoForLabel
TestNoQuickInfoInWhitespace
TestOverloadQuickInfo
TestProtoVarVisibleWithOuterScopeUnderscoreProto
TestQualifyModuleTypeNames
TestQuickfixImplementInterfaceUnreachableTypeUsesRelativeImport
TestQuickInfo_notInsideComment
TestQuickinfo01
Expand Down
Loading