Skip to content

Commit bb7fb7d

Browse files
author
Andy
authored
For getCompletionsAtPosition, require a flag to provide completions with code actions (#19687)
* For getCompletionsAtPosition, require a flag to provide completions with code actions * Change name * Increase API version * Update API baselines * Add comment * Update API baseline
1 parent f75a1dc commit bb7fb7d

25 files changed

+102
-64
lines changed

src/harness/fourslash.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -855,8 +855,8 @@ namespace FourSlash {
855855
});
856856
}
857857

858-
public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
859-
const completions = this.getCompletionListAtCaret();
858+
public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean, options?: ts.GetCompletionsAtPositionOptions) {
859+
const completions = this.getCompletionListAtCaret(options);
860860
if (completions) {
861861
this.assertItemInCompletionList(completions.entries, entryId, text, documentation, kind, spanIndex, hasAction);
862862
}
@@ -876,13 +876,13 @@ namespace FourSlash {
876876
* @param expectedKind the kind of symbol (see ScriptElementKind)
877877
* @param spanIndex the index of the range that the completion item's replacement text span should match
878878
*/
879-
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
879+
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number, options?: ts.GetCompletionsAtPositionOptions) {
880880
let replacementSpan: ts.TextSpan;
881881
if (spanIndex !== undefined) {
882882
replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex);
883883
}
884884

885-
const completions = this.getCompletionListAtCaret();
885+
const completions = this.getCompletionListAtCaret(options);
886886
if (completions) {
887887
let filterCompletions = completions.entries.filter(e => e.name === entryId.name && e.source === entryId.source);
888888
filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind) : filterCompletions;
@@ -1195,11 +1195,11 @@ Actual: ${stringify(fullActual)}`);
11951195
this.raiseError(`verifyReferencesAtPositionListContains failed - could not find the item: ${stringify(missingItem)} in the returned list: (${stringify(references)})`);
11961196
}
11971197

1198-
private getCompletionListAtCaret() {
1199-
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition);
1198+
private getCompletionListAtCaret(options?: ts.GetCompletionsAtPositionOptions): ts.CompletionInfo {
1199+
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options);
12001200
}
12011201

1202-
private getCompletionEntryDetails(entryName: string, source?: string) {
1202+
private getCompletionEntryDetails(entryName: string, source?: string): ts.CompletionEntryDetails {
12031203
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source);
12041204
}
12051205

@@ -1790,7 +1790,7 @@ Actual: ${stringify(fullActual)}`);
17901790
}
17911791
else if (prevChar === " " && /A-Za-z_/.test(ch)) {
17921792
/* Completions */
1793-
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset);
1793+
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, { includeExternalModuleExports: false });
17941794
}
17951795

