diff --git a/internal/fourslash/tests/completionsForContextualConstraintTypeInJsDoc_test.go b/internal/fourslash/tests/completionsForContextualConstraintTypeInJsDoc_test.go new file mode 100644 index 0000000000..26ad826465 --- /dev/null +++ b/internal/fourslash/tests/completionsForContextualConstraintTypeInJsDoc_test.go @@ -0,0 +1,58 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestCompletionsForContextualConstraintTypeInJsDoc(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + const content = ` +// @allowJs: true +// @filename: a.ts +export interface Blah { +} + +// @filename: b.js +/** @import * as a from "./a" */ + +/** @type {a.Blah<{ a: /*1*/ }>} */ +let x; + +// @filename: c.js +/** @import * as a from "./a" */ + +/** @type {a.Blah<{ a: /*2*/ }>} */ +` + + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + + // These examples both would panic in retrieving the symbols + // of property signature nodes within JSDoc types. + // In both cases, we'd have a JSDoc property signature that has no symbol. + // + // The two cases differ in whether or not there is a variable declaration + // following the `@type` comment. These are important to test differently + // because of how JSDoc re-parsing would construct nodes in the tree. + // + // Getting the symbol of the reparsed node is a sufficient fix for marker 1. + // However, that would not fix the case at marker 2 because + // there is no variable to attach the `@type` annotation, so the node basically + // doesn't exist for subsequent passes like the binder. + + f.VerifyCompletions(t, f.Markers(), &fourslash.CompletionsExpectedList{ + IsIncomplete: false, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{CommitCharacters: &[]string{".", ",", ";"}}, + Items: &fourslash.CompletionsExpectedItems{ + Includes: []fourslash.CompletionsExpectedItem{ + `"hello"`, + `"world"`, + }, + }, + }) +} diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 92b0290dd5..0c6b618e7b 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -3655,7 +3655,20 @@ func getConstraintOfTypeArgumentProperty(node *ast.Node, typeChecker *checker.Ch switch node.Kind { case ast.KindPropertySignature: - return typeChecker.GetTypeOfPropertyOfContextualType(t, node.Symbol().Name) + // Try to get the reparsed node first - we may be in JSDoc. + reparsed := ast.GetReparsedNodeForNode(node) + if symbol := reparsed.Symbol(); symbol != nil { + return typeChecker.GetTypeOfPropertyOfContextualType(t, symbol.Name) + } + + // In some cases, we won't have a corresponding symbol + // (e.g. JSDoc types that never get re-attached) so we'll use + // the name as declared by the property as a best-effort. + if name, ok := ast.TryGetTextOfPropertyName(reparsed.Name()); ok { + return typeChecker.GetTypeOfPropertyOfContextualType(t, name) + } + + return nil case ast.KindColonToken: if node.Parent.Kind == ast.KindPropertySignature { // The cursor is at a property value location like `Foo<{ x: | }`.