Cherry-pick PR #44125 into release-4.3 (#44136)

Component commits:
34b80a51af Don’t offer import statement completions at `from` position

afa4d051a9 Set isGlobalCompletion to false, use indexOf lookup

Co-authored-by: Andrew Branch <andrew@wheream.io>
This commit is contained in:
TypeScript Bot 2021-05-17 17:48:35 -07:00 committed by GitHub
parent 1d850c0927
commit 22c3c24cf0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 9 deletions

View file

@ -174,6 +174,8 @@ namespace ts.Completions {
return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions());
case CompletionDataKind.JsDocParameterName:
return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag));
case CompletionDataKind.Keywords:
return specificKeywordCompletionInfo(completionData.keywords);
default:
return Debug.assertNever(completionData);
}
@ -183,6 +185,20 @@ namespace ts.Completions {
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
}
function specificKeywordCompletionInfo(keywords: readonly SyntaxKind[]): CompletionInfo {
return {
isGlobalCompletion: false,
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: keywords.map(k => ({
name: tokenToString(k)!,
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none,
sortText: SortText.GlobalsOrKeywords,
})),
};
}
function getOptionalReplacementSpan(location: Node | undefined) {
// StringLiteralLike locations are handled separately in stringCompletions.ts
return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined;
@ -802,6 +818,8 @@ namespace ts.Completions {
return JsDoc.getJSDocTagCompletionDetails(name);
case CompletionDataKind.JsDocParameterName:
return JsDoc.getJSDocParameterNameCompletionDetails(name);
case CompletionDataKind.Keywords:
return request.keywords.indexOf(stringToToken(name)!) > -1 ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined;
default:
return Debug.assertNever(request);
}
@ -893,7 +911,7 @@ namespace ts.Completions {
return completion.type === "symbol" ? completion.symbol : undefined;
}
const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName }
const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName, Keywords }
/** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */
type IsJsxInitializer = boolean | Identifier;
interface CompletionData {
@ -918,7 +936,10 @@ namespace ts.Completions {
readonly isJsxIdentifierExpected: boolean;
readonly importCompletionNode?: Node;
}
type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag };
type Request =
| { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag }
| { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }
| { readonly kind: CompletionDataKind.Keywords, keywords: readonly SyntaxKind[] };
export const enum CompletionKind {
ObjectPropertyDeclaration,
@ -1101,13 +1122,17 @@ namespace ts.Completions {
let location = getTouchingPropertyName(sourceFile, position);
if (contextToken) {
const importCompletionCandidate = getImportCompletionNode(contextToken);
if (importCompletionCandidate === SyntaxKind.FromKeyword) {
return { kind: CompletionDataKind.Keywords, keywords: [SyntaxKind.FromKeyword] };
}
// Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
// added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
// is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
// to opt in with the `includeCompletionsForImportStatements` user preference.
importCompletionNode = preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText
? getImportCompletionNode(contextToken)
: undefined;
if (importCompletionCandidate && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
importCompletionNode = importCompletionCandidate;
}
// Bail out if this is a known invalid completion location
if (!importCompletionNode && isCompletionListBlocker(contextToken)) {
log("Returning an empty list because completion was requested in an invalid position.");
@ -3041,7 +3066,7 @@ namespace ts.Completions {
function getImportCompletionNode(contextToken: Node) {
const candidate = getCandidate();
return candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile()) ? candidate : undefined;
return candidate === SyntaxKind.FromKeyword || candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile()) ? candidate : undefined;
function getCandidate() {
const parent = contextToken.parent;
@ -3049,9 +3074,13 @@ namespace ts.Completions {
return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined;
}
if (isNamedImports(parent) || isNamespaceImport(parent)) {
return isModuleSpecifierMissingOrEmpty(parent.parent.parent.moduleSpecifier) && (isNamespaceImport(parent) || parent.elements.length < 2) && !parent.parent.name
? parent.parent.parent
: undefined;
if (isModuleSpecifierMissingOrEmpty(parent.parent.parent.moduleSpecifier) && (isNamespaceImport(parent) || parent.elements.length < 2) && !parent.parent.name) {
// At `import { ... } |` or `import * as Foo |`, the only possible completion is `from`
return contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier
? SyntaxKind.FromKeyword
: parent.parent.parent;
}
return undefined;
}
if (isImportKeyword(contextToken) && isSourceFile(parent)) {
// A lone import keyword with nothing following it does not parse as a statement at all

View file

@ -73,3 +73,29 @@
}
});
});
// @Filename: /index13.ts
//// import {} /*13*/
// @Filename: /index14.ts
//// import {} f/*14*/
// @Filename: /index15.ts
//// import * as foo /*15*/
// @Filename: /index16.ts
//// import * as foo f/*16*/
[13, 14, 15, 16].forEach(marker => {
verify.completions({
marker: "" + marker,
exact: {
name: "from",
sortText: completion.SortText.GlobalsOrKeywords,
},
preferences: {
includeCompletionsForImportStatements: true,
includeInsertTextCompletions: true,
}
});
});