Skip to content

Commit 1e4a5c9

Browse files
authored
fix(41259) : JS autocomplete doesn't work for object literal shorthands (microsoft#41539)
* fix: microsoft#41259 * fix: microsoft#41259 * fix: fourslash * fix: remove nested if * fix: change tc result for microsoft#41259 * fix: less restrictive shorthand completion rules * fix: use typeMembers to find out whether properties are empty * fix: typo * fix: lint * fix: exclude Object in completion * fix: test * fix: testcase tidy up * fix: apply completions for unclosed literal and missing comma * fix: ignore auto-imports * fix: use exact to ensure the order of completions * fix: use exact to ensure the order of completions * fix: add new lines so it can easy to be distinguished
1 parent 22f452c commit 1e4a5c9

10 files changed

+161
-13
lines changed

src/services/completions.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,7 @@ namespace ts.Completions {
10861086
const semanticStart = timestamp();
10871087
let completionKind = CompletionKind.None;
10881088
let isNewIdentifierLocation = false;
1089+
let isNonContextualObjectLiteral = false;
10891090
let keywordFilters = KeywordCompletionFilters.None;
10901091
// This also gets mutated in nested-functions after the return
10911092
let symbols: Symbol[] = [];
@@ -1471,6 +1472,8 @@ namespace ts.Completions {
14711472
}
14721473

14731474
function shouldOfferImportCompletions(): boolean {
1475+
// If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols
1476+
if (isNonContextualObjectLiteral) return false;
14741477
// If not already a module, must have modules enabled.
14751478
if (!preferences.includeCompletionsForModuleExports) return false;
14761479
// If already using ES6 modules, OK to continue using them.
@@ -1892,13 +1895,29 @@ namespace ts.Completions {
18921895

18931896
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
18941897
const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker);
1898+
1899+
// Check completions for Object property value shorthand
18951900
if (instantiatedType === undefined) {
1896-
return GlobalsSearch.Fail;
1901+
if (objectLikeContainer.flags & NodeFlags.InWithStatement) {
1902+
return GlobalsSearch.Fail;
1903+
}
1904+
isNonContextualObjectLiteral = true;
1905+
return GlobalsSearch.Continue;
18971906
}
18981907
const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions);
1899-
isNewIdentifierLocation = hasIndexSignature(completionsType || instantiatedType);
1908+
const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType();
1909+
const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType();
1910+
isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype;
19001911
typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker);
19011912
existingMembers = objectLikeContainer.properties;
1913+
1914+
if (typeMembers.length === 0) {
1915+
// Edge case: If NumberIndexType exists
1916+
if (!hasNumberIndextype) {
1917+
isNonContextualObjectLiteral = true;
1918+
return GlobalsSearch.Continue;
1919+
}
1920+
}
19021921
}
19031922
else {
19041923
Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern);
@@ -2313,6 +2332,7 @@ namespace ts.Completions {
23132332
}
23142333

23152334
return isDeclarationName(contextToken)
2335+
&& !isShorthandPropertyAssignment(contextToken.parent)
23162336
&& !isJsxAttribute(contextToken.parent)
23172337
// Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`.
23182338
// If `contextToken !== previousToken`, this is `class C ex/**/`.
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
/// <reference path='fourslash.ts' />
22

3+
// @Filename: a.ts
34
//// var [x/*variable1*/
45

6+
// @Filename: b.ts
57
//// var [x, y/*variable2*/
68

9+
// @Filename: c.ts
710
//// var [./*variable3*/
811

12+
// @Filename: d.ts
913
//// var [x, ...z/*variable4*/
1014

15+
// @Filename: e.ts
1116
//// var {x/*variable5*/
1217

18+
// @Filename: f.ts
1319
//// var {x, y/*variable6*/
1420

21+
// @Filename: g.ts
1522
//// function func1({ a/*parameter1*/
1623

24+
// @Filename: h.ts
1725
//// function func2({ a, b/*parameter2*/
1826

