microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
064161d8277a88b3facccca58df87aa332bf9187

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/compiler/server/completion.ts

275lines · modecode

1import {
2 CompletionItem,
3 CompletionItemKind,
4 CompletionItemTag,
5 CompletionList,
6 CompletionParams,
7 MarkupKind,
8 TextEdit,
9} from "vscode-languageserver";
10import {
11 TypeSpecScriptNode,
12 IdentifierNode,
13 Node,
14 Program,
15 StringLiteralNode,
16 SymbolFlags,
17 SyntaxKind,
18 Type,
19} from "../core/index.js";
20import {
21 getAnyExtensionFromPath,
22 getBaseFileName,
23 getDirectoryPath,
24 hasTrailingDirectorySeparator,
25 resolvePath,
26} from "../core/path-utils.js";
27import { findProjectRoot, loadFile } from "../core/util.js";
28import { isDeprecated } from "../lib/decorators.js";
29import { getTypeDetails } from "./type-details.js";
30
31export type CompletionContext = {
32 program: Program;
33 params: CompletionParams;
34 file: TypeSpecScriptNode;
35 completions: CompletionList;
36};
37
38export async function resolveCompletion(
39 context: CompletionContext,
40 node: Node | undefined
41): Promise<CompletionList> {
42 const completions: CompletionList = {
43 isIncomplete: false,
44 items: [],
45 };
46 if (node === undefined) {
47 addKeywordCompletion("root", completions);
48 } else {
49 switch (node.kind) {
50 case SyntaxKind.NamespaceStatement:
51 addKeywordCompletion("namespace", completions);
52 break;
53 case SyntaxKind.Identifier:
54 addIdentifierCompletion(context, node);
55 break;
56 case SyntaxKind.StringLiteral:
57 if (node.parent && node.parent.kind === SyntaxKind.ImportStatement) {
58 await addImportCompletion(context, node);
59 }
60 break;
61 }
62 }
63 return completions;
64}
65
66interface KeywordArea {
67 root?: boolean;
68 namespace?: boolean;
69 model?: boolean;
70 identifier?: boolean;
71}
72
73const keywords = [
74 // Root only
75 ["import", { root: true }],
76
77 // Root and namespace
78 ["using", { root: true, namespace: true }],
79 ["model", { root: true, namespace: true }],
80 ["scalar", { root: true, namespace: true }],
81 ["namespace", { root: true, namespace: true }],
82 ["interface", { root: true, namespace: true }],
83 ["union", { root: true, namespace: true }],
84 ["enum", { root: true, namespace: true }],
85 ["alias", { root: true, namespace: true }],
86 ["op", { root: true, namespace: true }],
87 ["dec", { root: true, namespace: true }],
88 ["fn", { root: true, namespace: true }],
89
90 // On model `model Foo <keyword> ...`
91 ["extends", { model: true }],
92 ["is", { model: true }],
93
94 // On identifier`
95 ["true", { identifier: true }],
96 ["false", { identifier: true }],
97 ["unknown", { identifier: true }],
98 ["void", { identifier: true }],
99 ["never", { identifier: true }],
100
101 // Modifiers
102 ["extern", { root: true, namespace: true }],
103] as const;
104
105function addKeywordCompletion(area: keyof KeywordArea, completions: CompletionList) {
106 const filteredKeywords = keywords.filter(([_, x]) => area in x);
107 for (const [keyword] of filteredKeywords) {
108 completions.items.push({
109 label: keyword,
110 kind: CompletionItemKind.Keyword,
111 });
112 }
113}
114
115async function addLibraryImportCompletion(
116 { program, file, completions }: CompletionContext,
117 node: StringLiteralNode
118) {
119 const documentPath = file.file.path;
120 const projectRoot = await findProjectRoot(program.host, documentPath);
121 if (projectRoot !== undefined) {
122 const [packagejson] = await loadFile(
123 program.host,
124 resolvePath(projectRoot, "package.json"),
125 JSON.parse,
126 program.reportDiagnostic
127 );
128 let dependencies: string[] = [];
129 if (packagejson.dependencies !== undefined) {
130 dependencies = dependencies.concat(Object.keys(packagejson.dependencies));
131 }
132 if (packagejson.peerDependencies !== undefined) {
133 dependencies = dependencies.concat(Object.keys(packagejson.peerDependencies));
134 }
135 for (const dependency of dependencies) {
136 const nodeProjectRoot = resolvePath(projectRoot, "node_modules", dependency);
137 const [libPackageJson] = await loadFile(
138 program.host,
139 resolvePath(nodeProjectRoot, "package.json"),
140 JSON.parse,
141 program.reportDiagnostic
142 );
143 if (libPackageJson.typespecMain !== undefined) {
144 const range = {
145 start: file.file.getLineAndCharacterOfPosition(node.pos + 1),
146 end: file.file.getLineAndCharacterOfPosition(node.end - 1),
147 };
148 completions.items.push({
149 textEdit: TextEdit.replace(range, dependency),
150 label: dependency,
151 kind: CompletionItemKind.Module,
152 });
153 }
154 }
155 }
156}
157
158async function addImportCompletion(context: CompletionContext, node: StringLiteralNode) {
159 if (node.value.startsWith("./") || node.value.startsWith("../")) {
160 await addRelativePathCompletion(context, node);
161 } else if (!node.value.startsWith(".")) {
162 await addLibraryImportCompletion(context, node);
163 }
164}
165
166async function addRelativePathCompletion(
167 { program, completions, file }: CompletionContext,
168 node: StringLiteralNode
169) {
170 const documentPath = file.file.path;
171 const documentFile = getBaseFileName(documentPath);
172 const documentDir = getDirectoryPath(documentPath);
173 const nodevalueDir = hasTrailingDirectorySeparator(node.value)
174 ? node.value
175 : getDirectoryPath(node.value);
176 const mainTypeSpec = resolvePath(documentDir, nodevalueDir);
177 const files = (await program.host.readDir(mainTypeSpec)).filter(
178 (x) => x !== documentFile && x !== "node_modules"
179 );
180 for (const file of files) {
181 const extension = getAnyExtensionFromPath(file);
182 switch (extension) {
183 case ".tsp":
184 case ".js":
185 case ".mjs":
186 completions.items.push({
187 label: file,
188 commitCharacters: [],
189 kind: CompletionItemKind.File,
190 });
191 break;
192 case "":
193 completions.items.push({
194 label: file,
195 commitCharacters: [],
196 kind: CompletionItemKind.Folder,
197 });
198 break;
199 }
200 }
201}
202
203/**
204 * Add completion options for an identifier.
205 */
206function addIdentifierCompletion(
207 { program, completions }: CompletionContext,
208 node: IdentifierNode
209) {
210 const result = program.checker.resolveCompletions(node);
211 if (result.size === 0) {
212 return;
213 }
214 for (const [key, { sym, label }] of result) {
215 let kind: CompletionItemKind;
216 let deprecated = false;
217 const type = sym.type ?? program.checker.getTypeForNode(sym.declarations[0]);
218 if (sym.flags & (SymbolFlags.Function | SymbolFlags.Decorator)) {
219 kind = CompletionItemKind.Function;
220 } else if (
221 sym.flags & SymbolFlags.Namespace &&
222 sym.declarations[0].kind !== SyntaxKind.NamespaceStatement
223 ) {
224 kind = CompletionItemKind.Module;
225 } else {
226 kind = getCompletionItemKind(program, type);
227 deprecated = isDeprecated(program, type);
228 }
229 const documentation = getTypeDetails(program, type);
230 const item: CompletionItem = {
231 label: label ?? key,
232 documentation: documentation
233 ? {
234 kind: MarkupKind.Markdown,
235 value: documentation,
236 }
237 : undefined,
238 kind,
239 insertText: key,
240 };
241 if (deprecated) {
242 item.tags = [CompletionItemTag.Deprecated];
243 }
244 completions.items.push(item);
245 }
246
247 if (node.parent?.kind === SyntaxKind.TypeReference) {
248 addKeywordCompletion("identifier", completions);
249 }
250}
251
252function getCompletionItemKind(program: Program, target: Type): CompletionItemKind {
253 switch (target.node?.kind) {
254 case SyntaxKind.EnumStatement:
255 case SyntaxKind.UnionStatement:
256 return CompletionItemKind.Enum;
257 case SyntaxKind.EnumMember:
258 case SyntaxKind.UnionVariant:
259 return CompletionItemKind.EnumMember;
260 case SyntaxKind.AliasStatement:
261 return CompletionItemKind.Variable;
262 case SyntaxKind.ModelStatement:
263 return CompletionItemKind.Class;
264 case SyntaxKind.ScalarStatement:
265 return CompletionItemKind.Unit;
266 case SyntaxKind.ModelProperty:
267 return CompletionItemKind.Field;
268 case SyntaxKind.OperationStatement:
269 return CompletionItemKind.Method;
270 case SyntaxKind.NamespaceStatement:
271 return CompletionItemKind.Module;
272 default:
273 return CompletionItemKind.Struct;
274 }
275}