diff --git a/src/services/completions.ts b/src/services/completions.ts index 40f3cac68c95c..51a296aeafb80 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -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); @@ -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/**/`. diff --git a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts index 27c7f9a4dae21..6cc77a3f61cf8 100644 --- a/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts +++ b/tests/cases/fourslash/completionListAtIdentifierDefinitionLocations_destructuring.ts @@ -1,19 +1,27 @@ /// +// @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 }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral.ts b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral.ts new file mode 100644 index 0000000000000..0f8f65e70ebd1 --- /dev/null +++ b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral.ts @@ -0,0 +1,66 @@ +/// + +//// 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): void; +//// declare function f6(obj: { [key: string]: number }): void; +//// declare function f7(obj: T): void; +//// declare function f8(obj: T): void; +//// declare function f9(obj: T): void; +//// declare function f10(obj: T): void; +//// declare function f11 | {})>(obj: T): void; + +//// declare function f12(obj: Typed): void; +//// declare function f13(obj: T): void; + +//// declare function f14(obj: { [key: string]: number, prop: number }): void; + +//// declare function f15(obj: Record): 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, [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}, +); diff --git a/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral2.ts b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral2.ts new file mode 100644 index 0000000000000..ec54418c77935 --- /dev/null +++ b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral2.ts @@ -0,0 +1,17 @@ +/// + +//// 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"]), +}); diff --git a/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral3.ts b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral3.ts new file mode 100644 index 0000000000000..0a6fad60b293e --- /dev/null +++ b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral3.ts @@ -0,0 +1,11 @@ +/// + +//// const foo = 1; +//// const bar = 2; +//// const obj = { +//// foo b/*1*/ + +verify.completions({ + marker: ["1"], + exact: completion.globalsPlus(["foo", "bar", "obj"]) +}); diff --git a/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral4.ts b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral4.ts new file mode 100644 index 0000000000000..35daa6185a3f1 --- /dev/null +++ b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral4.ts @@ -0,0 +1,11 @@ +/// + +//// const foo = 1; +//// const bar = 2; +//// const obj: any = { +//// foo b/*1*/ + +verify.completions({ + marker: ["1"], + exact: completion.globalsPlus(["foo", "bar", "obj"]) +}); diff --git a/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral5.ts b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral5.ts new file mode 100644 index 0000000000000..74ded23477463 --- /dev/null +++ b/tests/cases/fourslash/completionPropertyShorthandForObjectLiteral5.ts @@ -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 } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsGenericUnconstrained.ts b/tests/cases/fourslash/completionsGenericUnconstrained.ts index 3865dc515618f..d18991830e7dd 100644 --- a/tests/cases/fourslash/completionsGenericUnconstrained.ts +++ b/tests/cases/fourslash/completionsGenericUnconstrained.ts @@ -10,5 +10,8 @@ verify.completions({ marker: "", - exact: [] + includes: [{ + name: "Object", + sortText: completion.SortText.GlobalsOrKeywords + }] }); diff --git a/tests/cases/fourslash/completionsSelfDeclaring2.ts b/tests/cases/fourslash/completionsSelfDeclaring2.ts index ee313de90c0bf..7a46020a2c210 100644 --- a/tests/cases/fourslash/completionsSelfDeclaring2.ts +++ b/tests/cases/fourslash/completionsSelfDeclaring2.ts @@ -1,15 +1,15 @@ /// -////function f1(x: T) {} -////f1({ abc/*1*/ }); -//// -////function f2(x: T) {} -////f2({ x/*2*/ }); +//// function f1(x: T) {} +//// f1({ abc/*1*/ }); + +//// function f2(x: T) {} +//// f2({ x/*2*/ }); verify.completions({ marker: "1", - exact: [] + exact: completion.globalsPlus(["f1", "f2"]) }); verify.completions({ diff --git a/tests/cases/fourslash/globalCompletionListInsideObjectLiterals.ts b/tests/cases/fourslash/globalCompletionListInsideObjectLiterals.ts index e41ec0a99f44d..503758461489b 100644 --- a/tests/cases/fourslash/globalCompletionListInsideObjectLiterals.ts +++ b/tests/cases/fourslash/globalCompletionListInsideObjectLiterals.ts @@ -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 } );