19-
verify.completions({ marker: test.markers(), exact: undefined });
27+
verify.completions({ marker: test.markers(), exact: undefined });
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// declare const foo: number;
4+
//// interface Empty {}
5+
//// interface Typed { typed: number; }
6+
7+
//// declare function f1(obj): void;
8+
//// declare function f2(obj: any): void;
9+
//// declare function f3(obj: unknown): void;
10+
//// declare function f4(obj: object): void;
11+
//// declare function f5(obj: Record<string, any>): void;
12+
//// declare function f6(obj: { [key: string]: number }): void;
13+
//// declare function f7<T>(obj: T): void;
14+
//// declare function f8<T extends object>(obj: T): void;
15+
//// declare function f9<T extends {}>(obj: T): void;
16+
//// declare function f10<T extends Empty>(obj: T): void;
17+
//// declare function f11<T extends (Empty | Record<string, any> | {})>(obj: T): void;
18+
19+
//// declare function f12(obj: Typed): void;
20+
//// declare function f13<T extends (Empty | Typed)>(obj: T): void;
21+
22+
//// declare function f14(obj: { [key: string]: number, prop: number }): void;
23+
24+
//// declare function f15(obj: Record<number, any>): void;
25+
//// declare function f16(obj: { [key: number]: number }): void;
26+
27+
//// f1({f/*1*/});
28+
//// f2({f/*2*/});
29+
//// f3({f/*3*/});
30+
//// f4({f/*4*/});
31+
//// f5({f/*5*/});
32+
//// f6({f/*6*/});
33+
//// f7({f/*7*/});
34+
//// f8({f/*8*/});
35+
//// f9({f/*9*/});
36+
//// f10({f/*10*/});
37+
//// f11({f/*11*/});
38+
39+
//// f12({f/*12*/});
40+
//// f13({f/*13*/});
41+
42+
//// f14({f/*14*/});
43+
44+
//// f15({f/*15*/});
45+
//// f16({f/*16*/});
46+
47+
const locals = [
48+
...(() => {
49+
const symbols = [];
50+
for (let i = 1; i <= 16; i ++) {
51+
symbols.push(`f${i}`);
52+
}
53+
return symbols;
54+
})(),
55+
"foo"
56+
];
57+
verify.completions(
58+
// Non-contextual, any, unknown, object, Record<string, ..>, [key: string]: .., Type parameter, etc..
59+
{ marker: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], exact: completion.globalsPlus(locals)},
60+
// Has named property
61+
{ marker: ["12", "13"], exact: "typed"},
62+
// Has both StringIndexType and named property
63+
{ marker: ["14"], exact: "prop", isNewIdentifierLocation: true},
64+
// NumberIndexType
65+
{ marker: ["15", "16"], exact: [], isNewIdentifierLocation: true},
66+
);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// const foo = 1;
4+
//// const bar = 2;
5+
6+
//// const obj1 = {
7+
//// foo b/*1*/
8+
//// };
9+
10+
//// const obj2: any = {
11+
//// foo b/*2*/
12+
//// };
13+
14+
verify.completions({
15+
marker: test.markers(),
16+
exact: completion.globalsPlus(["foo", "bar", "obj1", "obj2"]),
17+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// const foo = 1;
4+
//// const bar = 2;
5+
//// const obj = {
6+
//// foo b/*1*/
7+
8+
verify.completions({
9+
marker: ["1"],
10+
exact: completion.globalsPlus(["foo", "bar", "obj"])
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
//// const foo = 1;
4+
//// const bar = 2;
5+
//// const obj: any = {
6+
//// foo b/*1*/
7+
8+
verify.completions({
9+
marker: ["1"],
10+
exact: completion.globalsPlus(["foo", "bar", "obj"])
11+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @module: esnext
2+
3+
// @Filename: /a.ts
4+
//// export const exportedConstant = 0;
5+
6+
// @Filename: /b.ts
7+
//// const obj = { exp/**/
8+
9+
verify.completions({
10+
marker: "",
11+
exact: completion.globalsPlus(["obj"]),
12+
preferences: { includeCompletionsForModuleExports: true }
13+
});

tests/cases/fourslash/completionsGenericUnconstrained.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010

1111
verify.completions({
1212
marker: "",
13-
exact: []
13+
includes: [{
14+
name: "Object",
15+
sortText: completion.SortText.GlobalsOrKeywords
16+
}]
1417
});

tests/cases/fourslash/completionsSelfDeclaring2.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/// <reference path="fourslash.ts" />
22

3-
////function f1<T>(x: T) {}
4-
////f1({ abc/*1*/ });
5-
////
6-
////function f2<T extends { xyz: number }>(x: T) {}
7-
////f2({ x/*2*/ });
3+
//// function f1<T>(x: T) {}
4+
//// f1({ abc/*1*/ });
5+
6+
//// function f2<T extends { xyz: number }>(x: T) {}
7+
//// f2({ x/*2*/ });
88

99

1010
verify.completions({
1111
marker: "1",
12-
exact: []
12+
exact: completion.globalsPlus(["f1", "f2"])
1313
});
1414

1515
verify.completions({

tests/cases/fourslash/globalCompletionListInsideObjectLiterals.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
// 5, 6: Literal member completion after member name with empty member expression.
2929
const exact = ["p1", "p2", "p3", "p4", ...completion.globalsPlus(["ObjectLiterals"])];
3030
verify.completions(
31-
{ marker: ["1"], exact, isNewIdentifierLocation: true },
32-
{ marker: ["2", "3", "5", "6"], exact },
33-
{ marker: "4", exact: undefined },
31+
{ marker: ["1",], exact, isNewIdentifierLocation: true },
32+
{ marker: ["2", "3", "4", "5", "6"], exact }
3433
);

0 commit comments

Comments
 (0)