Skip to content
Draft
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
12 changes: 12 additions & 0 deletions internal/checker/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,18 @@ func (c *Checker) GetContextualDeclarationsForObjectLiteralElement(objectLiteral
return result
}

// GetContextualTypeForArrayElement returns the contextual type for an element at the given index
// in an array with the given contextual type.
func (c *Checker) GetContextualTypeForArrayElement(contextualArrayType *Type, elementIndex int) *Type {
if contextualArrayType == nil {
return nil
}
// Pass -1 for length, firstSpreadIndex, and lastSpreadIndex since we don't have
// access to the actual array literal. This falls back to getting the iterated type
// or checking numeric properties, which is appropriate for completion contexts.
return c.getContextualTypeForElementExpression(contextualArrayType, elementIndex, -1, -1, -1)
}

var knownGenericTypeNames = map[string]struct{}{
"Array": {},
"ArrayLike": {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
"github.com/microsoft/typescript-go/internal/testutil"
)

// Test that string literal completions are suggested in tuple contexts
// even without typing a quote character.
func TestCompletionsInArrayLiteralWithContextualType(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")

// Test 1: Completions after `[` in a tuple should suggest string literals
const content1 = `let y: ["foo" | "bar", string] = [/*a*/];`
f1, done1 := fourslash.NewFourslash(t, nil /*capabilities*/, content1)
defer done1()
f1.VerifyCompletions(t, "a", &fourslash.CompletionsExpectedList{
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{
Includes: []fourslash.CompletionsExpectedItem{
"\"foo\"",
"\"bar\"",
},
},
})

// Test 2: Completions after `,` in a tuple should provide contextual type for second element
const content2 = `let z: ["a", "b" | "c"] = ["a", /*b*/];`
f2, done2 := fourslash.NewFourslash(t, nil /*capabilities*/, content2)
defer done2()
f2.VerifyCompletions(t, "b", &fourslash.CompletionsExpectedList{
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{
Includes: []fourslash.CompletionsExpectedItem{
"\"b\"",
"\"c\"",
},
},
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
"github.com/microsoft/typescript-go/internal/testutil"
)

// Test for issue: Completions crash in call to `new Map(...)`.
// When requesting completions inside a Map constructor's array literal,
// IndexOfNode returns -1 causing a panic in getContextualTypeForElementExpression
// when trying to access array elements without a bounds check.
func TestCompletionsInMapConstructorNoCrash(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")

// Test completion at position /*a*/ - before the string literal
const content1 = `const m = new Map([
[/*a*/'0', ['0', false]],
]);`
f1, done1 := fourslash.NewFourslash(t, nil /*capabilities*/, content1)
defer done1()
// Just verify that completions don't crash - accept any completion list
f1.VerifyCompletions(t, "a", &fourslash.CompletionsExpectedList{
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{},
})

// Test completion at position /*b*/ - after the array literal
const content2 = `const m = new Map([
['0', ['0', false]]/*b*/,
]);`
f2, done2 := fourslash.NewFourslash(t, nil /*capabilities*/, content2)
defer done2()
// Just verify that completions don't crash - accept any completion list
f2.VerifyCompletions(t, "b", &fourslash.CompletionsExpectedList{
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{},
})
}
31 changes: 31 additions & 0 deletions internal/ls/completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2967,6 +2967,37 @@ func getContextualType(previousToken *ast.Node, position int, file *ast.SourceFi
return typeChecker.GetContextualTypeForJsxAttribute(parent.Parent)
}
return nil
case ast.KindOpenBracketToken:
// When completing after `[` in an array literal (e.g., `[/*here*/]`),
// we should provide contextual type for the first element
if ast.IsArrayLiteralExpression(parent) {
contextualArrayType := typeChecker.GetContextualType(parent, checker.ContextFlagsNone)
if contextualArrayType != nil {
// Get the type for the first element (index 0)
return typeChecker.GetContextualTypeForArrayElement(contextualArrayType, 0)
}
}
return nil
case ast.KindCommaToken:
// When completing after `,` in an array literal (e.g., `[x, /*here*/]`),
// we should provide contextual type for the element after the comma
if ast.IsArrayLiteralExpression(parent) {
contextualArrayType := typeChecker.GetContextualType(parent, checker.ContextFlagsNone)
if contextualArrayType != nil {
// Count how many elements come before the cursor position
arrayLiteral := parent.AsArrayLiteralExpression()
elementIndex := 0
for _, elem := range arrayLiteral.Elements.Nodes {
if elem.Pos() < position {
elementIndex++
} else {
break
}
}
return typeChecker.GetContextualTypeForArrayElement(contextualArrayType, elementIndex)
}
}
return nil
case ast.KindQuestionToken:
// When completing after `?` in a ternary conditional (e.g., `foo(a ? /*here*/)`),
// we need to look at the parent conditional expression to find the contextual type.
Expand Down