diff --git a/tsc/internal/fourslash/tests/inlayHintsParameterNames_destructuredParameter_test.go b/tsc/internal/fourslash/tests/inlayHintsParameterNames_destructuredParameter_test.go new file mode 100644 index 0000000000000..0cb8763dc532e --- /dev/null +++ b/tsc/internal/fourslash/tests/inlayHintsParameterNames_destructuredParameter_test.go @@ -0,0 +1,19 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestInlayHintsParameterNames_destructuredParameter(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `function foo({ a }: { a: number }, b: number) {} +foo({ a: 1 }, 2)` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineInlayHints(t, nil /*span*/, &lsutil.UserPreferences{InlayHints: lsutil.InlayHintsPreferences{IncludeInlayParameterNameHints: lsutil.IncludeInlayParameterNameHintsAll}}) +} diff --git a/tsc/internal/fourslash/tests/inlayHintsRestParameters_trailingRequired_test.go b/tsc/internal/fourslash/tests/inlayHintsRestParameters_trailingRequired_test.go new file mode 100644 index 0000000000000..a4c3a47ce4fda --- /dev/null +++ b/tsc/internal/fourslash/tests/inlayHintsRestParameters_trailingRequired_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestInlayHintsRestParameters_trailingRequired(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `function test(...rest: [first: number, ...middle: string[], last: string]) {} +test(10, 'a', 'b', 'c') +test(10, 'a', 'c') +test(10, 'c') + +function head(...rest: [...init: string[], last: number]) {} +head('a', 'b', 1) +head(1) + +function mixed(a: boolean, ...rest: [x: number, ...ys: string[], z: string]) {} +mixed(true, 1, 'a', 'b', 'c') + +function noTrailing(...rest: [first: number, ...middle: string[]]) {} +noTrailing(1, 'a', 'b')` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineInlayHints(t, nil /*span*/, &lsutil.UserPreferences{InlayHints: lsutil.InlayHintsPreferences{IncludeInlayParameterNameHints: lsutil.IncludeInlayParameterNameHintsAll}}) +} diff --git a/tsc/internal/ls/inlay_hints.go b/tsc/internal/ls/inlay_hints.go index 0864f8df361e7..5325370662cb1 100644 --- a/tsc/internal/ls/inlay_hints.go +++ b/tsc/internal/ls/inlay_hints.go @@ -157,6 +157,9 @@ func (s *inlayHintState) visitCallOrNewExpression(expr *ast.CallOrNewExpression) return } + // The elements after the rest element of a tuple are matched with the last arguments, which are only known + // when there is no spread argument. + argumentCount := core.IfElse(slices.ContainsFunc(args, ast.IsSpreadElement), -1, len(args)) signatureParamPos := 0 for _, originalArg := range args { arg := ast.SkipParentheses(originalArg) @@ -184,10 +187,10 @@ func (s *inlayHintState) visitCallOrNewExpression(expr *ast.CallOrNewExpression) } } - identifierInfo := s.getParameterIdentifierInfoAtPosition(signature, signatureParamPos) + identifierInfo := s.getParameterIdentifierInfoAtPosition(signature, signatureParamPos, argumentCount) signatureParamPos = signatureParamPos + core.IfElse(spreadArgs > 0, spreadArgs, 1) if identifierInfo == nil { - return + continue } parameter := identifierInfo.parameter @@ -831,7 +834,7 @@ type parameterInfo struct { isRestParameter bool } -func (s *inlayHintState) getParameterIdentifierInfoAtPosition(signature *checker.Signature, pos int) *parameterInfo { +func (s *inlayHintState) getParameterIdentifierInfoAtPosition(signature *checker.Signature, pos int, argumentCount int) *parameterInfo { parameters := signature.Parameters() paramCount := len(parameters) - core.IfElse(signature.HasRestParameter(), 1, 0) if pos < paramCount { @@ -859,14 +862,11 @@ func (s *inlayHintState) getParameterIdentifierInfoAtPosition(signature *checker restType := s.checker.GetTypeOfSymbol(restParameter) if restType.IsTupleType() { - associatedNames := make([]*ast.Node, 0, len(restType.Target().AsTupleType().ElementInfos())) - for _, elementInfo := range restType.Target().AsTupleType().ElementInfos() { - labeledElement := elementInfo.LabeledDeclaration() - associatedNames = append(associatedNames, labeledElement) - } - index := pos - paramCount - if index < len(associatedNames) { - associatedName := associatedNames[index] + elementInfos := restType.Target().AsTupleType().ElementInfos() + restArgumentCount := core.IfElse(argumentCount < 0, -1, max(argumentCount-paramCount, 0)) + index := getRestTupleElementIndex(elementInfos, pos-paramCount, restArgumentCount) + if index >= 0 { + associatedName := elementInfos[index].LabeledDeclaration() if associatedName != nil { debug.Assert(ast.IsIdentifier(associatedName.Name())) var isRestTupleElement bool @@ -896,6 +896,37 @@ func (s *inlayHintState) getParameterIdentifierInfoAtPosition(signature *checker return nil } +// getRestTupleElementIndex returns the index of the element of a rest parameter's tuple type that the argument at the given +// index among the rest arguments corresponds to, or -1 if there is none. argumentCount is the number of rest arguments, +// or -1 if it is not known. +func getRestTupleElementIndex(elementInfos []checker.TupleElementInfo, index int, argumentCount int) int { + restIndex, lastRestIndex := -1, -1 + for i, info := range elementInfos { + if info.TupleElementFlags()&checker.ElementFlagsVariable != 0 { + if restIndex < 0 { + restIndex = i + } + lastRestIndex = i + } + } + + // The elements after the rest element correspond to the last arguments, so that the rest element is left with + // the arguments in between, and only the first of them gets a hint. + trailingCount := len(elementInfos) - lastRestIndex - 1 + trailingStart := argumentCount - trailingCount + if restIndex >= 0 && restIndex == lastRestIndex && trailingCount > 0 && argumentCount >= 0 && trailingStart >= restIndex { + switch { + case index < restIndex: + return index + case index < trailingStart: + return core.IfElse(index == restIndex, restIndex, -1) + default: + return restIndex + 1 + index - trailingStart + } + } + return core.IfElse(index < len(elementInfos), index, -1) +} + func getParameterDeclarationIdentifier(symbol *ast.Symbol) *ast.IdentifierNode { if symbol.ValueDeclaration != nil && ast.IsParameterDeclaration(symbol.ValueDeclaration) && ast.IsIdentifier(symbol.ValueDeclaration.Name()) { return symbol.ValueDeclaration.Name() diff --git a/tsc/testdata/baselines/reference/fourslash/inlayHints/inlayHintsParameterNames_destructuredParameter.baseline b/tsc/testdata/baselines/reference/fourslash/inlayHints/inlayHintsParameterNames_destructuredParameter.baseline new file mode 100644 index 0000000000000..66233bf258f04 --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/inlayHints/inlayHintsParameterNames_destructuredParameter.baseline @@ -0,0 +1,32 @@ +// === Inlay Hints === +foo({ a: 1 }, 2) + ^ +{ + "position": { + "line": 1, + "character": 14 + }, + "label": [ + { + "value": "b", + "location": { + "uri": "file:///inlayHintsParameterNames_destructuredParameter.ts", + "range": { + "start": { + "line": 0, + "character": 35 + }, + "end": { + "line": 0, + "character": 36 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/fourslash/inlayHints/inlayHintsRestParameters_trailingRequired.baseline b/tsc/testdata/baselines/reference/fourslash/inlayHints/inlayHintsRestParameters_trailingRequired.baseline new file mode 100644 index 0000000000000..51e94c63bf743 --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/inlayHints/inlayHintsRestParameters_trailingRequired.baseline @@ -0,0 +1,544 @@ +// === Inlay Hints === +test(10, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 1, + "character": 5 + }, + "label": [ + { + "value": "first", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 24 + }, + "end": { + "line": 0, + "character": 29 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 1, + "character": 9 + }, + "label": [ + { + "value": "...middle", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 42 + }, + "end": { + "line": 0, + "character": 48 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 1, + "character": 19 + }, + "label": [ + { + "value": "last", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 60 + }, + "end": { + "line": 0, + "character": 64 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'a', 'c') + ^ +{ + "position": { + "line": 2, + "character": 5 + }, + "label": [ + { + "value": "first", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 24 + }, + "end": { + "line": 0, + "character": 29 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'a', 'c') + ^ +{ + "position": { + "line": 2, + "character": 9 + }, + "label": [ + { + "value": "...middle", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 42 + }, + "end": { + "line": 0, + "character": 48 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'a', 'c') + ^ +{ + "position": { + "line": 2, + "character": 14 + }, + "label": [ + { + "value": "last", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 60 + }, + "end": { + "line": 0, + "character": 64 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'c') + ^ +{ + "position": { + "line": 3, + "character": 5 + }, + "label": [ + { + "value": "first", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 24 + }, + "end": { + "line": 0, + "character": 29 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +test(10, 'c') + ^ +{ + "position": { + "line": 3, + "character": 9 + }, + "label": [ + { + "value": "last", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 0, + "character": 60 + }, + "end": { + "line": 0, + "character": 64 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +head('a', 'b', 1) + ^ +{ + "position": { + "line": 6, + "character": 5 + }, + "label": [ + { + "value": "...init", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 5, + "character": 27 + }, + "end": { + "line": 5, + "character": 31 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +head('a', 'b', 1) + ^ +{ + "position": { + "line": 6, + "character": 15 + }, + "label": [ + { + "value": "last", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 5, + "character": 43 + }, + "end": { + "line": 5, + "character": 47 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +head(1) + ^ +{ + "position": { + "line": 7, + "character": 5 + }, + "label": [ + { + "value": "last", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 5, + "character": 43 + }, + "end": { + "line": 5, + "character": 47 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +mixed(true, 1, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 10, + "character": 6 + }, + "label": [ + { + "value": "a", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 9, + "character": 15 + }, + "end": { + "line": 9, + "character": 16 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +mixed(true, 1, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 10, + "character": 12 + }, + "label": [ + { + "value": "x", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 9, + "character": 37 + }, + "end": { + "line": 9, + "character": 38 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +mixed(true, 1, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 10, + "character": 15 + }, + "label": [ + { + "value": "...ys", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 9, + "character": 51 + }, + "end": { + "line": 9, + "character": 53 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +mixed(true, 1, 'a', 'b', 'c') + ^ +{ + "position": { + "line": 10, + "character": 25 + }, + "label": [ + { + "value": "z", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 9, + "character": 65 + }, + "end": { + "line": 9, + "character": 66 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +noTrailing(1, 'a', 'b') + ^ +{ + "position": { + "line": 13, + "character": 11 + }, + "label": [ + { + "value": "first", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 12, + "character": 30 + }, + "end": { + "line": 12, + "character": 35 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} + +noTrailing(1, 'a', 'b') + ^ +{ + "position": { + "line": 13, + "character": 14 + }, + "label": [ + { + "value": "...middle", + "location": { + "uri": "file:///inlayHintsRestParameters_trailingRequired.ts", + "range": { + "start": { + "line": 12, + "character": 48 + }, + "end": { + "line": 12, + "character": 54 + } + } + } + }, + { + "value": ":" + } + ], + "kind": 2, + "paddingRight": true +} \ No newline at end of file