Enable existing commit characters in certain locations where isNewIdentifier is true (#59523)

This commit is contained in:
Gabriela Araujo Britto
2024-08-19 13:24:03 -07:00
committed by GitHub
parent 936a79bbb5
commit 1e7889c697
14 changed files with 91403 additions and 55 deletions

View File

@@ -1037,8 +1037,8 @@ export class TestState {
if (ts.hasProperty(options, "defaultCommitCharacters")) {
assert.deepEqual(
actualCompletions.defaultCommitCharacters?.sort(),
options.defaultCommitCharacters?.sort(),
actualCompletions.defaultCommitCharacters?.slice().sort(),
options.defaultCommitCharacters?.slice().sort(),
"Expected 'defaultCommitCharacters' properties to match",
);
}
@@ -1191,8 +1191,8 @@ export class TestState {
assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `At entry ${actual.name}: Expected 'sortText' properties to match`);
if (ts.hasProperty(expected, "commitCharacters")) {
assert.deepEqual(
actual.commitCharacters?.sort(),
expected.commitCharacters?.sort(),
actual.commitCharacters?.slice().sort(),
expected.commitCharacters?.slice().sort(),
`At entry ${actual.name}: Expected 'commitCharacters' values to match`,
);
}

View File

@@ -7,6 +7,7 @@ import {
BindingElement,
BindingPattern,
BreakOrContinueStatement,
CallExpression,
CancellationToken,
canHaveDecorators,
canUsePropertyAccess,
@@ -304,6 +305,7 @@ import {
ModuleReference,
NamedImportBindings,
newCaseClauseTracker,
NewExpression,
Node,
NodeArray,
NodeBuilderFlags,
@@ -433,6 +435,12 @@ export const SortText = {
},
};
/** All commit characters, valid when `isNewIdentifierLocation` is false. */
const allCommitCharacters = [".", ",", ";"];
/** Commit characters valid at expression positions where we could be inside a parameter list. */
const noCommaCommitCharacters = [".", ";"];
/**
* Special values for `CompletionInfo['source']` used to disambiguate
* completion items with the same `name`. (Each completion item must
@@ -691,7 +699,7 @@ export function getDefaultCommitCharacters(isNewIdentifierLocation: boolean): st
if (isNewIdentifierLocation) {
return [];
}
return [".", ",", ";"];
return allCommitCharacters;
}
/** @internal */
@@ -1295,6 +1303,7 @@ function completionInfoFromData(
insideJsDocTagTypeExpression,
symbolToSortTextMap,
hasUnresolvedAutoImports,
defaultCommitCharacters,
} = completionData;
let literals = completionData.literals;
@@ -1413,7 +1422,7 @@ function completionInfoFromData(
isNewIdentifierLocation,
optionalReplacementSpan: getOptionalReplacementSpan(location),
entries,
defaultCommitCharacters: getDefaultCommitCharacters(isNewIdentifierLocation),
defaultCommitCharacters: defaultCommitCharacters ?? getDefaultCommitCharacters(isNewIdentifierLocation),
};
}
@@ -3198,6 +3207,7 @@ interface CompletionData {
readonly importStatementCompletion?: ImportStatementCompletionInfo;
readonly hasUnresolvedAutoImports?: boolean;
readonly flags: CompletionInfoFlags;
readonly defaultCommitCharacters: string[] | undefined;
}
type Request =
| { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag; }
@@ -3391,6 +3401,7 @@ function getCompletionData(
let keywordFilters = KeywordCompletionFilters.None;
let isNewIdentifierLocation = false;
let flags = CompletionInfoFlags.None;
let defaultCommitCharacters: string[] | undefined;
if (contextToken) {
const importStatementCompletionInfo = getImportStatementCompletionInfo(contextToken, sourceFile);
@@ -3417,7 +3428,7 @@ function getCompletionData(
if (!importStatementCompletionInfo.replacementSpan && isCompletionListBlocker(contextToken)) {
log("Returning an empty list because completion was requested in an invalid position.");
return keywordFilters
? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation())
? keywordCompletionData(keywordFilters, isJsOnlyLocation, computeCommitCharactersAndIsNewIdentifier().isNewIdentifierLocation)
: undefined;
}
@@ -3634,6 +3645,7 @@ function getCompletionData(
importStatementCompletion,
hasUnresolvedAutoImports,
flags,
defaultCommitCharacters,
};
type JSDocTagWithTypeExpression =
@@ -3686,7 +3698,10 @@ function getCompletionData(
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) {
const isNamespaceName = isModuleDeclaration(node.parent);
if (isNamespaceName) isNewIdentifierLocation = true;
if (isNamespaceName) {
isNewIdentifierLocation = true;
defaultCommitCharacters = [];
}
let symbol = typeChecker.getSymbolAtLocation(node);
if (symbol) {
symbol = skipAlias(symbol, typeChecker);
@@ -3768,9 +3783,13 @@ function getCompletionData(
}
function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void {
isNewIdentifierLocation = !!type.getStringIndexType();
if (type.getStringIndexType()) {
isNewIdentifierLocation = true;
defaultCommitCharacters = [];
}
if (isRightOfQuestionDot && some(type.getCallSignatures())) {
isNewIdentifierLocation = true;
defaultCommitCharacters ??= allCommitCharacters; // Only invalid commit character here would be `(`.
}
const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName;
@@ -3945,7 +3964,7 @@ function getCompletionData(
// Get all entities in the current scope.
completionKind = CompletionKind.Global;
isNewIdentifierLocation = isNewIdentifierDefinitionLocation();
({ isNewIdentifierLocation, defaultCommitCharacters } = computeCommitCharactersAndIsNewIdentifier());
if (previousToken !== contextToken) {
Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'.");
@@ -4344,7 +4363,7 @@ function getCompletionData(
return false;
}
function isNewIdentifierDefinitionLocation(): boolean {
function computeCommitCharactersAndIsNewIdentifier(): { defaultCommitCharacters: string[]; isNewIdentifierLocation: boolean; } {
if (contextToken) {
const containingNodeKind = contextToken.parent.kind;
const tokenKind = keywordForNode(contextToken);
@@ -4352,62 +4371,118 @@ function getCompletionData(
// dprint-ignore
switch (tokenKind) {
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|
|| containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, |
switch (containingNodeKind) {
case SyntaxKind.CallExpression: // func( a, |
case SyntaxKind.NewExpression: { // new C(a, |
const expression = (contextToken.parent as CallExpression | NewExpression).expression;
if (getLineAndCharacterOfPosition(sourceFile, expression.end).line !==
getLineAndCharacterOfPosition(sourceFile, position).line) { // func\n(a, |
return { defaultCommitCharacters: noCommaCommitCharacters, isNewIdentifierLocation: true };
}
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: true };
}
case SyntaxKind.BinaryExpression: // const x = (a, |
return { defaultCommitCharacters: noCommaCommitCharacters, isNewIdentifierLocation: true };
case SyntaxKind.Constructor: // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */
case SyntaxKind.FunctionType: // var x: (s: string, list|
case SyntaxKind.ObjectLiteralExpression: // const obj = { x, |
return { defaultCommitCharacters: [], isNewIdentifierLocation: true };
case SyntaxKind.ArrayLiteralExpression: // [a, |
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: true };
default:
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: 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( |
case SyntaxKind.NewExpression: { // new C(a|
const expression = (contextToken.parent as CallExpression | NewExpression).expression;
if (getLineAndCharacterOfPosition(sourceFile, expression.end).line !==
getLineAndCharacterOfPosition(sourceFile, position).line) { // func\n( |
return { defaultCommitCharacters: noCommaCommitCharacters, isNewIdentifierLocation: true };
}
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: true };
}
case SyntaxKind.ParenthesizedExpression: // const x = (a|
return { defaultCommitCharacters: noCommaCommitCharacters, isNewIdentifierLocation: true };
case SyntaxKind.Constructor: // constructor( |
case SyntaxKind.ParenthesizedType: // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */
return { defaultCommitCharacters: [], isNewIdentifierLocation: true };
default:
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: 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: // [ |
case SyntaxKind.IndexSignature: // [ | : string ]
case SyntaxKind.TupleType: // [ | : string ]
case SyntaxKind.ComputedPropertyName: // [ | /* this can become an index signature */
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: true };
default:
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
}
case SyntaxKind.ModuleKeyword: // module |
case SyntaxKind.NamespaceKeyword: // namespace |
case SyntaxKind.ImportKeyword: // import |
return true;
case SyntaxKind.ModuleKeyword: // module |
case SyntaxKind.NamespaceKeyword: // namespace |
case SyntaxKind.ImportKeyword: // import |
return { defaultCommitCharacters: [], isNewIdentifierLocation: true };
case SyntaxKind.DotToken:
return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.|
switch (containingNodeKind) {
case SyntaxKind.ModuleDeclaration: // module A.|
return { defaultCommitCharacters: [], isNewIdentifierLocation: true };
default:
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
}
case SyntaxKind.OpenBraceToken:
return containingNodeKind === SyntaxKind.ClassDeclaration // class A { |
|| containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { |
switch (containingNodeKind) {
case SyntaxKind.ClassDeclaration: // class A { |
case SyntaxKind.ObjectLiteralExpression: // const obj = { |
return { defaultCommitCharacters: [], isNewIdentifierLocation: true };
default:
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
}
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 { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: true };
default:
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
}
case SyntaxKind.TemplateHead:
return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${|
return {
defaultCommitCharacters: allCommitCharacters,
isNewIdentifierLocation: containingNodeKind === SyntaxKind.TemplateExpression // `aa ${|
};
case SyntaxKind.TemplateMiddle:
return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${|
return {
defaultCommitCharacters: allCommitCharacters,
isNewIdentifierLocation: containingNodeKind === SyntaxKind.TemplateSpan // `aa ${10} dd ${|
};
case SyntaxKind.AsyncKeyword:
return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|()
|| containingNodeKind === SyntaxKind.ShorthandPropertyAssignment; // const obj = { async c|
return (containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|()
|| containingNodeKind === SyntaxKind.ShorthandPropertyAssignment) // const obj = { async c|
? { defaultCommitCharacters: [], isNewIdentifierLocation: true }
: { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
case SyntaxKind.AsteriskToken:
return containingNodeKind === SyntaxKind.MethodDeclaration; // const obj = { * c|
return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { * c|
? { defaultCommitCharacters: [], isNewIdentifierLocation: true }
: { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
}
if (isClassMemberCompletionKeyword(tokenKind)) {
return true;
return { defaultCommitCharacters: [], isNewIdentifierLocation: true };
}
}
return false;
return { defaultCommitCharacters: allCommitCharacters, isNewIdentifierLocation: false };
}
function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean {

View File

@@ -15330,7 +15330,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -27308,7 +27312,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -45958,7 +45966,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -56488,7 +56500,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -61751,7 +61767,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -69949,7 +69969,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -78147,7 +78171,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -86345,7 +86373,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -90932,7 +90964,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
@@ -100829,7 +100865,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
}
]

View File

@@ -8290,7 +8290,11 @@
]
}
],
"defaultCommitCharacters": []
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
// === Completions ===
=== /tests/cases/fourslash/a.ts ===
// const xx: string = "aa";
// function ff(): void {}
// export { };
// ^
// | ----------------------------------------------------------------------
// | function ff(): void
// | const xx: string
// | type
// | ----------------------------------------------------------------------
=== /tests/cases/fourslash/imports.ts ===
// import { } from "./exports";
// ^
// | ----------------------------------------------------------------------
// | const aa: () => void
// | const ff: string
// | type
// | ----------------------------------------------------------------------
[
{
"marker": {
"fileName": "/tests/cases/fourslash/a.ts",
"position": 57,
"name": "1"
},
"item": {
"flags": 0,
"isGlobalCompletion": false,
"isMemberCompletion": false,
"isNewIdentifierLocation": false,
"entries": [
{
"name": "ff",
"kind": "function",
"kindModifiers": "",
"sortText": "11",
"displayParts": [
{
"text": "function",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "ff",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"documentation": []
},
{
"name": "xx",
"kind": "const",
"kindModifiers": "",
"sortText": "11",
"displayParts": [
{
"text": "const",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "xx",
"kind": "localName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
}
],
"documentation": []
},
{
"name": "type",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "type",
"kind": "keyword"
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/imports.ts",
"position": 9,
"name": "2"
},
"item": {
"flags": 0,
"isGlobalCompletion": false,
"isMemberCompletion": true,
"isNewIdentifierLocation": false,
"entries": [
{
"name": "aa",
"kind": "const",
"kindModifiers": "export",
"sortText": "11",
"displayParts": [
{
"text": "const",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "aa",
"kind": "localName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "=>",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"documentation": []
},
{
"name": "ff",
"kind": "const",
"kindModifiers": "export",
"sortText": "11",
"displayParts": [
{
"text": "const",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "ff",
"kind": "localName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
}
],
"documentation": []
},
{
"name": "type",
"kind": "keyword",
"kindModifiers": "",
"sortText": "15",
"displayParts": [
{
"text": "type",
"kind": "keyword"
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
// === Completions ===
=== /tests/cases/fourslash/file1.ts ===
// const a: "aa" | "bb" = ""
// ^
// | ----------------------------------------------------------------------
// | aa
// | bb
// | ----------------------------------------------------------------------
[
{
"marker": {
"fileName": "/tests/cases/fourslash/file1.ts",
"position": 24,
"name": ""
},
"item": {
"isGlobalCompletion": false,
"isMemberCompletion": false,
"isNewIdentifierLocation": false,
"optionalReplacementSpan": {
"start": 24,
"length": 0
},
"entries": [
{
"name": "aa",
"kindModifiers": "",
"kind": "string",
"sortText": "11",
"replacementSpan": {
"start": 24,
"length": 0
},
"commitCharacters": [],
"displayParts": [
{
"text": "aa",
"kind": "text"
}
]
},
{
"name": "bb",
"kindModifiers": "",
"kind": "string",
"sortText": "11",
"replacementSpan": {
"start": 24,
"length": 0
},
"commitCharacters": [],
"displayParts": [
{
"text": "bb",
"kind": "text"
}
]
}
],
"defaultCommitCharacters": [
".",
",",
";"
]
}
}
]

View File

@@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
//// declare const obj: { banana: 1 };
//// const x = obj./*1*/
//// declare module obj./*2*/ {}
//// declare const obj2: { banana: 1 } | undefined;
//// const y = obj2?./*3*/
//// declare const obj3: { [x: string]: number };
//// const z = obj3./*4*/
//// declare const obj4: { (): string; [x: string]: number } | undefined;
//// const w = obj4?./*5*/
//// declare const obj5: { (): string } | undefined;
//// const a = obj5?./*6*/
verify.baselineCompletions();

View File

@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
// @filename: a.ts
//// const xx: string = "aa";
//// function ff(): void {}
//// export { /*1*/ };
// @filename: exports.ts
//// export const ff: string = "";
//// export const aa = () => {};
// @filename: imports.ts
//// import { /*2*/ } from "./exports";
verify.baselineCompletions();

View File

@@ -0,0 +1,45 @@
/// <reference path='fourslash.ts' />
//// declare function func(a: string, b: number): { a: string, b: number };
//// const x1 = func(/*1*/)
//// const x2 = func
//// (a/*2*/)
//// (a/*22*/)
//// ;
//// const x3 = func("a", /*3*/)
//// const x4 = func
//// (a, b/*4*/)
//// const x5 = func((a, /*5*/));
//// const x6 = (a/*6*/)
//// const x7 = (a, x/*7*/)
//// function x8(/*8*/) {}
//// function x9(a: number, /*9*/)
//// const x10: [/*10*/]
//// const x11: [ ]
//// let x12;
//// x12 = /*12*/
//// const x13 = `hello, ${/*13*/}`
//// interface I<T> {
//// [/*14*/]: T;
//// }
//// class C {
//// [/*16*/]: string;
//// [str/*17*/: string]: number;
//// }
//// type T = {
//// [x/*18*/yz: number]: boolean;
//// [/*19*/
//// }
//// function F(pred: (x/*20*/)
verify.baselineCompletions();

View File

@@ -0,0 +1,7 @@
/// <reference path="fourslash.ts" />
// @Filename: file1.ts
//// const mySpecialVar = 1;
//// const foo = mySpec/**/
verify.baselineCompletions();

View File

@@ -0,0 +1,8 @@
/// <reference path="fourslash.ts" />
// @Filename: file1.ts
//// const a: "aa" | "bb" = "/**/"
verify.baselineCompletions({
includeInsertTextCompletions: true,
});