From 987e8cd9e1b12889817b784f1a1bfae92f7536f9 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 16 Jul 2018 13:14:47 -0700 Subject: [PATCH] completions: Add "arrow-head" as possible value for isNewIdentifierLocation --- src/harness/fourslash.ts | 4 +- src/server/protocol.ts | 2 +- src/services/completions.ts | 68 ++++++++++++------- src/services/types.ts | 15 ++-- .../reference/api/tsserverlibrary.d.ts | 15 ++-- tests/baselines/reference/api/typescript.d.ts | 13 ++-- .../completionListInUnclosedFunction08.ts | 2 +- .../completionListInUnclosedFunction09.ts | 2 +- tests/cases/fourslash/completionsLiterals.ts | 2 +- .../fourslash/completionsRecommended_union.ts | 4 +- tests/cases/fourslash/fourslash.ts | 2 +- 11 files changed, 78 insertions(+), 51 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index b7da07f603db7..2b6fedf2de744 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -4763,12 +4763,12 @@ namespace FourSlashInterface { }; export interface CompletionsAtOptions extends Partial { triggerCharacter?: ts.CompletionsTriggerCharacter; - isNewIdentifierLocation?: boolean; + isNewIdentifierLocation?: ts.IsNewIdentifierLocation; } export interface VerifyCompletionsOptions { readonly marker?: ArrayOrSingle; - readonly isNewIdentifierLocation?: boolean; + readonly isNewIdentifierLocation?: ts.IsNewIdentifierLocation; readonly exact?: ArrayOrSingle; readonly includes?: ArrayOrSingle; readonly excludes?: ArrayOrSingle; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 2e5218f754d0f..fc70764c85dfd 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1988,7 +1988,7 @@ namespace ts.server.protocol { export interface CompletionInfo { readonly isGlobalCompletion: boolean; readonly isMemberCompletion: boolean; - readonly isNewIdentifierLocation: boolean; + readonly isNewIdentifierLocation: boolean | "arrow-head"; readonly entries: ReadonlyArray; } diff --git a/src/services/completions.ts b/src/services/completions.ts index 1353ba8be218e..ecbfc459875b3 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -998,7 +998,7 @@ namespace ts.Completions { const semanticStart = timestamp(); let completionKind = CompletionKind.None; - let isNewIdentifierLocation = false; + let isNewIdentifierLocation: IsNewIdentifierLocation = false; let keywordFilters = KeywordCompletionFilters.None; let symbols: Symbol[] = []; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; @@ -1458,29 +1458,47 @@ namespace ts.Completions { return false; } - function isNewIdentifierDefinitionLocation(previousToken: Node | undefined): boolean { + function isNewIdentifierDefinitionLocation(previousToken: Node | undefined): IsNewIdentifierLocation { if (previousToken) { const containingNodeKind = previousToken.parent.kind; switch (previousToken.kind) { case SyntaxKind.CommaToken: - return containingNodeKind === SyntaxKind.CallExpression // func( a, | - || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ - || containingNodeKind === SyntaxKind.NewExpression // new C(a, | - || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | - || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | - || containingNodeKind === SyntaxKind.FunctionType; // var x: (s: string, list| + switch (containingNodeKind) { + case SyntaxKind.CallExpression: // func( a, | + case SyntaxKind.NewExpression: // new C(a, | + case SyntaxKind.ArrayLiteralExpression: // [a, | + case SyntaxKind.BinaryExpression: // const x = (a, | + return "arrow-head"; + case SyntaxKind.Constructor: // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ + case SyntaxKind.FunctionType: // var x: (s: string, list| + return true; + default: + return false; + } case SyntaxKind.OpenParenToken: - return containingNodeKind === SyntaxKind.CallExpression // func( | - || containingNodeKind === SyntaxKind.Constructor // constructor( | - || containingNodeKind === SyntaxKind.NewExpression // new C(a| - || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| - || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ + switch (containingNodeKind) { + case SyntaxKind.CallExpression: // func( a, | + case SyntaxKind.NewExpression: // new C(a| + case SyntaxKind.ParenthesizedExpression: // const x = (a| + case SyntaxKind.ParenthesizedType: // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ + return "arrow-head"; + case SyntaxKind.Constructor: // constructor( | + return true; + default: + return false; + } case SyntaxKind.OpenBracketToken: - return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | - || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] - || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ + switch (containingNodeKind) { + case SyntaxKind.ArrayLiteralExpression: // [ | + return "arrow-head"; + case SyntaxKind.IndexSignature: // [ | : string ] + case SyntaxKind.ComputedPropertyName: // [ | /* this can become an index signature */ + return true; + default: + return false; + } case SyntaxKind.ModuleKeyword: // module | case SyntaxKind.NamespaceKeyword: // namespace | @@ -1493,19 +1511,19 @@ namespace ts.Completions { return containingNodeKind === SyntaxKind.ClassDeclaration; // class A{ | case SyntaxKind.EqualsToken: - return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| - || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| + switch (containingNodeKind) { + case SyntaxKind.VariableDeclaration: // const x = a| + case SyntaxKind.BinaryExpression: // x = a| + return "arrow-head"; + default: + return false; + } case SyntaxKind.TemplateHead: - return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| + return containingNodeKind === SyntaxKind.TemplateExpression ? "arrow-head" : false; // `aa ${| case SyntaxKind.TemplateMiddle: - return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| - - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - return containingNodeKind === SyntaxKind.PropertyDeclaration; // class A{ public | + return containingNodeKind === SyntaxKind.TemplateSpan ? "arrow-head" : false; // `aa ${10} dd ${| } // Previous token may have been a keyword that was converted to an identifier. diff --git a/src/services/types.ts b/src/services/types.ts index 51c98724c0957..634672fc4b909 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -836,17 +836,20 @@ namespace ts { } export interface CompletionInfo { - /** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; - - /** - * true when the current location also allows for a new identifier - */ - isNewIdentifierLocation: boolean; + isNewIdentifierLocation: IsNewIdentifierLocation; entries: CompletionEntry[]; } + /** + * `true`: Likely a new identifier. (e.g. `class |` could be anything). But still provide completions in case it merges with something. + * `false`: Never a new identifier. (e.g. `{ x: 0 }.|` must be `x`, can't be anything else) + * `"arrow-head"`: Not a new identifier *unless* it starts an unparenthesized arrow function. (e.g. `x => x + 1`) + */ + export type IsNewIdentifierLocation = boolean | "arrow-head"; + // see comments in protocol.ts export interface CompletionEntry { name: string; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 57deee2ab9856..834ca5e707d93 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5291,15 +5291,18 @@ declare namespace ts { argumentCount: number; } interface CompletionInfo { - /** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; - /** - * true when the current location also allows for a new identifier - */ - isNewIdentifierLocation: boolean; + isNewIdentifierLocation: IsNewIdentifierLocation; entries: CompletionEntry[]; } + /** + * `true`: Likely a new identifier. (e.g. `class |` could be anything). But still provide completions in case it merges with something. + * `false`: Never a new identifier. (e.g. `{ x: 0 }.|` must be `x`, can't be anything else) + * `"arrow-head"`: Not a new identifier *unless* it starts an unparenthesized arrow function. (e.g. `x => x + 1`) + */ + type IsNewIdentifierLocation = boolean | "arrow-head"; interface CompletionEntry { name: string; kind: ScriptElementKind; @@ -7266,7 +7269,7 @@ declare namespace ts.server.protocol { interface CompletionInfo { readonly isGlobalCompletion: boolean; readonly isMemberCompletion: boolean; - readonly isNewIdentifierLocation: boolean; + readonly isNewIdentifierLocation: boolean | "arrow-head"; readonly entries: ReadonlyArray; } interface CompletionDetailsResponse extends Response { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4a792a34763e3..40759e64ba798 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5291,15 +5291,18 @@ declare namespace ts { argumentCount: number; } interface CompletionInfo { - /** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; - /** - * true when the current location also allows for a new identifier - */ - isNewIdentifierLocation: boolean; + isNewIdentifierLocation: IsNewIdentifierLocation; entries: CompletionEntry[]; } + /** + * `true`: Likely a new identifier. (e.g. `class |` could be anything). But still provide completions in case it merges with something. + * `false`: Never a new identifier. (e.g. `{ x: 0 }.|` must be `x`, can't be anything else) + * `"arrow-head"`: Not a new identifier *unless* it starts an unparenthesized arrow function. (e.g. `x => x + 1`) + */ + type IsNewIdentifierLocation = boolean | "arrow-head"; interface CompletionEntry { name: string; kind: ScriptElementKind; diff --git a/tests/cases/fourslash/completionListInUnclosedFunction08.ts b/tests/cases/fourslash/completionListInUnclosedFunction08.ts index c0616881bd6a9..2557a3822e548 100644 --- a/tests/cases/fourslash/completionListInUnclosedFunction08.ts +++ b/tests/cases/fourslash/completionListInUnclosedFunction08.ts @@ -5,4 +5,4 @@ //// var v = /*1*/ // Note: "v" questionable since we're in its initializer -verify.completions({ marker: "1", includes: ["foo", "x", "y", "z", "bar", "a", "b", "c", "v"], isNewIdentifierLocation: true }); +verify.completions({ marker: "1", includes: ["foo", "x", "y", "z", "bar", "a", "b", "c", "v"], isNewIdentifierLocation: "arrow-head" }); diff --git a/tests/cases/fourslash/completionListInUnclosedFunction09.ts b/tests/cases/fourslash/completionListInUnclosedFunction09.ts index 14827b1a891cd..a31d3fd098be0 100644 --- a/tests/cases/fourslash/completionListInUnclosedFunction09.ts +++ b/tests/cases/fourslash/completionListInUnclosedFunction09.ts @@ -6,4 +6,4 @@ ////} // Note: "v" questionable since we're in its initializer -verify.completions({ marker: "1", includes: ["foo", "x", "y", "z", "bar", "a", "b", "c", "v"], isNewIdentifierLocation: true }); +verify.completions({ marker: "1", includes: ["foo", "x", "y", "z", "bar", "a", "b", "c", "v"], isNewIdentifierLocation: "arrow-head" }); diff --git a/tests/cases/fourslash/completionsLiterals.ts b/tests/cases/fourslash/completionsLiterals.ts index 475d3dd247edf..1635d00b16c0c 100644 --- a/tests/cases/fourslash/completionsLiterals.ts +++ b/tests/cases/fourslash/completionsLiterals.ts @@ -8,5 +8,5 @@ verify.completions({ { name: "0", kind: "string", text: "0" }, { name: '"one"', kind: "string", text: '"one"' }, ], - isNewIdentifierLocation: true, + isNewIdentifierLocation: "arrow-head", }); diff --git a/tests/cases/fourslash/completionsRecommended_union.ts b/tests/cases/fourslash/completionsRecommended_union.ts index 95c31a008586e..eeddd2d561c38 100644 --- a/tests/cases/fourslash/completionsRecommended_union.ts +++ b/tests/cases/fourslash/completionsRecommended_union.ts @@ -11,12 +11,12 @@ verify.completions( { marker: "a", includes: { name: "E", isRecommended: true }, - isNewIdentifierLocation: true, + isNewIdentifierLocation: "arrow-head", }, { marker: "b", // Arbitrarily chooses one to be recommended includes: [{ name: "E", isRecommended: true, }, { name: "E2" }], - isNewIdentifierLocation: true, + isNewIdentifierLocation: "arrow-head", }, ); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 51989a0503091..b3798a19b0c50 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -201,7 +201,7 @@ declare namespace FourSlashInterface { caretAtMarker(markerName?: string): void; completions(...options: { readonly marker?: ArrayOrSingle, - readonly isNewIdentifierLocation?: boolean; + readonly isNewIdentifierLocation?: boolean | "arrow-head"; readonly exact?: ArrayOrSingle; readonly includes?: ArrayOrSingle; readonly excludes?: ArrayOrSingle;