Skip to content

fix(41259) : JS autocomplete doesn't work for object literal shorthands #41539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 22, 2020
Merged
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
24 changes: 22 additions & 2 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,7 @@ namespace ts.Completions {
const semanticStart = timestamp();
let completionKind = CompletionKind.None;
let isNewIdentifierLocation = false;
let isNonContextualObjectLiteral = false;
let keywordFilters = KeywordCompletionFilters.None;
// This also gets mutated in nested-functions after the return
let symbols: Symbol[] = [];
Expand Down Expand Up @@ -1471,6 +1472,8 @@ namespace ts.Completions {
}

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

if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker);

// Check completions for Object property value shorthand
if (instantiatedType === undefined) {
return GlobalsSearch.Fail;
if (objectLikeContainer.flags & NodeFlags.InWithStatement) {
return GlobalsSearch.Fail;
}
isNonContextualObjectLiteral = true;
return GlobalsSearch.Continue;
}
const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions);
isNewIdentifierLocation = hasIndexSignature(completionsType || instantiatedType);
const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType();
const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType();
isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype;
typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker);
existingMembers = objectLikeContainer.properties;

if (typeMembers.length === 0) {
// Edge case: If NumberIndexType exists
if (!hasNumberIndextype) {
isNonContextualObjectLiteral = true;
return GlobalsSearch.Continue;
}
}
}
else {
Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern);
Expand Down Expand Up @@ -2312,6 +2331,7 @@ namespace ts.Completions {
}

return isDeclarationName(contextToken)
&& !isShorthandPropertyAssignment(contextToken.parent)
&& !isJsxAttribute(contextToken.parent)
// 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`.
// If `contextToken !== previousToken`, this is `class C ex/**/`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
/// <reference path='fourslash.ts' />

// @Filename: a.ts
//// var [x/*variable1*/

// @Filename: b.ts
//// var [x, y/*variable2*/

// @Filename: c.ts
//// var [./*variable3*/

// @Filename: d.ts
//// var [x, ...z/*variable4*/

// @Filename: e.ts
//// var {x/*variable5*/

// @Filename: f.ts
//// var {x, y/*variable6*/

// @Filename: g.ts
//// function func1({ a/*parameter1*/

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

verify.completions({ marker: test.markers(), exact: undefined });
verify.completions({ marker: test.markers(), exact: undefined });
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/// <reference path="fourslash.ts"/>

//// declare const foo: number;
//// interface Empty {}
//// interface Typed { typed: number; }

//// declare function f1(obj): void;
//// declare function f2(obj: any): void;
//// declare function f3(obj: unknown): void;
//// declare function f4(obj: object): void;
//// declare function f5(obj: Record<string, any>): void;
//// declare function f6(obj: { [key: string]: number }): void;
//// declare function f7<T>(obj: T): void;
//// declare function f8<T extends object>(obj: T): void;
//// declare function f9<T extends {}>(obj: T): void;
//// declare function f10<T extends Empty>(obj: T): void;
//// declare function f11<T extends (Empty | Record<string, any> | {})>(obj: T): void;

//// declare function f12(obj: Typed): void;
//// declare function f13<T extends (Empty | Typed)>(obj: T): void;

//// declare function f14(obj: { [key: string]: number, prop: number }): void;

//// declare function f15(obj: Record<number, any>): void;
//// declare function f16(obj: { [key: number]: number }): void;

//// f1({f/*1*/});
//// f2({f/*2*/});
//// f3({f/*3*/});
//// f4({f/*4*/});
//// f5({f/*5*/});
//// f6({f/*6*/});
//// f7({f/*7*/});
//// f8({f/*8*/});
//// f9({f/*9*/});
//// f10({f/*10*/});
//// f11({f/*11*/});

//// f12({f/*12*/});
//// f13({f/*13*/});

//// f14({f/*14*/});

//// f15({f/*15*/});
//// f16({f/*16*/});

const locals = [
...(() => {
const symbols = [];
for (let i = 1; i <= 16; i ++) {
symbols.push(`f${i}`);
}
return symbols;
})(),
"foo"
];
verify.completions(
// Non-contextual, any, unknown, object, Record<string, ..>, [key: string]: .., Type parameter, etc..
{ marker: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], exact: completion.globalsPlus(locals)},
// Has named property
{ marker: ["12", "13"], exact: "typed"},
// Has both StringIndexType and named property
{ marker: ["14"], exact: "prop", isNewIdentifierLocation: true},
// NumberIndexType
{ marker: ["15", "16"], exact: [], isNewIdentifierLocation: true},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference path="fourslash.ts"/>

//// const foo = 1;
//// const bar = 2;

//// const obj1 = {
//// foo b/*1*/
//// };

//// const obj2: any = {
//// foo b/*2*/
//// };

verify.completions({
marker: test.markers(),
exact: completion.globalsPlus(["foo", "bar", "obj1", "obj2"]),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts"/>

//// const foo = 1;
//// const bar = 2;
//// const obj = {
//// foo b/*1*/

verify.completions({
marker: ["1"],
exact: completion.globalsPlus(["foo", "bar", "obj"])
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts"/>

//// const foo = 1;
//// const bar = 2;
//// const obj: any = {
//// foo b/*1*/

verify.completions({
marker: ["1"],
exact: completion.globalsPlus(["foo", "bar", "obj"])
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @module: esnext

// @Filename: /a.ts
//// export const exportedConstant = 0;

// @Filename: /b.ts
//// const obj = { exp/**/

verify.completions({
marker: "",
exact: completion.globalsPlus(["obj"]),
preferences: { includeCompletionsForModuleExports: true }
});
5 changes: 4 additions & 1 deletion tests/cases/fourslash/completionsGenericUnconstrained.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@

verify.completions({
marker: "",
exact: []
includes: [{
name: "Object",
sortText: completion.SortText.GlobalsOrKeywords
}]
});
12 changes: 6 additions & 6 deletions tests/cases/fourslash/completionsSelfDeclaring2.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/// <reference path="fourslash.ts" />

////function f1<T>(x: T) {}
////f1({ abc/*1*/ });
////
////function f2<T extends { xyz: number }>(x: T) {}
////f2({ x/*2*/ });
//// function f1<T>(x: T) {}
//// f1({ abc/*1*/ });

//// function f2<T extends { xyz: number }>(x: T) {}
//// f2({ x/*2*/ });


verify.completions({
marker: "1",
exact: []
exact: completion.globalsPlus(["f1", "f2"])
});

verify.completions({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
// 5, 6: Literal member completion after member name with empty member expression.
const exact = ["p1", "p2", "p3", "p4", ...completion.globalsPlus(["ObjectLiterals"])];
verify.completions(
{ marker: ["1"], exact, isNewIdentifierLocation: true },
{ marker: ["2", "3", "5", "6"], exact },
{ marker: "4", exact: undefined },
{ marker: ["1",], exact, isNewIdentifierLocation: true },
{ marker: ["2", "3", "4", "5", "6"], exact }
);