17961796
if (i % checkCadence === 0) {
@@ -2365,7 +2365,7 @@ Actual: ${stringify(fullActual)}`);
23652365
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
23662366
this.goToMarker(markerName);
23672367

2368-
const actualCompletion = this.getCompletionListAtCaret().entries.find(e => e.name === options.name && e.source === options.source);
2368+
const actualCompletion = this.getCompletionListAtCaret({ includeExternalModuleExports: true }).entries.find(e => e.name === options.name && e.source === options.source);
23692369

23702370
if (!actualCompletion.hasAction) {
23712371
this.raiseError(`Completion for ${options.name} does not have an associated action.`);
@@ -3803,15 +3803,15 @@ namespace FourSlashInterface {
38033803

38043804
// Verifies the completion list contains the specified symbol. The
38053805
// completion list is brought up if necessary
3806-
public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
3806+
public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean, options?: ts.GetCompletionsAtPositionOptions) {
38073807
if (typeof entryId === "string") {
38083808
entryId = { name: entryId, source: undefined };
38093809
}
38103810
if (this.negative) {
3811-
this.state.verifyCompletionListDoesNotContain(entryId, text, documentation, kind, spanIndex);
3811+
this.state.verifyCompletionListDoesNotContain(entryId, text, documentation, kind, spanIndex, options);
38123812
}
38133813
else {
3814-
this.state.verifyCompletionListContains(entryId, text, documentation, kind, spanIndex, hasAction);
3814+
this.state.verifyCompletionListContains(entryId, text, documentation, kind, spanIndex, hasAction, options);
38153815
}
38163816
}
38173817

src/harness/harnessLanguageService.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,8 @@ namespace Harness.LanguageService {
413413
getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications {
414414
return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length));
415415
}
416-
getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo {
417-
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position));
416+
getCompletionsAtPosition(fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions | undefined): ts.CompletionInfo {
417+
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, options));
418418
}
419419
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: ts.FormatCodeOptions | undefined, source: string | undefined): ts.CompletionEntryDetails {
420420
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(options), source));

src/harness/unittests/tsserverProjectSystem.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1248,13 +1248,13 @@ namespace ts.projectSystem {
12481248
service.checkNumberOfProjects({ externalProjects: 1 });
12491249
checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
12501250

1251-
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2);
1251+
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false });
12521252
// should contain completions for string
12531253
assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'");
12541254
assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'");
12551255

12561256
service.closeClientFile(f2.path);
1257-
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2);
1257+
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false });
12581258
// should contain completions for string
12591259
assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'");
12601260
assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'");
@@ -1280,11 +1280,11 @@ namespace ts.projectSystem {
12801280
service.checkNumberOfProjects({ externalProjects: 1 });
12811281
checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]);
12821282

1283-
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0);
1283+
const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false });
12841284
assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'");
12851285

12861286
service.closeClientFile(f2.path);
1287-
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0);
1287+
const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false });
12881288
assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'");
12891289
const sf2 = service.externalProjects[0].getLanguageService().getProgram().getSourceFile(f2.path);
12901290
assert.equal(sf2.text, "");
@@ -1845,7 +1845,7 @@ namespace ts.projectSystem {
18451845

18461846
// Check identifiers defined in HTML content are available in .ts file
18471847
const project = configuredProjectAt(projectService, 0);
1848-
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1);
1848+
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, { includeExternalModuleExports: false });
18491849
assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
18501850

18511851
// Close HTML file
@@ -1859,7 +1859,7 @@ namespace ts.projectSystem {
18591859
checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
18601860

18611861
// Check identifiers defined in HTML content are not available in .ts file
1862-
completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5);
1862+
completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, { includeExternalModuleExports: false });
18631863
assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`);
18641864
});
18651865

src/server/client.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ namespace ts.server {
170170
};
171171
}
172172

173-
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
174-
const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position);
173+
getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): CompletionInfo {
174+
const args: protocol.CompletionsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), ...options };
175175

176176
const request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
177177
const response = this.processResponse<protocol.CompletionsResponse>(request);

src/server/protocol.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,11 @@ namespace ts.server.protocol {
16191619
* Optional prefix to apply to possible completions.
16201620
*/
16211621
prefix?: string;
1622+
/**
1623+
* If enabled, TypeScript will search through all external modules' exports and add them to the completions list.
1624+
* This affects lone identifier completions but not completions on the right hand side of `obj.`.
1625+
*/
1626+
includeExternalModuleExports: boolean;
16221627
}
16231628

