diff --git a/Herebyfile.mjs b/Herebyfile.mjs
index a8650f7d29..93dee2dcc1 100644
--- a/Herebyfile.mjs
+++ b/Herebyfile.mjs
@@ -331,6 +331,7 @@ const enumDefs = [
{ name: "ModifierFlags", goPrefix: "ModifierFlags", goFile: "internal/ast/modifierflags.go", outDir: "_packages/native-preview/src/enums" },
{ name: "TokenFlags", goPrefix: "TokenFlags", goFile: "internal/ast/tokenflags.go", outDir: "_packages/native-preview/src/enums" },
{ name: "NodeBuilderFlags", goPrefix: "Flags", goFile: "internal/nodebuilder/types.go", outDir: "_packages/native-preview/src/enums" },
+ { name: "CompletionItemKind", goPrefix: "CompletionItemKind", goFile: "internal/lsp/lsproto/lsp_generated.go", outDir: "_packages/native-preview/src/enums" },
];
/**
diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts
index 3d26ec9245..5c87a9dda4 100644
--- a/_packages/native-preview/src/api/async/api.ts
+++ b/_packages/native-preview/src/api/async/api.ts
@@ -1,4 +1,5 @@
///
+import { CompletionItemKind } from "#enums/completionItemKind";
import { DiagnosticCategory } from "#enums/diagnosticCategory";
import { ElementFlags } from "#enums/elementFlags";
import { NodeBuilderFlags } from "#enums/nodeBuilderFlags";
@@ -40,6 +41,7 @@ import {
toPath,
} from "../path.ts";
import type {
+ CompletionInfoResponse,
ConfigResponse,
DocumentIdentifier,
DocumentPosition,
@@ -65,6 +67,9 @@ import {
import type {
AssertsIdentifierTypePredicate,
AssertsThisTypePredicate,
+ CompletionEntry,
+ CompletionInfo,
+ CompletionOptions,
ConditionalType,
Diagnostic,
FreshableType,
@@ -91,9 +96,9 @@ import type {
UnionType,
} from "./types.ts";
-export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
+export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions };
-export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };
+export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
/** Type alias for the snapshot-scoped object registry */
@@ -578,6 +583,25 @@ export class Checker {
}));
}
+ async getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): Promise {
+ const data = await this.client.apiRequest("getCompletionsAtPosition", {
+ snapshot: this.snapshotId,
+ project: this.projectId,
+ file: document,
+ position,
+ triggerCharacter: options?.triggerCharacter,
+ includeSymbol: options?.includeSymbol,
+ });
+ if (!data) return undefined;
+ return {
+ isIncomplete: data.isIncomplete,
+ entries: data.entries.map(e => ({
+ ...e,
+ symbol: e.symbol ? this.objectRegistry.getOrCreateSymbol(e.symbol) : undefined,
+ })),
+ };
+ }
+
getTypeAtLocation(node: Node): Promise;
getTypeAtLocation(nodes: readonly Node[]): Promise<(Type | undefined)[]>;
async getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Promise {
diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts
index 66a5184af1..d5c2f97ac6 100644
--- a/_packages/native-preview/src/api/async/types.ts
+++ b/_packages/native-preview/src/api/async/types.ts
@@ -1,3 +1,4 @@
+import type { CompletionItemKind } from "#enums/completionItemKind";
import type { DiagnosticCategory } from "#enums/diagnosticCategory";
import type { ElementFlags } from "#enums/elementFlags";
import type { ObjectFlags } from "#enums/objectFlags";
@@ -205,6 +206,37 @@ export interface IndexInfo {
readonly declaration?: NodeHandle;
}
+export interface CompletionEntryLabelDetails {
+ detail?: string;
+ description?: string;
+}
+
+/** Options for {@link Checker.getCompletionsAtPosition}. */
+export interface CompletionOptions {
+ triggerCharacter?: string;
+ /** Include a `symbol` property on each completion entry. Only populated for symbol-based completions (not keywords or literals). */
+ includeSymbol?: boolean;
+}
+
+/** A single completion item returned by {@link Checker.getCompletionsAtPosition}. */
+export interface CompletionEntry {
+ readonly name: string;
+ readonly kind?: CompletionItemKind;
+ readonly sortText?: string;
+ readonly insertText?: string;
+ readonly filterText?: string;
+ readonly detail?: string;
+ readonly labelDetails?: CompletionEntryLabelDetails;
+ /** The symbol associated with this completion entry. Only set when `includeSymbol: true` is passed and a symbol is available. */
+ readonly symbol?: Symbol;
+}
+
+/** The result of {@link Checker.getCompletionsAtPosition}. */
+export interface CompletionInfo {
+ readonly isIncomplete: boolean;
+ readonly entries: readonly CompletionEntry[];
+}
+
/**
* A diagnostic message from the TypeScript compiler.
*/
diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts
index bc501126f1..706f3b4750 100644
--- a/_packages/native-preview/src/api/proto.ts
+++ b/_packages/native-preview/src/api/proto.ts
@@ -1,3 +1,4 @@
+import type { CompletionItemKind } from "#enums/completionItemKind";
import {
documentURIToFileName,
fileNameToDocumentURI,
@@ -194,3 +195,24 @@ export interface ProfileParams {
export interface ProfileResult {
file: string;
}
+
+export interface CompletionEntryLabelDetailsResponse {
+ detail?: string;
+ description?: string;
+}
+
+export interface CompletionEntryResponse {
+ name: string;
+ kind?: CompletionItemKind;
+ sortText?: string;
+ insertText?: string;
+ filterText?: string;
+ detail?: string;
+ labelDetails?: CompletionEntryLabelDetailsResponse;
+ symbol?: SymbolResponse;
+}
+
+export interface CompletionInfoResponse {
+ isIncomplete: boolean;
+ entries: CompletionEntryResponse[];
+}
diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts
index 8e090615cf..7baa7d05d4 100644
--- a/_packages/native-preview/src/api/sync/api.ts
+++ b/_packages/native-preview/src/api/sync/api.ts
@@ -7,6 +7,7 @@
// Regenerate: npm run generate (from _packages/native-preview)
//
///
+import { CompletionItemKind } from "#enums/completionItemKind";
import { DiagnosticCategory } from "#enums/diagnosticCategory";
import { ElementFlags } from "#enums/elementFlags";
import { NodeBuilderFlags } from "#enums/nodeBuilderFlags";
@@ -48,6 +49,7 @@ import {
toPath,
} from "../path.ts";
import type {
+ CompletionInfoResponse,
ConfigResponse,
DocumentIdentifier,
DocumentPosition,
@@ -73,6 +75,9 @@ import {
import type {
AssertsIdentifierTypePredicate,
AssertsThisTypePredicate,
+ CompletionEntry,
+ CompletionInfo,
+ CompletionOptions,
ConditionalType,
Diagnostic,
FreshableType,
@@ -99,9 +104,9 @@ import type {
UnionType,
} from "./types.ts";
-export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
+export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind };
export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions };
-export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };
+export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType };
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
/** Type alias for the snapshot-scoped object registry */
@@ -586,6 +591,25 @@ export class Checker {
}));
}
+ getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): CompletionInfo | undefined {
+ const data = this.client.apiRequest("getCompletionsAtPosition", {
+ snapshot: this.snapshotId,
+ project: this.projectId,
+ file: document,
+ position,
+ triggerCharacter: options?.triggerCharacter,
+ includeSymbol: options?.includeSymbol,
+ });
+ if (!data) return undefined;
+ return {
+ isIncomplete: data.isIncomplete,
+ entries: data.entries.map(e => ({
+ ...e,
+ symbol: e.symbol ? this.objectRegistry.getOrCreateSymbol(e.symbol) : undefined,
+ })),
+ };
+ }
+
getTypeAtLocation(node: Node): Type | undefined;
getTypeAtLocation(nodes: readonly Node[]): (Type | undefined)[];
getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Type | (Type | undefined)[] | undefined {
diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts
index c0acae5814..90226d1bb7 100644
--- a/_packages/native-preview/src/api/sync/types.ts
+++ b/_packages/native-preview/src/api/sync/types.ts
@@ -6,6 +6,7 @@
// Source: src/api/async/types.ts
// Regenerate: npm run generate (from _packages/native-preview)
//
+import type { CompletionItemKind } from "#enums/completionItemKind";
import type { DiagnosticCategory } from "#enums/diagnosticCategory";
import type { ElementFlags } from "#enums/elementFlags";
import type { ObjectFlags } from "#enums/objectFlags";
@@ -213,6 +214,37 @@ export interface IndexInfo {
readonly declaration?: NodeHandle;
}
+export interface CompletionEntryLabelDetails {
+ detail?: string;
+ description?: string;
+}
+
+/** Options for {@link Checker.getCompletionsAtPosition}. */
+export interface CompletionOptions {
+ triggerCharacter?: string;
+ /** Include a `symbol` property on each completion entry. Only populated for symbol-based completions (not keywords or literals). */
+ includeSymbol?: boolean;
+}
+
+/** A single completion item returned by {@link Checker.getCompletionsAtPosition}. */
+export interface CompletionEntry {
+ readonly name: string;
+ readonly kind?: CompletionItemKind;
+ readonly sortText?: string;
+ readonly insertText?: string;
+ readonly filterText?: string;
+ readonly detail?: string;
+ readonly labelDetails?: CompletionEntryLabelDetails;
+ /** The symbol associated with this completion entry. Only set when `includeSymbol: true` is passed and a symbol is available. */
+ readonly symbol?: Symbol;
+}
+
+/** The result of {@link Checker.getCompletionsAtPosition}. */
+export interface CompletionInfo {
+ readonly isIncomplete: boolean;
+ readonly entries: readonly CompletionEntry[];
+}
+
/**
* A diagnostic message from the TypeScript compiler.
*/
diff --git a/_packages/native-preview/src/enums/completionItemKind.enum.ts b/_packages/native-preview/src/enums/completionItemKind.enum.ts
new file mode 100644
index 0000000000..c5a4140c45
--- /dev/null
+++ b/_packages/native-preview/src/enums/completionItemKind.enum.ts
@@ -0,0 +1,29 @@
+// Code generated by Herebyfile.mjs generate:enums from internal/lsp/lsproto/lsp_generated.go. DO NOT EDIT.
+
+export enum CompletionItemKind {
+ Text = 1,
+ Method = 2,
+ Function = 3,
+ Constructor = 4,
+ Field = 5,
+ Variable = 6,
+ Class = 7,
+ Interface = 8,
+ Module = 9,
+ Property = 10,
+ Unit = 11,
+ Value = 12,
+ Enum = 13,
+ Keyword = 14,
+ Snippet = 15,
+ Color = 16,
+ File = 17,
+ Reference = 18,
+ Folder = 19,
+ EnumMember = 20,
+ Constant = 21,
+ Struct = 22,
+ Event = 23,
+ Operator = 24,
+ TypeParameter = 25,
+}
diff --git a/_packages/native-preview/src/enums/completionItemKind.ts b/_packages/native-preview/src/enums/completionItemKind.ts
new file mode 100644
index 0000000000..cb1e5d9aa3
--- /dev/null
+++ b/_packages/native-preview/src/enums/completionItemKind.ts
@@ -0,0 +1,29 @@
+// Code generated by Herebyfile.mjs generate:enums from internal/lsp/lsproto/lsp_generated.go. DO NOT EDIT.
+export var CompletionItemKind: any;
+(function (CompletionItemKind) {
+ CompletionItemKind[CompletionItemKind["Text"] = 1] = "Text";
+ CompletionItemKind[CompletionItemKind["Method"] = 2] = "Method";
+ CompletionItemKind[CompletionItemKind["Function"] = 3] = "Function";
+ CompletionItemKind[CompletionItemKind["Constructor"] = 4] = "Constructor";
+ CompletionItemKind[CompletionItemKind["Field"] = 5] = "Field";
+ CompletionItemKind[CompletionItemKind["Variable"] = 6] = "Variable";
+ CompletionItemKind[CompletionItemKind["Class"] = 7] = "Class";
+ CompletionItemKind[CompletionItemKind["Interface"] = 8] = "Interface";
+ CompletionItemKind[CompletionItemKind["Module"] = 9] = "Module";
+ CompletionItemKind[CompletionItemKind["Property"] = 10] = "Property";
+ CompletionItemKind[CompletionItemKind["Unit"] = 11] = "Unit";
+ CompletionItemKind[CompletionItemKind["Value"] = 12] = "Value";
+ CompletionItemKind[CompletionItemKind["Enum"] = 13] = "Enum";
+ CompletionItemKind[CompletionItemKind["Keyword"] = 14] = "Keyword";
+ CompletionItemKind[CompletionItemKind["Snippet"] = 15] = "Snippet";
+ CompletionItemKind[CompletionItemKind["Color"] = 16] = "Color";
+ CompletionItemKind[CompletionItemKind["File"] = 17] = "File";
+ CompletionItemKind[CompletionItemKind["Reference"] = 18] = "Reference";
+ CompletionItemKind[CompletionItemKind["Folder"] = 19] = "Folder";
+ CompletionItemKind[CompletionItemKind["EnumMember"] = 20] = "EnumMember";
+ CompletionItemKind[CompletionItemKind["Constant"] = 21] = "Constant";
+ CompletionItemKind[CompletionItemKind["Struct"] = 22] = "Struct";
+ CompletionItemKind[CompletionItemKind["Event"] = 23] = "Event";
+ CompletionItemKind[CompletionItemKind["Operator"] = 24] = "Operator";
+ CompletionItemKind[CompletionItemKind["TypeParameter"] = 25] = "TypeParameter";
+})(CompletionItemKind || (CompletionItemKind = {}));
diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts
index 996b507acb..ae2fe5c7af 100644
--- a/_packages/native-preview/test/async/api.test.ts
+++ b/_packages/native-preview/test/async/api.test.ts
@@ -2407,6 +2407,89 @@ describe("Checker - isTypeAssignableTo", () => {
});
});
+describe("Checker - getCompletionsAtPosition", () => {
+ test("returns member completions after a dot", async () => {
+ const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`;
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": src,
+ });
+ try {
+ const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ // Position right after "obj." — member completion trigger
+ const pos = src.indexOf("obj.") + "obj.".length;
+ const completions = await project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." });
+ assert.ok(completions, "Expected completions to be returned");
+ assert.ok(completions.entries.length > 0, "Expected at least one completion entry");
+ assert.ok(completions.entries.some(e => e.name === "name"), "Expected 'name' property in completions");
+ assert.ok(completions.entries.some(e => e.name === "age"), "Expected 'age' property in completions");
+ assert.ok(completions.entries.every(e => e.symbol === undefined), "Expected no symbol information");
+ }
+ finally {
+ await api.close();
+ }
+ });
+
+ test("completion entries include sortText", async () => {
+ const src = `\nconst obj = { value: 1 };\nobj.\n`;
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": src,
+ });
+ try {
+ const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ const pos = src.indexOf("obj.") + "obj.".length;
+ const completions = await project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." });
+ assert.ok(completions);
+ assert.ok(completions.entries.length > 0);
+ assert.ok(completions.entries.some(e => e.sortText !== undefined), "Expected sortText on all entries");
+ }
+ finally {
+ await api.close();
+ }
+ });
+
+ test("returns undefined for a non-existent file", async () => {
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": `export {};`,
+ });
+ try {
+ const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ const completions = await project.checker.getCompletionsAtPosition("/src/does-not-exist.ts", 0);
+ assert.equal(completions, undefined, "Expected undefined for non-existent file");
+ }
+ finally {
+ await api.close();
+ }
+ });
+
+ test("includeSymbol: true populates symbol on property completions", async () => {
+ const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`;
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": src,
+ });
+ try {
+ const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ const pos = src.indexOf("obj.") + "obj.".length;
+ const completions = await project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: ".", includeSymbol: true });
+ assert.ok(completions, "Expected completions");
+ const nameEntry = completions.entries.find(e => e.name === "name");
+ assert.ok(nameEntry, "Expected 'name' entry");
+ assert.ok(nameEntry.symbol, "Expected symbol to be set on 'name' entry when includeSymbol: true");
+ assert.equal(nameEntry.symbol.name, "name", "Symbol name should match completion name");
+ }
+ finally {
+ await api.close();
+ }
+ });
+});
+
describe("Emitter - printNode", () => {
const emitterFiles = {
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts
index ef0a2430af..8aeaa64bdb 100644
--- a/_packages/native-preview/test/sync/api.test.ts
+++ b/_packages/native-preview/test/sync/api.test.ts
@@ -2415,6 +2415,89 @@ describe("Checker - isTypeAssignableTo", () => {
});
});
+describe("Checker - getCompletionsAtPosition", () => {
+ test("returns member completions after a dot", () => {
+ const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`;
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": src,
+ });
+ try {
+ const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ // Position right after "obj." — member completion trigger
+ const pos = src.indexOf("obj.") + "obj.".length;
+ const completions = project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." });
+ assert.ok(completions, "Expected completions to be returned");
+ assert.ok(completions.entries.length > 0, "Expected at least one completion entry");
+ assert.ok(completions.entries.some(e => e.name === "name"), "Expected 'name' property in completions");
+ assert.ok(completions.entries.some(e => e.name === "age"), "Expected 'age' property in completions");
+ assert.ok(completions.entries.every(e => e.symbol === undefined), "Expected no symbol information");
+ }
+ finally {
+ api.close();
+ }
+ });
+
+ test("completion entries include sortText", () => {
+ const src = `\nconst obj = { value: 1 };\nobj.\n`;
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": src,
+ });
+ try {
+ const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ const pos = src.indexOf("obj.") + "obj.".length;
+ const completions = project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." });
+ assert.ok(completions);
+ assert.ok(completions.entries.length > 0);
+ assert.ok(completions.entries.some(e => e.sortText !== undefined), "Expected sortText on all entries");
+ }
+ finally {
+ api.close();
+ }
+ });
+
+ test("returns undefined for a non-existent file", () => {
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": `export {};`,
+ });
+ try {
+ const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ const completions = project.checker.getCompletionsAtPosition("/src/does-not-exist.ts", 0);
+ assert.equal(completions, undefined, "Expected undefined for non-existent file");
+ }
+ finally {
+ api.close();
+ }
+ });
+
+ test("includeSymbol: true populates symbol on property completions", () => {
+ const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`;
+ const api = spawnAPI({
+ "/tsconfig.json": "{}",
+ "/src/main.ts": src,
+ });
+ try {
+ const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
+ const project = snapshot.getProject("/tsconfig.json")!;
+ const pos = src.indexOf("obj.") + "obj.".length;
+ const completions = project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: ".", includeSymbol: true });
+ assert.ok(completions, "Expected completions");
+ const nameEntry = completions.entries.find(e => e.name === "name");
+ assert.ok(nameEntry, "Expected 'name' entry");
+ assert.ok(nameEntry.symbol, "Expected symbol to be set on 'name' entry when includeSymbol: true");
+ assert.equal(nameEntry.symbol.name, "name", "Symbol name should match completion name");
+ }
+ finally {
+ api.close();
+ }
+ });
+});
+
describe("Emitter - printNode", () => {
const emitterFiles = {
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
diff --git a/internal/api/proto.go b/internal/api/proto.go
index 5b09155d23..8951fb6481 100644
--- a/internal/api/proto.go
+++ b/internal/api/proto.go
@@ -127,6 +127,9 @@ const (
MethodGetReferencedSymbolsForNode Method = "getReferencedSymbolsForNode"
MethodGetSignatureUsages Method = "getSignatureUsages"
+ // Language service methods
+ MethodGetCompletionsAtPosition Method = "getCompletionsAtPosition"
+
// Diagnostic methods
MethodGetSyntacticDiagnostics Method = "getSyntacticDiagnostics"
MethodGetSemanticDiagnostics Method = "getSemanticDiagnostics"
@@ -361,6 +364,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){
MethodGetReferencesToSymbolInFile: unmarshallerFor[GetReferencesToSymbolInFileParams],
MethodGetReferencedSymbolsForNode: unmarshallerFor[GetReferencedSymbolsForNodeParams],
MethodGetSignatureUsages: unmarshallerFor[GetSignatureUsagesParams],
+ MethodGetCompletionsAtPosition: unmarshallerFor[GetCompletionsAtPositionParams],
MethodPrintNode: unmarshallerFor[PrintNodeParams],
MethodGetAnyType: unmarshallerFor[GetIntrinsicTypeParams],
MethodGetStringType: unmarshallerFor[GetIntrinsicTypeParams],
@@ -764,6 +768,40 @@ type SignatureUsageResponse struct {
Call NodeHandle `json:"call,omitempty"`
}
+// GetCompletionsAtPositionParams are the parameters for the getCompletionsAtPosition method.
+type GetCompletionsAtPositionParams struct {
+ Snapshot SnapshotID `json:"snapshot"`
+ Project ProjectID `json:"project"`
+ File DocumentIdentifier `json:"file"`
+ Position uint32 `json:"position"`
+ TriggerCharacter *string `json:"triggerCharacter,omitempty"`
+ IncludeSymbol bool `json:"includeSymbol,omitempty"`
+}
+
+// CompletionEntryLabelDetailsResponse holds additional label display text for a completion entry.
+type CompletionEntryLabelDetailsResponse struct {
+ Detail *string `json:"detail,omitempty"`
+ Description *string `json:"description,omitempty"`
+}
+
+// CompletionEntryResponse represents a single completion item.
+type CompletionEntryResponse struct {
+ Name string `json:"name"`
+ Kind uint32 `json:"kind,omitempty"`
+ SortText *string `json:"sortText,omitempty"`
+ InsertText *string `json:"insertText,omitempty"`
+ FilterText *string `json:"filterText,omitempty"`
+ Detail *string `json:"detail,omitempty"`
+ LabelDetails *CompletionEntryLabelDetailsResponse `json:"labelDetails,omitempty"`
+ Symbol *SymbolResponse `json:"symbol,omitempty"`
+}
+
+// CompletionInfoResponse wraps a list of completion entries.
+type CompletionInfoResponse struct {
+ IsIncomplete bool `json:"isIncomplete"`
+ Entries []*CompletionEntryResponse `json:"entries"`
+}
+
// GetIntrinsicTypeParams is used for intrinsic type getters (anyType, stringType, etc.).
type GetIntrinsicTypeParams struct {
Snapshot SnapshotID `json:"snapshot"`
diff --git a/internal/api/session.go b/internal/api/session.go
index cf5a8fda2a..b9ec4ed0d7 100644
--- a/internal/api/session.go
+++ b/internal/api/session.go
@@ -544,6 +544,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json.
return s.handleGetReferencedSymbolsForNode(ctx, parsed.(*GetReferencedSymbolsForNodeParams))
case string(MethodGetSignatureUsages):
return s.handleGetSignatureUsages(ctx, parsed.(*GetSignatureUsagesParams))
+ case string(MethodGetCompletionsAtPosition):
+ return s.handleGetCompletionsAtPosition(ctx, parsed.(*GetCompletionsAtPositionParams))
default:
return nil, fmt.Errorf("unknown method: %s", method)
}
@@ -2307,6 +2309,59 @@ func (s *Session) handleGetSignatureUsages(ctx context.Context, params *GetSigna
return result, nil
}
+// handleGetCompletionsAtPosition returns completions at a position in a document.
+func (s *Session) handleGetCompletionsAtPosition(ctx context.Context, params *GetCompletionsAtPositionParams) (*CompletionInfoResponse, error) {
+ sd, err := s.getSnapshotData(params.Snapshot)
+ if err != nil {
+ return nil, err
+ }
+ program, err := sd.getProgram(params.Project)
+ if err != nil {
+ return nil, err
+ }
+ sourceFile := program.GetSourceFile(params.File.ToFileName())
+ if sourceFile == nil {
+ return nil, nil
+ }
+ langSvc, err := s.setupLanguageService(sd, program, params.Project, "")
+ if err != nil {
+ return nil, err
+ }
+ positionMap := sourceFile.GetPositionMap()
+ internalPos := positionMap.UTF16ToUTF8(int(params.Position))
+ result, err := langSvc.GetCompletionsAtPosition(ctx, sourceFile, internalPos, params.TriggerCharacter, params.IncludeSymbol)
+ if err != nil || result == nil {
+ return nil, err
+ }
+ entries := make([]*CompletionEntryResponse, 0, len(result.Items))
+ for _, item := range result.Items {
+ entry := &CompletionEntryResponse{
+ Name: item.Label,
+ SortText: item.SortText,
+ InsertText: item.InsertText,
+ FilterText: item.FilterText,
+ Detail: item.Detail,
+ }
+ if item.Kind != nil {
+ entry.Kind = uint32(*item.Kind)
+ }
+ if item.LabelDetails != nil {
+ entry.LabelDetails = &CompletionEntryLabelDetailsResponse{
+ Detail: item.LabelDetails.Detail,
+ Description: item.LabelDetails.Description,
+ }
+ }
+ if item.Symbol != nil {
+ entry.Symbol = sd.registerSymbol(item.Symbol)
+ }
+ entries = append(entries, entry)
+ }
+ return &CompletionInfoResponse{
+ IsIncomplete: result.IsIncomplete,
+ Entries: entries,
+ }, nil
+}
+
// handleGetReferencedSymbolsForNode returns node handles for all references found at a node.
func (s *Session) handleGetReferencedSymbolsForNode(ctx context.Context, params *GetReferencedSymbolsForNodeParams) ([]ReferencedSymbolEntry, error) {
sd, err := s.getSnapshotData(params.Snapshot)
diff --git a/internal/ls/completions.go b/internal/ls/completions.go
index b8d1dfeed4..5eb4015730 100644
--- a/internal/ls/completions.go
+++ b/internal/ls/completions.go
@@ -44,19 +44,36 @@ func (l *LanguageService) ProvideCompletion(
}
ctx = format.WithFormatCodeSettings(ctx, l.FormatOptions(), l.FormatOptions().NewLineCharacter)
position := int(l.converters.LineAndCharacterToPosition(file, LSPPosition))
- completionList, err := l.getCompletionsAtPosition(
+ completionListInternal, err := l.getCompletionsAtPosition(
ctx,
file,
position,
triggerCharacter,
+ false, /*includeSymbols*/
)
if err != nil {
return lsproto.CompletionItemsOrListOrNull{}, err
}
- completionList = ensureItemData(file.FileName(), position, completionList)
+ completionList := ensureItemData(file.FileName(), position, completionListInternal.toLSP())
return lsproto.CompletionItemsOrListOrNull{List: completionList}, nil
}
+func (l *LanguageService) GetCompletionsAtPosition(ctx context.Context, file *ast.SourceFile, position int, triggerCharacter *string, includeSymbols bool) (*CompletionList, error) {
+ return l.getCompletionsAtPosition(ctx, file, position, triggerCharacter, includeSymbols)
+}
+
+type CompletionItem struct {
+ *lsproto.CompletionItem
+ Symbol *ast.Symbol // non-nil for symbol completions when IncludeSymbols is set; nil otherwise
+}
+
+type CompletionList struct {
+ IsIncomplete bool
+ ItemDefaults *lsproto.CompletionItemDefaults
+ ApplyKind *lsproto.CompletionItemApplyKinds
+ Items []*CompletionItem
+}
+
func ensureItemData(fileName string, pos int, list *lsproto.CompletionList) *lsproto.CompletionList {
if list == nil {
return nil
@@ -106,7 +123,7 @@ type completionDataData struct {
}
type completionDataKeyword struct {
- keywordCompletions []*lsproto.CompletionItem
+ keywordCompletions []*CompletionItem
isNewIdentifierLocation bool
}
@@ -290,12 +307,31 @@ const (
globalsSearchFail
)
+func (l *CompletionList) toLSP() *lsproto.CompletionList {
+ if l == nil {
+ return nil
+ }
+ items := make([]*lsproto.CompletionItem, 0, len(l.Items))
+ for _, entry := range l.Items {
+ if entry != nil && entry.CompletionItem != nil {
+ items = append(items, entry.CompletionItem)
+ }
+ }
+ return &lsproto.CompletionList{
+ IsIncomplete: l.IsIncomplete,
+ ItemDefaults: l.ItemDefaults,
+ ApplyKind: l.ApplyKind,
+ Items: items,
+ }
+}
+
func (l *LanguageService) getCompletionsAtPosition(
ctx context.Context,
file *ast.SourceFile,
position int,
triggerCharacter *string,
-) (*lsproto.CompletionList, error) {
+ includeSymbols bool,
+) (*CompletionList, error) {
_, previousToken := getRelevantTokens(position, file)
if triggerCharacter != nil && !IsInString(file, position, previousToken) && !isValidTrigger(file, *triggerCharacter, previousToken, position) {
return nil, nil
@@ -304,9 +340,7 @@ func (l *LanguageService) getCompletionsAtPosition(
if triggerCharacter != nil && *triggerCharacter == " " {
// `isValidTrigger` ensures we are at `import |`
if l.UserPreferences().IncludeCompletionsForImportStatements.IsTrue() {
- return &lsproto.CompletionList{
- IsIncomplete: true,
- }, nil
+ return &CompletionList{IsIncomplete: true}, nil
}
return nil, nil
}
@@ -325,6 +359,7 @@ func (l *LanguageService) getCompletionsAtPosition(
previousToken,
checker,
compilerOptions,
+ includeSymbols,
)
if stringCompletions != nil {
return stringCompletions, nil
@@ -363,6 +398,7 @@ func (l *LanguageService) getCompletionsAtPosition(
data,
position,
optionalReplacementSpan,
+ includeSymbols,
)
if err != nil {
return nil, err
@@ -530,10 +566,12 @@ func (l *LanguageService) getCompletionData(
if importStatementCompletionInfo.keywordCompletion != ast.KindUnknown {
if importStatementCompletionInfo.isKeywordOnlyCompletion {
return &completionDataKeyword{
- keywordCompletions: []*lsproto.CompletionItem{{
- Label: scanner.TokenToString(importStatementCompletionInfo.keywordCompletion),
- Kind: new(lsproto.CompletionItemKindKeyword),
- SortText: new(string(SortTextGlobalsOrKeywords)),
+ keywordCompletions: []*CompletionItem{{
+ CompletionItem: &lsproto.CompletionItem{
+ Label: scanner.TokenToString(importStatementCompletionInfo.keywordCompletion),
+ Kind: new(lsproto.CompletionItemKindKeyword),
+ SortText: new(string(SortTextGlobalsOrKeywords)),
+ },
}},
isNewIdentifierLocation: importStatementCompletionInfo.isNewIdentifierLocation,
}, nil
@@ -1694,7 +1732,8 @@ func (l *LanguageService) completionInfoFromData(
data *completionDataData,
position int,
optionalReplacementSpan *lsproto.Range,
-) (*lsproto.CompletionList, error) {
+ includeSymbols bool,
+) (*CompletionList, error) {
keywordFilters := data.keywordFilters
isNewIdentifierLocation := data.isNewIdentifierLocation
contextToken := data.contextToken
@@ -1743,6 +1782,7 @@ func (l *LanguageService) completionInfoFromData(
position,
file,
compilerOptions,
+ includeSymbols,
)
if data.keywordFilters != KeywordCompletionFiltersNone {
@@ -1760,14 +1800,14 @@ func (l *LanguageService) completionInfoFromData(
for _, keywordEntry := range getContextualKeywords(file, contextToken, position) {
if !uniqueNames.Has(keywordEntry.Label) {
uniqueNames.Add(keywordEntry.Label)
- sortedEntries = append(sortedEntries, keywordEntry)
+ sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: keywordEntry})
}
}
for _, literal := range literals {
literalEntry := createCompletionItemForLiteral(file, preferences, literal)
uniqueNames.Add(literalEntry.Label)
- sortedEntries = append(sortedEntries, literalEntry)
+ sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: literalEntry})
}
if !isChecked {
@@ -1795,7 +1835,7 @@ func (l *LanguageService) completionInfoFromData(
return nil, err
}
if casesItem != nil {
- sortedEntries = append(sortedEntries, casesItem)
+ sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: casesItem})
}
}
}
@@ -1809,7 +1849,7 @@ func (l *LanguageService) completionInfoFromData(
optionalReplacementSpan,
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: data.hasUnresolvedAutoImports,
ItemDefaults: itemDefaults,
Items: sortedEntries,
@@ -1824,7 +1864,8 @@ func (l *LanguageService) getCompletionEntriesFromSymbols(
position int,
file *ast.SourceFile,
compilerOptions *core.CompilerOptions,
-) (uniqueNames collections.Set[string], sortedEntries []*lsproto.CompletionItem) {
+ includeSymbols bool,
+) (uniqueNames collections.Set[string], sortedEntries []*CompletionItem) {
closestSymbolDeclaration := getClosestSymbolDeclaration(data.contextToken, data.location)
useSemicolons := lsutil.ProbablyUsesSemicolons(file)
isMemberCompletion := isMemberCompletionKind(data.completionKind)
@@ -1890,7 +1931,11 @@ func (l *LanguageService) getCompletionEntriesFromSymbols(
!(symbol.Parent == nil &&
!core.Some(symbol.Declarations, func(d *ast.Node) bool { return ast.GetSourceFileOfNode(d) == file }))
uniques[name] = shouldShadowLaterSymbols
- sortedEntries = append(sortedEntries, entry)
+ var sym *ast.Symbol
+ if includeSymbols {
+ sym = symbol
+ }
+ sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: entry, Symbol: sym})
}
for _, autoImport := range data.autoImports {
@@ -1944,7 +1989,7 @@ func (l *LanguageService) getCompletionEntriesFromSymbols(
if isShadowed, _ := uniques[autoImport.Fix.Name]; !isShadowed {
uniques[autoImport.Fix.Name] = false
- sortedEntries = append(sortedEntries, entry)
+ sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: entry})
}
}
@@ -3221,16 +3266,19 @@ var (
})
)
-func cloneItems(items []*lsproto.CompletionItem) []*lsproto.CompletionItem {
- result := make([]*lsproto.CompletionItem, len(items))
+func cloneItems(items []*lsproto.CompletionItem) []*CompletionItem {
+ if items == nil {
+ return nil
+ }
+ entries := make([]*CompletionItem, len(items))
for i, item := range items {
itemClone := *item
- result[i] = &itemClone
+ entries[i] = &CompletionItem{CompletionItem: &itemClone}
}
- return result
+ return entries
}
-func getKeywordCompletions(keywordFilter KeywordCompletionFilters, filterOutTsOnlyKeywords bool) []*lsproto.CompletionItem {
+func getKeywordCompletions(keywordFilter KeywordCompletionFilters, filterOutTsOnlyKeywords bool) []*CompletionItem {
if !filterOutTsOnlyKeywords {
return cloneItems(getTypescriptKeywordCompletions(keywordFilter))
}
@@ -3391,8 +3439,8 @@ func (l *LanguageService) getJSCompletionEntries(
file *ast.SourceFile,
position int,
uniqueNames *collections.Set[string],
- sortedEntries []*lsproto.CompletionItem,
-) []*lsproto.CompletionItem {
+ sortedEntries []*CompletionItem,
+) []*CompletionItem {
nameTable := file.GetNameTable()
for name, pos := range nameTable {
// Skip identifiers produced only from the current location
@@ -3401,11 +3449,13 @@ func (l *LanguageService) getJSCompletionEntries(
}
if !uniqueNames.Has(name) && scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
uniqueNames.Add(name)
- sortedEntries = append(sortedEntries, &lsproto.CompletionItem{
- Label: name,
- Kind: new(lsproto.CompletionItemKindText),
- SortText: new(string(SortTextJavascriptIdentifiers)),
- CommitCharacters: new([]string{}),
+ sortedEntries = append(sortedEntries, &CompletionItem{
+ CompletionItem: &lsproto.CompletionItem{
+ Label: name,
+ Kind: new(lsproto.CompletionItemKindText),
+ SortText: new(string(SortTextJavascriptIdentifiers)),
+ CommitCharacters: new([]string{}),
+ },
})
}
}
@@ -4193,7 +4243,7 @@ func (l *LanguageService) setItemDefaults(
ctx context.Context,
position int,
file *ast.SourceFile,
- items []*lsproto.CompletionItem,
+ items []*CompletionItem,
defaultCommitCharacters *[]string,
optionalReplacementSpan *lsproto.Range,
) *lsproto.CompletionItemDefaults {
@@ -4262,10 +4312,10 @@ func (l *LanguageService) specificKeywordCompletionInfo(
ctx context.Context,
position int,
file *ast.SourceFile,
- items []*lsproto.CompletionItem,
+ items []*CompletionItem,
isNewIdentifierLocation bool,
optionalReplacementSpan *lsproto.Range,
-) *lsproto.CompletionList {
+) *CompletionList {
defaultCommitCharacters := getDefaultCommitCharacters(isNewIdentifierLocation)
itemDefaults := l.setItemDefaults(
ctx,
@@ -4275,7 +4325,7 @@ func (l *LanguageService) specificKeywordCompletionInfo(
&defaultCommitCharacters,
optionalReplacementSpan,
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,
@@ -4287,7 +4337,7 @@ func (l *LanguageService) getJsxClosingTagCompletion(
location *ast.Node,
file *ast.SourceFile,
position int,
-) *lsproto.CompletionList {
+) *CompletionList {
// We wanna walk up the tree till we find a JSX closing element.
jsxClosingElement := ast.FindAncestorOrQuit(location, func(node *ast.Node) ast.FindAncestorResult {
switch node.Kind {
@@ -4323,7 +4373,7 @@ func (l *LanguageService) getJsxClosingTagCompletion(
optionalReplacementSpan := new(l.createLspRangeFromNode(jsxClosingElement.TagName(), file))
defaultCommitCharacters := getDefaultCommitCharacters(false /*isNewIdentifierLocation*/)
- item := l.createLSPCompletionItem(
+ lspItem := l.createLSPCompletionItem(
ctx,
fullClosingTag, /*name*/
"", /*insertText*/
@@ -4344,7 +4394,10 @@ func (l *LanguageService) getJsxClosingTagCompletion(
nil, /*autoImportEntryData*/ // !!! jsx autoimports
nil, /*detail*/
)
- items := []*lsproto.CompletionItem{item}
+ item := &CompletionItem{
+ CompletionItem: lspItem,
+ }
+ items := []*CompletionItem{item}
itemDefaults := l.setItemDefaults(
ctx,
position,
@@ -4354,7 +4407,7 @@ func (l *LanguageService) getJsxClosingTagCompletion(
optionalReplacementSpan,
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,
@@ -4460,7 +4513,7 @@ func (l *LanguageService) getLabelCompletionsAtPosition(
file *ast.SourceFile,
position int,
optionalReplacementSpan *lsproto.Range,
-) *lsproto.CompletionList {
+) *CompletionList {
items := l.getLabelStatementCompletions(ctx, node, file, position)
if len(items) == 0 {
return nil
@@ -4474,7 +4527,7 @@ func (l *LanguageService) getLabelCompletionsAtPosition(
&defaultCommitCharacters,
optionalReplacementSpan,
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,
@@ -4486,9 +4539,9 @@ func (l *LanguageService) getLabelStatementCompletions(
node *ast.BreakOrContinueStatement,
file *ast.SourceFile,
position int,
-) []*lsproto.CompletionItem {
+) []*CompletionItem {
var uniques collections.Set[string]
- var items []*lsproto.CompletionItem
+ var items []*CompletionItem
current := node
for current != nil {
if ast.IsFunctionLike(current) {
@@ -4498,7 +4551,7 @@ func (l *LanguageService) getLabelStatementCompletions(
name := current.Label().Text()
if !uniques.Has(name) {
uniques.Add(name)
- items = append(items, l.createLSPCompletionItem(
+ lspItem := l.createLSPCompletionItem(
ctx,
name,
"", /*insertText*/
@@ -4518,7 +4571,10 @@ func (l *LanguageService) getLabelStatementCompletions(
"", /*source*/
nil, /*autoImportEntryData*/
nil, /*detail*/
- ))
+ )
+ items = append(items, &CompletionItem{
+ CompletionItem: lspItem,
+ })
}
}
current = current.Parent
@@ -4895,7 +4951,7 @@ func (l *LanguageService) getCompletionItemDetails(
case *completionDataJSDocParameterName:
return createSimpleDetails(item, data.Name, docFormat)
case *completionDataKeyword:
- if core.Some(request.keywordCompletions, func(c *lsproto.CompletionItem) bool {
+ if core.Some(request.keywordCompletions, func(c *CompletionItem) bool {
return c.Label == data.Name
}) {
return createSimpleDetails(item, data.Name, docFormat)
@@ -5300,8 +5356,8 @@ func (l *LanguageService) jsDocCompletionInfo(
ctx context.Context,
position int,
file *ast.SourceFile,
- items []*lsproto.CompletionItem,
-) *lsproto.CompletionList {
+ items []*CompletionItem,
+) *CompletionList {
defaultCommitCharacters := getDefaultCommitCharacters(false /*isNewIdentifierLocation*/)
itemDefaults := l.setItemDefaults(
ctx,
@@ -5311,7 +5367,7 @@ func (l *LanguageService) jsDocCompletionInfo(
&defaultCommitCharacters,
nil, /*optionalReplacementSpan*/
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,
@@ -5431,11 +5487,11 @@ var jsDocTagCompletionItems = sync.OnceValue(func() []*lsproto.CompletionItem {
return items
})
-func getJSDocTagNameCompletions() []*lsproto.CompletionItem {
+func getJSDocTagNameCompletions() []*CompletionItem {
return cloneItems(jsDocTagNameCompletionItems())
}
-func getJSDocTagCompletions() []*lsproto.CompletionItem {
+func getJSDocTagCompletions() []*CompletionItem {
return cloneItems(jsDocTagCompletionItems())
}
@@ -5447,7 +5503,7 @@ func getJSDocParameterCompletions(
options *core.CompilerOptions,
preferences lsutil.UserPreferences,
tagNameOnly bool,
-) []*lsproto.CompletionItem {
+) []*CompletionItem {
currentToken := astnav.GetTokenAtPosition(file, position)
if !ast.IsJSDocTag(currentToken) && !currentToken.IsJSDoc() {
return nil
@@ -5482,7 +5538,7 @@ func getJSDocParameterCompletions(
}
}
paramIndex := -1
- return core.MapNonNil(fun.Parameters(), func(param *ast.ParameterDeclarationNode) *lsproto.CompletionItem {
+ return core.MapNonNil(fun.Parameters(), func(param *ast.ParameterDeclarationNode) *CompletionItem {
paramIndex++
if paramIndex < paramTagCount {
// This parameter is already annotated.
@@ -5525,12 +5581,14 @@ func getJSDocParameterCompletions(
}
}
- return &lsproto.CompletionItem{
- Label: displayText,
- Kind: new(lsproto.CompletionItemKindVariable),
- SortText: new(string(SortTextLocationPriority)),
- InsertText: strPtrTo(snippetText),
- InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil),
+ return &CompletionItem{
+ CompletionItem: &lsproto.CompletionItem{
+ Label: displayText,
+ Kind: new(lsproto.CompletionItemKindVariable),
+ SortText: new(string(SortTextLocationPriority)),
+ InsertText: strPtrTo(snippetText),
+ InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil),
+ },
}
} else if paramIndex == paramTagCount {
// Destructuring parameter; do it positionally
@@ -5566,12 +5624,14 @@ func getJSDocParameterCompletions(
displayText = strings.TrimPrefix(displayText, "@")
snippetText = strings.TrimPrefix(snippetText, "@")
}
- return &lsproto.CompletionItem{
- Label: displayText,
- Kind: new(lsproto.CompletionItemKindVariable),
- SortText: new(string(SortTextLocationPriority)),
- InsertText: strPtrTo(snippetText),
- InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil),
+ return &CompletionItem{
+ CompletionItem: &lsproto.CompletionItem{
+ Label: displayText,
+ Kind: new(lsproto.CompletionItemKindVariable),
+ SortText: new(string(SortTextLocationPriority)),
+ InsertText: strPtrTo(snippetText),
+ InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil),
+ },
}
}
return nil
@@ -5836,7 +5896,7 @@ func jsDocParamElementWorker(
return nil
}
-func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*lsproto.CompletionItem {
+func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*CompletionItem {
if !ast.IsIdentifier(tag.Name()) {
return nil
}
@@ -5852,7 +5912,7 @@ func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*l
tags = jsDoc.AsJSDoc().Tags.Nodes
}
- return core.MapNonNil(fn.Parameters(), func(param *ast.ParameterDeclarationNode) *lsproto.CompletionItem {
+ return core.MapNonNil(fn.Parameters(), func(param *ast.ParameterDeclarationNode) *CompletionItem {
if !ast.IsIdentifier(param.Name()) {
return nil
}
@@ -5867,10 +5927,12 @@ func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*l
return nil
}
- return &lsproto.CompletionItem{
- Label: name,
- Kind: new(lsproto.CompletionItemKindVariable),
- SortText: new(string(SortTextLocationPriority)),
+ return &CompletionItem{
+ CompletionItem: &lsproto.CompletionItem{
+ Label: name,
+ Kind: new(lsproto.CompletionItemKindVariable),
+ SortText: new(string(SortTextLocationPriority)),
+ },
}
})
}
diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go
index 9bba30eb9a..f5240bcf32 100644
--- a/internal/ls/string_completions.go
+++ b/internal/ls/string_completions.go
@@ -57,7 +57,8 @@ func (l *LanguageService) getStringLiteralCompletions(
contextToken *ast.Node,
checker *checker.Checker,
compilerOptions *core.CompilerOptions,
-) *lsproto.CompletionList {
+ includeSymbols bool,
+) *CompletionList {
if isInReferenceComment(file, position) {
entries := l.getTripleSlashReferenceCompletions(file, position, l.GetProgram(), checker)
return l.convertPathCompletions(ctx, entries, file, position)
@@ -81,6 +82,7 @@ func (l *LanguageService) getStringLiteralCompletions(
position,
checker,
compilerOptions,
+ includeSymbols,
)
}
return nil
@@ -94,7 +96,8 @@ func (l *LanguageService) convertStringLiteralCompletions(
position int,
typeChecker *checker.Checker,
options *core.CompilerOptions,
-) *lsproto.CompletionList {
+ includeSymbols bool,
+) *CompletionList {
if completion == nil {
return nil
}
@@ -121,6 +124,7 @@ func (l *LanguageService) convertStringLiteralCompletions(
position,
file,
options,
+ includeSymbols, /*includeSymbols*/
)
defaultCommitCharacters := getDefaultCommitCharacters(completion.hasIndexSignature)
itemDefaults := l.setItemDefaults(
@@ -131,7 +135,7 @@ func (l *LanguageService) convertStringLiteralCompletions(
&defaultCommitCharacters,
optionalReplacementRange,
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,
@@ -146,9 +150,9 @@ func (l *LanguageService) convertStringLiteralCompletions(
} else {
quoteChar = printer.QuoteCharDoubleQuote
}
- items := core.Map(completion.types, func(t *checker.StringLiteralType) *lsproto.CompletionItem {
+ items := core.Map(completion.types, func(t *checker.StringLiteralType) *CompletionItem {
name := printer.EscapeString(t.AsLiteralType().Value().(string), quoteChar)
- return l.createLSPCompletionItem(
+ lspItem := l.createLSPCompletionItem(
ctx,
name,
"", /*insertText*/
@@ -169,6 +173,9 @@ func (l *LanguageService) convertStringLiteralCompletions(
nil, /*autoImportEntryData*/
nil, /*detail*/
)
+ return &CompletionItem{
+ CompletionItem: lspItem,
+ }
})
defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier)
itemDefaults := l.setItemDefaults(
@@ -179,7 +186,7 @@ func (l *LanguageService) convertStringLiteralCompletions(
&defaultCommitCharacters,
nil, /*optionalReplacementSpan*/
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,
@@ -194,10 +201,10 @@ func (l *LanguageService) convertPathCompletions(
pathCompletions []*pathCompletion,
file *ast.SourceFile,
position int,
-) *lsproto.CompletionList {
+) *CompletionList {
isNewIdentifierLocation := true // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
defaultCommitCharacters := getDefaultCommitCharacters(isNewIdentifierLocation)
- items := core.Map(pathCompletions, func(pathCompletion *pathCompletion) *lsproto.CompletionItem {
+ items := core.Map(pathCompletions, func(pathCompletion *pathCompletion) *CompletionItem {
var replacementSpan *lsproto.Range
if pathCompletion.textRange != nil {
replacementSpan = new(l.createLspRangeFromBounds(pathCompletion.textRange.Pos(), pathCompletion.textRange.End(), file))
@@ -206,7 +213,7 @@ func (l *LanguageService) convertPathCompletions(
if !strings.HasSuffix(pathCompletion.name, pathCompletion.extension) {
detail += pathCompletion.extension
}
- return l.createLSPCompletionItem(
+ lspItem := l.createLSPCompletionItem(
ctx,
pathCompletion.name,
"", /*insertText*/
@@ -227,6 +234,9 @@ func (l *LanguageService) convertPathCompletions(
nil, /*autoImportEntryData*/
&detail,
)
+ return &CompletionItem{
+ CompletionItem: lspItem,
+ }
})
itemDefaults := l.setItemDefaults(
ctx,
@@ -236,7 +246,7 @@ func (l *LanguageService) convertPathCompletions(
&defaultCommitCharacters,
nil, /*optionalReplacementSpan*/
)
- return &lsproto.CompletionList{
+ return &CompletionList{
IsIncomplete: false,
ItemDefaults: itemDefaults,
Items: items,