Skip to content
Closed
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
4 changes: 2 additions & 2 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4763,12 +4763,12 @@ namespace FourSlashInterface {
};
export interface CompletionsAtOptions extends Partial<ts.UserPreferences> {
triggerCharacter?: ts.CompletionsTriggerCharacter;
isNewIdentifierLocation?: boolean;
isNewIdentifierLocation?: ts.IsNewIdentifierLocation;
}

export interface VerifyCompletionsOptions {
readonly marker?: ArrayOrSingle<string | FourSlash.Marker>;
readonly isNewIdentifierLocation?: boolean;
readonly isNewIdentifierLocation?: ts.IsNewIdentifierLocation;
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly includes?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly excludes?: ArrayOrSingle<string | { readonly name: string, readonly source: string }>;
Expand Down
2 changes: 1 addition & 1 deletion src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CompletionEntry>;
}

Expand Down
68 changes: 43 additions & 25 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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 |
Expand All @@ -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.
Expand Down
15 changes: 9 additions & 6 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 9 additions & 6 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<CompletionEntry>;
}
interface CompletionDetailsResponse extends Response {
Expand Down
13 changes: 8 additions & 5 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
2 changes: 1 addition & 1 deletion tests/cases/fourslash/completionsLiterals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ verify.completions({
{ name: "0", kind: "string", text: "0" },
{ name: '"one"', kind: "string", text: '"one"' },
],
isNewIdentifierLocation: true,
isNewIdentifierLocation: "arrow-head",
});
4 changes: 2 additions & 2 deletions tests/cases/fourslash/completionsRecommended_union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
);
2 changes: 1 addition & 1 deletion tests/cases/fourslash/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ declare namespace FourSlashInterface {
caretAtMarker(markerName?: string): void;
completions(...options: {
readonly marker?: ArrayOrSingle<string | Marker>,
readonly isNewIdentifierLocation?: boolean;
readonly isNewIdentifierLocation?: boolean | "arrow-head";
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly includes?: ArrayOrSingle<ExpectedCompletionEntry>;
readonly excludes?: ArrayOrSingle<string | { name: string, source: string }>;
Expand Down