16241629
/**

src/server/session.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1200,10 +1200,10 @@ namespace ts.server {
12001200
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file);
12011201
const position = this.getPosition(args, scriptInfo);
12021202

1203-
const completions = project.getLanguageService().getCompletionsAtPosition(file, position);
1203+
const completions = project.getLanguageService().getCompletionsAtPosition(file, position, args);
12041204
if (simplifiedResult) {
12051205
return mapDefined<CompletionEntry, protocol.CompletionEntry>(completions && completions.entries, entry => {
1206-
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
1206+
if (completions.isMemberCompletion || startsWith(entry.name.toLowerCase(), prefix.toLowerCase())) {
12071207
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source } = entry;
12081208
const convertedSpan = replacementSpan ? this.toLocationTextSpan(replacementSpan, scriptInfo) : undefined;
12091209
// Use `hasAction || undefined` to avoid serializing `false`.
@@ -1831,10 +1831,10 @@ namespace ts.server {
18311831
[CommandNames.FormatRangeFull]: (request: protocol.FormatRequest) => {
18321832
return this.requiredResponse(this.getFormattingEditsForRangeFull(request.arguments));
18331833
},
1834-
[CommandNames.Completions]: (request: protocol.CompletionDetailsRequest) => {
1834+
[CommandNames.Completions]: (request: protocol.CompletionsRequest) => {
18351835
return this.requiredResponse(this.getCompletions(request.arguments, /*simplifiedResult*/ true));
18361836
},
1837-
[CommandNames.CompletionsFull]: (request: protocol.CompletionDetailsRequest) => {
1837+
[CommandNames.CompletionsFull]: (request: protocol.CompletionsRequest) => {
18381838
return this.requiredResponse(this.getCompletions(request.arguments, /*simplifiedResult*/ false));
18391839
},
18401840
[CommandNames.CompletionDetails]: (request: protocol.CompletionDetailsRequest) => {

src/services/completions.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace ts.Completions {
2828
sourceFile: SourceFile,
2929
position: number,
3030
allSourceFiles: ReadonlyArray<SourceFile>,
31+
options: GetCompletionsAtPositionOptions,
3132
): CompletionInfo | undefined {
3233
if (isInReferenceComment(sourceFile, position)) {
3334
const entries = PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
@@ -38,7 +39,7 @@ namespace ts.Completions {
3839
return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log);
3940
}
4041

41-
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
42+
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, options);
4243
if (!completionData) {
4344
return undefined;
4445
}
@@ -380,7 +381,7 @@ namespace ts.Completions {
380381
{ name, source }: CompletionEntryIdentifier,
381382
allSourceFiles: ReadonlyArray<SourceFile>,
382383
): { type: "symbol", symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | { type: "request", request: Request } | { type: "none" } {
383-
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
384+
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, { includeExternalModuleExports: true });
384385
if (!completionData) {
385386
return { type: "none" };
386387
}
@@ -521,6 +522,7 @@ namespace ts.Completions {
521522
sourceFile: SourceFile,
522523
position: number,
523524
allSourceFiles: ReadonlyArray<SourceFile>,
525+
options: GetCompletionsAtPositionOptions,
524526
): CompletionData | undefined {
525527
const isJavaScriptFile = isSourceFileJavaScript(sourceFile);
526528

@@ -918,7 +920,9 @@ namespace ts.Completions {
918920
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
919921

920922
symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);
921-
getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "");
923+
if (options.includeExternalModuleExports) {
924+
getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "");
925+
}
922926
filterGlobalCompletion(symbols);
923927

924928
return true;

src/services/services.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
namespace ts {
3333
/** The version of the language service API */
34-
export const servicesVersion = "0.6";
34+
export const servicesVersion = "0.7";
3535

3636
/* @internal */
3737
let ruleProvider: formatting.RulesProvider;
@@ -1326,9 +1326,17 @@ namespace ts {
13261326
return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)];
13271327
}
13281328

1329-
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
1329+
function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = { includeExternalModuleExports: false }): CompletionInfo {
13301330
synchronizeHostData();
1331-
return Completions.getCompletionsAtPosition(host, program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, program.getSourceFiles());
1331+
return Completions.getCompletionsAtPosition(
1332+
host,
1333+
program.getTypeChecker(),
1334+
log,
1335+
program.getCompilerOptions(),
1336+
getValidSourceFile(fileName),
1337+
position,
1338+
program.getSourceFiles(),
1339+
options);
13321340
}
13331341

13341342
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions?: FormatCodeSettings, source?: string): CompletionEntryDetails {

src/services/shims.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ namespace ts {
140140
getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string;
141141
getEncodedSemanticClassifications(fileName: string, start: number, length: number): string;
142142

143-
getCompletionsAtPosition(fileName: string, position: number): string;
143+
getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): string;
144144
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/, source: string | undefined): string;
145145

146146
getQuickInfoAtPosition(fileName: string, position: number): string;
@@ -898,10 +898,10 @@ namespace ts {
898898
* to provide at the given source position and providing a member completion
899899
* list if requested.
900900
*/
901-
public getCompletionsAtPosition(fileName: string, position: number) {
901+
public getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined) {
902902
return this.forwardJSONCall(
903-
`getCompletionsAtPosition('${fileName}', ${position})`,
904-
() => this.languageService.getCompletionsAtPosition(fileName, position)
903+
`getCompletionsAtPosition('${fileName}', ${position}, ${options})`,
904+
() => this.languageService.getCompletionsAtPosition(fileName, position, options)
905905
);
906906
}
907907

0 commit comments

Comments
 (0)