Skip to content
Open
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
95 changes: 77 additions & 18 deletions src/services/inlayHints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ import {
PrefixUnaryExpression,
PropertyDeclaration,
QuotePreference,
Signature,
SignatureDeclarationBase,
signatureHasRestParameter,
skipParentheses,
some,
Symbol,
Expand Down Expand Up @@ -301,46 +303,103 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
const signature = checker.getResolvedSignature(expr);
if (signature === undefined) return;

const argumentSpans = args.map(arg => getArgumentSpan(skipParentheses(arg)));
let totalArgumentPositions = 0;
for (const span of argumentSpans) {
totalArgumentPositions += span;
}

const nonRestParamCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
const restTupleInfo = getRestTupleInfo(signature, nonRestParamCount, totalArgumentPositions);

let signatureParamPos = 0;
for (const originalArg of args) {
for (let argIndex = 0; argIndex < args.length; argIndex++) {
const originalArg = args[argIndex];
const arg = skipParentheses(originalArg);
const spreadArgs = argumentSpans[argIndex];
if (spreadArgs === 0) {
continue;
}
if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) {
signatureParamPos++;
signatureParamPos += spreadArgs;
continue;
}

let spreadArgs = 0;
const parameterPos = getAdjustedParameterPosition(signatureParamPos, restTupleInfo);
const identifierInfo = checker.getParameterIdentifierInfoAtPosition(signature, parameterPos);
signatureParamPos = signatureParamPos + (spreadArgs || 1);
if (identifierInfo) {
const { parameter, parameterName, isRestParameter: isFirstVariadicArgument } = identifierInfo;
const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName);
if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) {
continue;
}

const name = unescapeLeadingUnderscores(parameterName);
if (leadingCommentsContainsParameterName(arg, name)) {
continue;
}

addParameterHints(name, parameter, originalArg.getStart(), isFirstVariadicArgument);
}
}

function getArgumentSpan(arg: Expression) {
if (isSpreadElement(arg)) {
const spreadType = checker.getTypeAtLocation(arg.expression);
if (checker.isTupleType(spreadType)) {
const { elementFlags, fixedLength } = (spreadType as TupleTypeReference).target;
if (fixedLength === 0) {
continue;
return 0;
}
const firstOptionalIndex = findIndex(elementFlags, f => !(f & ElementFlags.Required));
const requiredArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
if (requiredArgs > 0) {
spreadArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
return requiredArgs;
}
}
}
return 1;
}

const identifierInfo = checker.getParameterIdentifierInfoAtPosition(signature, signatureParamPos);
signatureParamPos = signatureParamPos + (spreadArgs || 1);
if (identifierInfo) {
const { parameter, parameterName, isRestParameter: isFirstVariadicArgument } = identifierInfo;
const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName);
if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) {
continue;
}
function getRestTupleInfo(signature: Signature, paramCount: number, totalPositions: number) {
if (!signatureHasRestParameter(signature)) {
return undefined;
}
const restParameter = signature.parameters[paramCount];
if (!restParameter) {
return undefined;
}
const restType = checker.getTypeOfSymbol(restParameter);
if (!checker.isTupleType(restType)) {
return undefined;
}
const elementFlags = (restType as TupleTypeReference).target.elementFlags;
const restStartIndex = findIndex(elementFlags, f => !!(f & ElementFlags.Variable));
if (restStartIndex < 0) {
return undefined;
}
const restTailCount = elementFlags.length - restStartIndex - 1;
const restPositionsTotal = Math.max(0, totalPositions - paramCount);
return { restStartIndex, restTailCount, restPositionsTotal, paramCount };
}

const name = unescapeLeadingUnderscores(parameterName);
if (leadingCommentsContainsParameterName(arg, name)) {
continue;
function getAdjustedParameterPosition(signatureParamPos: number, restInfo?: { restStartIndex: number; restTailCount: number; restPositionsTotal: number; paramCount: number; }) {
if (!restInfo || signatureParamPos < restInfo.paramCount) {
return signatureParamPos;
}
const restPosition = signatureParamPos - restInfo.paramCount;
if (restPosition < restInfo.restStartIndex) {
return signatureParamPos;
}
if (restInfo.restTailCount > 0 && restInfo.restPositionsTotal >= restInfo.restTailCount) {
const tailStart = restInfo.restPositionsTotal - restInfo.restTailCount;
if (restPosition >= tailStart) {
const tailIndex = restPosition - tailStart;
return restInfo.paramCount + restInfo.restStartIndex + 1 + tailIndex;
}

addParameterHints(name, parameter, originalArg.getStart(), isFirstVariadicArgument);
}
return restInfo.paramCount + restInfo.restStartIndex;
}
}

Expand Down
63 changes: 63 additions & 0 deletions tests/baselines/reference/inlayHintsRestTupleTail.baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// === Inlay Hints ===
test(10, 'a', 'b', 'c')
^
{
"text": "first:",
"position": 83,
"kind": "Parameter",
"whitespaceAfter": true
}

test(10, 'a', 'b', 'c')
^
{
"text": "...middle:",
"position": 87,
"kind": "Parameter",
"whitespaceAfter": true
}

test(10, 'a', 'b', 'c')
^
{
"text": "...middle:",
"position": 92,
"kind": "Parameter",
"whitespaceAfter": true
}

test(10, 'a', 'b', 'c')
^
{
"text": "last:",
"position": 97,
"kind": "Parameter",
"whitespaceAfter": true
}

test(10, 'a', 'c')
^
{
"text": "first:",
"position": 107,
"kind": "Parameter",
"whitespaceAfter": true
}

test(10, 'a', 'c')
^
{
"text": "...middle:",
"position": 111,
"kind": "Parameter",
"whitespaceAfter": true
}

test(10, 'a', 'c')
^
{
"text": "last:",
"position": 116,
"kind": "Parameter",
"whitespaceAfter": true
}
8 changes: 8 additions & 0 deletions tests/cases/fourslash/inlayHintsRestTupleTail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//// function test(...rest: [first: number, ...middle: string[], last: string]) {}
//// test(10, 'a', 'b', 'c')
//// test(10, 'a', 'c')

verify.baselineInlayHints(undefined, {
includeInlayParameterNameHints: "all",
includeInlayFunctionParameterTypeHints: true,
});