microsoft/typespec

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3e669c74c1e16a47afe2b98706bdcf8dad4434af

Branches

Tags

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

Clone

HTTPS

Download ZIP

packages/compiler/server/completion.ts

276lines · modecode

1import {
2 CompletionItem,
3 CompletionItemKind,
4 CompletionItemTag,
5 CompletionList,
6 CompletionParams,
7 MarkupKind,
8 TextEdit,
9} from "vscode-languageserver";
10import {
11 IdentifierNode,
12 Node,
13 Program,
14 StringLiteralNode,
15 SymbolFlags,
16 SyntaxKind,
17 Type,
18 TypeSpecScriptNode,
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, resolveTspMain } 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
144 if (resolveTspMain(libPackageJson) !== undefined) {
145 const range = {
146 start: file.file.getLineAndCharacterOfPosition(node.pos + 1),
147 end: file.file.getLineAndCharacterOfPosition(node.end - 1),
148 };
149 completions.items.push({
150 textEdit: TextEdit.replace(range, dependency),
151 label: dependency,
152 kind: CompletionItemKind.Module,
153 });
154 }
155 }
156 }
157}
158
159async function addImportCompletion(context: CompletionContext, node: StringLiteralNode) {
160 if (node.value.startsWith("./") || node.value.startsWith("../")) {
161 await addRelativePathCompletion(context, node);
162 } else if (!node.value.startsWith(".")) {
163 await addLibraryImportCompletion(context, node);
164 }
165}
166
167async function addRelativePathCompletion(
168 { program, completions, file }: CompletionContext,
169 node: StringLiteralNode
170) {
171 const documentPath = file.file.path;
172 const documentFile = getBaseFileName(documentPath);
173 const documentDir = getDirectoryPath(documentPath);
174 const nodevalueDir = hasTrailingDirectorySeparator(node.value)
175 ? node.value
176 : getDirectoryPath(node.value);
177 const mainTypeSpec = resolvePath(documentDir, nodevalueDir);
178 const files = (await program.host.readDir(mainTypeSpec)).filter(
179 (x) => x !== documentFile && x !== "node_modules"
180 );
181 for (const file of files) {
182 const extension = getAnyExtensionFromPath(file);
183 switch (extension) {
184 case ".tsp":
185 case ".js":
186 case ".mjs":
187 completions.items.push({
188 label: file,
189 commitCharacters: [],
190 kind: CompletionItemKind.File,
191 });
192 break;
193 case "":
194 completions.items.push({
195 label: file,
196 commitCharacters: [],
197 kind: CompletionItemKind.Folder,
198 });
199 break;
200 }
201 }
202}
203
204/**
205 * Add completion options for an identifier.
206 */
207function addIdentifierCompletion(
208 { program, completions }: CompletionContext,
209 node: IdentifierNode
210) {
211 const result = program.checker.resolveCompletions(node);
212 if (result.size === 0) {
213 return;
214 }
215 for (const [key, { sym, label }] of result) {
216 let kind: CompletionItemKind;
217 let deprecated = false;
218 const type = sym.type ?? program.checker.getTypeForNode(sym.declarations[0]);
219 if (sym.flags & (SymbolFlags.Function | SymbolFlags.Decorator)) {
220 kind = CompletionItemKind.Function;
221 } else if (
222 sym.flags & SymbolFlags.Namespace &&
223 sym.declarations[0].kind !== SyntaxKind.NamespaceStatement
224 ) {
225 kind = CompletionItemKind.Module;
226 } else {
227 kind = getCompletionItemKind(program, type);
228 deprecated = isDeprecated(program, type);
229 }
230 const documentation = getTypeDetails(program, type);
231 const item: CompletionItem = {
232 label: label ?? key,
233 documentation: documentation
234 ? {
235 kind: MarkupKind.Markdown,
236 value: documentation,
237 }
238 : undefined,
239 kind,
240 insertText: key,
241 };
242 if (deprecated) {
243 item.tags = [CompletionItemTag.Deprecated];
244 }
245 completions.items.push(item);
246 }
247
248 if (node.parent?.kind === SyntaxKind.TypeReference) {
249 addKeywordCompletion("identifier", completions);
250 }
251}
252
253function getCompletionItemKind(program: Program, target: Type): CompletionItemKind {
254 switch (target.node?.kind) {
255 case SyntaxKind.EnumStatement:
256 case SyntaxKind.UnionStatement:
257 return CompletionItemKind.Enum;
258 case SyntaxKind.EnumMember:
259 case SyntaxKind.UnionVariant:
260 return CompletionItemKind.EnumMember;
261 case SyntaxKind.AliasStatement:
262 return CompletionItemKind.Variable;
263 case SyntaxKind.ModelStatement:
264 return CompletionItemKind.Class;
265 case SyntaxKind.ScalarStatement:
266 return CompletionItemKind.Unit;
267 case SyntaxKind.ModelProperty:
268 return CompletionItemKind.Field;
269 case SyntaxKind.OperationStatement:
270 return CompletionItemKind.Method;
271 case SyntaxKind.NamespaceStatement:
272 return CompletionItemKind.Module;
273 default:
274 return CompletionItemKind.Struct;
275 }
276}
277