From 62668264648aa51a0cdc8dcb1224254a37e49385 Mon Sep 17 00:00:00 2001 From: vi-ku Date: Fri, 27 Mar 2026 19:15:18 +0530 Subject: [PATCH] fix: sort JSDoc @param completions by parameter position (#20183) Previously, all JSDoc @param completion entries used the same sortText value, causing editors to sort them alphabetically by name rather than by their position in the function signature. This fix sets sortText to LocationPriority + parameterIndex so that completions appear in declaration order (e.g., for function foo(z, a), 'z' appears before 'a'). --- src/services/completions.ts | 6 +-- src/services/jsDoc.ts | 2 +- ...docParameterTagSnippetCompletion1.baseline | 42 +++++++++---------- ...docParameterTagSnippetCompletion2.baseline | 10 ++--- ...docParameterTagSnippetCompletion3.baseline | 12 +++--- .../fourslash/jsdocParameterNameCompletion.ts | 21 ++++++++-- .../fourslash/jsdocParameterNameSortOrder.ts | 16 +++++++ 7 files changed, 70 insertions(+), 39 deletions(-) create mode 100644 tests/cases/fourslash/jsdocParameterNameSortOrder.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 28d29136dab89..c503aeefee78f 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -944,7 +944,7 @@ function getJSDocParameterCompletions( const isJs = isSourceFileJS(sourceFile); const isSnippet = preferences.includeCompletionsWithSnippetText || undefined; const paramTagCount = countWhere(jsDoc.tags, tag => isJSDocParameterTag(tag) && tag.getEnd() <= position); - return mapDefined(func.parameters, param => { + return mapDefined(func.parameters, (param, parameterIndex) => { if (getJSDocParameterTags(param).length) { return undefined; // Parameter is already annotated. } @@ -983,7 +983,7 @@ function getJSDocParameterCompletions( return { name: displayText, kind: ScriptElementKind.parameterElement, - sortText: SortText.LocationPriority, + sortText: SortText.LocationPriority + String(parameterIndex) as SortText, insertText: isSnippet ? snippetText : undefined, isSnippet, }; @@ -1023,7 +1023,7 @@ function getJSDocParameterCompletions( return { name: displayText, kind: ScriptElementKind.parameterElement, - sortText: SortText.LocationPriority, + sortText: SortText.LocationPriority + String(parameterIndex) as SortText, insertText: isSnippet ? snippetText : undefined, isSnippet, }; diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 0f8cb859d6822..72b84efd3d0de 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -431,7 +431,7 @@ export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): Comple return undefined; } - return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }; + return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority + String(fn.parameters.indexOf(param)) as Completions.SortText }; }); } diff --git a/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline b/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline index bee86ad157385..376f09cc7035f 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion1.baseline @@ -2740,7 +2740,7 @@ { "name": "param value ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -2753,7 +2753,7 @@ { "name": "param maximumFractionDigits ", "kind": "", - "sortText": "11", + "sortText": "111", "kindModifiers": "", "displayParts": [ { @@ -3877,7 +3877,7 @@ { "name": "param param0 ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -3890,7 +3890,7 @@ { "name": "param b ", "kind": "", - "sortText": "11", + "sortText": "111", "kindModifiers": "", "displayParts": [ { @@ -5014,7 +5014,7 @@ { "name": "@param b ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -6138,7 +6138,7 @@ { "name": "param param0 ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -7262,7 +7262,7 @@ { "name": "param param0 ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -8386,7 +8386,7 @@ { "name": "param {Object} param0 \r\n* @param {number} [param0.a=1] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -8399,7 +8399,7 @@ { "name": "param {*} b ", "kind": "", - "sortText": "11", + "sortText": "111", "kindModifiers": "", "displayParts": [ { @@ -9523,7 +9523,7 @@ { "name": "@param {*} b ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -10647,7 +10647,7 @@ { "name": "param {Object} param0 \r\n* @param {Object} [param0.b={ a: 1, c: 3 }] \r\n* @param {*} param0.b.a \r\n* @param {*} param0.b.c ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -11771,7 +11771,7 @@ { "name": "param {Object} param0 \r\n* @param {Object} param0.a \r\n* @param {*} param0.a.b \r\n* @param {*} param0.a.c \r\n* @param {*} param0.d ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -12895,7 +12895,7 @@ { "name": "param {*} param0 ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -14019,7 +14019,7 @@ { "name": "param {Object} param0 \r\n* @param {*} param0.a ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -15143,7 +15143,7 @@ { "name": "param {*} a ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -16267,7 +16267,7 @@ { "name": "param {Object} param1 \r\n* @param {*} param1.b ", "kind": "", - "sortText": "11", + "sortText": "111", "kindModifiers": "", "displayParts": [ { @@ -17391,7 +17391,7 @@ { "name": "param {Object} param0 \r\n* @param {*} param0.b \r\n* @param {...*} param0.c ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -17404,7 +17404,7 @@ { "name": "param {...*} a ", "kind": "", - "sortText": "11", + "sortText": "111", "kindModifiers": "", "displayParts": [ { @@ -18528,7 +18528,7 @@ { "name": "param {...*} param0 ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -19652,7 +19652,7 @@ { "name": "param {...*} a ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -20776,7 +20776,7 @@ { "name": "param {*} [a] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { diff --git a/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline b/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline index 973d96fd68aa4..5bdcd48718d68 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion2.baseline @@ -1580,7 +1580,7 @@ { "name": "@param b ", "kind": "", - "sortText": "11", + "sortText": "110", "insertText": "@param b ${1}", "isSnippet": true, "kindModifiers": "", @@ -2706,7 +2706,7 @@ { "name": "@param {*} b ", "kind": "", - "sortText": "11", + "sortText": "110", "insertText": "@param {${1:*}} b ${2}", "isSnippet": true, "kindModifiers": "", @@ -3832,7 +3832,7 @@ { "name": "param {Object} param0 \r\n* @param {Object} [param0.b={ a: 1, c: 3 }] \r\n* @param {*} param0.b.a \r\n* @param {*} param0.b.c ", "kind": "", - "sortText": "11", + "sortText": "110", "insertText": "param {Object} param0 ${1}\r\n* @param {Object} [param0.b={ a: 1, c: 3 }] ${2}\r\n* @param {${3:*}} param0.b.a ${4}\r\n* @param {${5:*}} param0.b.c ${6}", "isSnippet": true, "kindModifiers": "", @@ -4958,7 +4958,7 @@ { "name": "param {...*} a ", "kind": "", - "sortText": "11", + "sortText": "110", "insertText": "param {...${1:*}} a ${2}", "isSnippet": true, "kindModifiers": "", @@ -6084,7 +6084,7 @@ { "name": "param {number} [a=3] ", "kind": "", - "sortText": "11", + "sortText": "110", "insertText": "param {number} [a=3] ${1}", "isSnippet": true, "kindModifiers": "", diff --git a/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline b/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline index 1496e3085daab..ea3d873913d54 100644 --- a/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline +++ b/tests/baselines/reference/jsdocParameterTagSnippetCompletion3.baseline @@ -1672,7 +1672,7 @@ { "name": "param {number} [a=3] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -2796,7 +2796,7 @@ { "name": "param {Object} param0 \r\n* @param {number} [param0.a=3] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -3920,7 +3920,7 @@ { "name": "param {Object} param0 \r\n* @param {*} param0.a \r\n* @param {Object} param0.o \r\n* @param {*} param0.o.b \r\n* @param {*} param0.o.c ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -5044,7 +5044,7 @@ { "name": "param {Object} param0 \r\n* @param {*} param0.a \r\n* @param {Object} param0.o \r\n* @param {*} param0.o.b \r\n* @param {[number, boolean]} [param0.o.c=[1, true]] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -6168,7 +6168,7 @@ { "name": "param {Object} param0 \r\n* @param {(number | boolean)[]} [param0.a=[1, true]] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { @@ -7292,7 +7292,7 @@ { "name": "param {Object} param0 \r\n* @param {*} [param0.a=random()] ", "kind": "", - "sortText": "11", + "sortText": "110", "kindModifiers": "", "displayParts": [ { diff --git a/tests/cases/fourslash/jsdocParameterNameCompletion.ts b/tests/cases/fourslash/jsdocParameterNameCompletion.ts index 05aa388009b47..b893198a35fb0 100644 --- a/tests/cases/fourslash/jsdocParameterNameCompletion.ts +++ b/tests/cases/fourslash/jsdocParameterNameCompletion.ts @@ -23,7 +23,22 @@ ////function i(foo, bar) {} verify.completions( - { marker: ["0", "3", "4"], exact: ["foo", "bar"] }, - { marker: "1", exact: "bar" }, - { marker: "2", exact: ["canary", "canoodle"] }, + { + marker: ["0", "3", "4"], + exact: [ + { name: "foo", kind: "parameter", sortText: completion.SortText.LocationPriority + "0" }, + { name: "bar", kind: "parameter", sortText: completion.SortText.LocationPriority + "1" }, + ], + }, + { + marker: "1", + exact: { name: "bar", kind: "parameter", sortText: completion.SortText.LocationPriority + "1" }, + }, + { + marker: "2", + exact: [ + { name: "canary", kind: "parameter", sortText: completion.SortText.LocationPriority + "1" }, + { name: "canoodle", kind: "parameter", sortText: completion.SortText.LocationPriority + "2" }, + ], + }, ); diff --git a/tests/cases/fourslash/jsdocParameterNameSortOrder.ts b/tests/cases/fourslash/jsdocParameterNameSortOrder.ts new file mode 100644 index 0000000000000..919583bbfa0b5 --- /dev/null +++ b/tests/cases/fourslash/jsdocParameterNameSortOrder.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: /a.ts +/////** +//// * @param /**/ +//// */ +////function foo(z: string, a: number, m: boolean) {} + +verify.completions({ + marker: "", + exact: [ + { name: "z", kind: "parameter", sortText: completion.SortText.LocationPriority + "0" }, + { name: "a", kind: "parameter", sortText: completion.SortText.LocationPriority + "1" }, + { name: "m", kind: "parameter", sortText: completion.SortText.LocationPriority + "2" }, + ], +});