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

* fix: #41259

* fix: #41259

* fix: fourslash

* fix: remove nested if

* fix: change tc result for #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
This commit is contained in:
orange4glace 2020-12-23 03:36:52 +09:00 committed by GitHub
parent 22f452c5cd
commit 1e4a5c9b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 13 deletions

View File

@ -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[] = [];
@ -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.
@ -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);
@ -2313,6 +2332,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/**/`.

View File

@ -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 });

View File

@ -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},
);

View File

@ -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"]),
});

View File

@ -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"])
});

View File

@ -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"])
});

View File

@ -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 }
});

View File

@ -10,5 +10,8 @@
verify.completions({
marker: "",
exact: []
includes: [{
name: "Object",
sortText: completion.SortText.GlobalsOrKeywords
}]
});

View File

@ -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({

View File

@ -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 }
);