@litsx/typescript 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/authored-semantics.cjs +18567 -0
- package/dist/authored-semantics.cjs.map +1 -0
- package/dist/editor-session.cjs +1345 -0
- package/dist/editor-session.cjs.map +1 -0
- package/dist/index.cjs +841 -0
- package/dist/index.cjs.map +1 -0
- package/dist/litsx-tsc.js +18025 -0
- package/dist/typecheck.cjs +931 -0
- package/dist/typecheck.cjs.map +1 -0
- package/dist/virtualization.cjs +113 -0
- package/dist/virtualization.cjs.map +1 -0
- package/package.json +76 -0
- package/src/authored-semantics.js +1543 -0
- package/src/editor-session.js +1360 -0
- package/src/index.js +844 -0
- package/src/litsx-tsc.js +4 -0
- package/src/typecheck.js +535 -0
- package/src/virtualization.js +125 -0
- package/tsserver-plugin.cjs +11 -0
|
@@ -0,0 +1,1360 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import defaultTs from "typescript";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
collectLitsxAuthoredDiagnostics,
|
|
7
|
+
createToolingVirtualLitsxSource,
|
|
8
|
+
decodeVirtualAttributeName,
|
|
9
|
+
getLitsxAttributeCompletionNames,
|
|
10
|
+
inferLitsxComponentEventNames,
|
|
11
|
+
inferLitsxComponentPropNames,
|
|
12
|
+
inferLitsxAttributeCompletionContext,
|
|
13
|
+
inferLitsxAttributeInfoAtPosition,
|
|
14
|
+
inferLitsxMarkupCompletionContext,
|
|
15
|
+
inferLitsxStaticHoistInfoAtPosition,
|
|
16
|
+
looksLikeLitsxJsx,
|
|
17
|
+
mapOriginalPositionToToolingVirtual,
|
|
18
|
+
remapToolingTextSpanToOriginal,
|
|
19
|
+
getLitsxMarkupCompletionNames,
|
|
20
|
+
remapVirtualText,
|
|
21
|
+
} from "./virtualization.js";
|
|
22
|
+
|
|
23
|
+
const QUERY_FILE_SUFFIX_BY_LANGUAGE_ID = {
|
|
24
|
+
litsx: ".tsx",
|
|
25
|
+
"litsx-jsx": ".jsx",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const SUPPORTED_SOURCE_EXTENSIONS = [
|
|
29
|
+
".litsx.jsx",
|
|
30
|
+
".litsx",
|
|
31
|
+
".tsx",
|
|
32
|
+
".ts",
|
|
33
|
+
".jsx",
|
|
34
|
+
".js",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const SCRIPT_KIND_BY_EXTENSION = {
|
|
38
|
+
"litsx.jsx": "JSX",
|
|
39
|
+
litsx: "TSX",
|
|
40
|
+
tsx: "TSX",
|
|
41
|
+
jsx: "JSX",
|
|
42
|
+
ts: "TS",
|
|
43
|
+
js: "JS",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const BINDING_HOVER_BY_PREFIX = {
|
|
47
|
+
"@": {
|
|
48
|
+
kindLabel: "event",
|
|
49
|
+
detail: "LitSX event listener binding",
|
|
50
|
+
},
|
|
51
|
+
".": {
|
|
52
|
+
kindLabel: "property",
|
|
53
|
+
detail: "LitSX property binding",
|
|
54
|
+
},
|
|
55
|
+
"?": {
|
|
56
|
+
kindLabel: "boolean",
|
|
57
|
+
detail: "LitSX boolean attribute binding",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const COMPLETION_KIND_BY_TS_KIND = {
|
|
62
|
+
keyword: "Keyword",
|
|
63
|
+
const: "Variable",
|
|
64
|
+
constElement: "Variable",
|
|
65
|
+
let: "Variable",
|
|
66
|
+
letElement: "Variable",
|
|
67
|
+
variable: "Variable",
|
|
68
|
+
variableElement: "Variable",
|
|
69
|
+
localVariableElement: "Variable",
|
|
70
|
+
memberVariableElement: "Property",
|
|
71
|
+
property: "Property",
|
|
72
|
+
function: "Function",
|
|
73
|
+
functionElement: "Function",
|
|
74
|
+
memberFunctionElement: "Function",
|
|
75
|
+
class: "Class",
|
|
76
|
+
classElement: "Class",
|
|
77
|
+
interface: "Interface",
|
|
78
|
+
interfaceElement: "Interface",
|
|
79
|
+
module: "Module",
|
|
80
|
+
moduleElement: "Module",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const SYMBOL_KIND_RULES = [
|
|
84
|
+
["Function", (ts) => ts.SymbolFlags.Function | ts.SymbolFlags.Method],
|
|
85
|
+
["Class", (ts) => ts.SymbolFlags.Class | ts.SymbolFlags.TypeAlias],
|
|
86
|
+
["Interface", (ts) => ts.SymbolFlags.Interface],
|
|
87
|
+
["Module", (ts) => ts.SymbolFlags.Module | ts.SymbolFlags.Namespace],
|
|
88
|
+
["Property", (ts) => ts.SymbolFlags.Property | ts.SymbolFlags.EnumMember],
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
function normalizeFileName(fileName) {
|
|
92
|
+
return path.resolve(fileName).replace(/\\/g, "/");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getParserPlugins(languageId) {
|
|
96
|
+
return languageId === "litsx" ? ["typescript"] : [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isRelevantFile(fileName) {
|
|
100
|
+
return /\.(jsx|tsx|litsx)$/.test(fileName) || fileName.endsWith(".litsx.jsx");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getPluginsForFile(fileName, languageId) {
|
|
104
|
+
return (languageId === "litsx" || /\.(tsx|litsx)$/.test(fileName ?? ""))
|
|
105
|
+
? ["typescript"]
|
|
106
|
+
: [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function createCompletionKindAdapter(adapter) {
|
|
110
|
+
if (typeof adapter === "function") {
|
|
111
|
+
return adapter;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (adapter && typeof adapter === "object") {
|
|
115
|
+
return (kind) => adapter[kind] ?? kind;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (kind) => kind;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function createDefaultLogger(logger) {
|
|
122
|
+
if (!logger) {
|
|
123
|
+
return () => {};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (typeof logger === "function") {
|
|
127
|
+
return logger;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof logger.appendLine === "function") {
|
|
131
|
+
return (message) => logger.appendLine(message);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return () => {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function remapDisplayParts(parts) {
|
|
138
|
+
return parts?.map((part) => ({
|
|
139
|
+
...part,
|
|
140
|
+
text: remapVirtualText(part.text),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function remapMessageText(messageText) {
|
|
145
|
+
if (typeof messageText === "string") {
|
|
146
|
+
return remapVirtualText(messageText);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!messageText || typeof messageText !== "object") {
|
|
150
|
+
return messageText;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
...messageText,
|
|
155
|
+
messageText: remapMessageText(messageText.messageText),
|
|
156
|
+
next: messageText.next?.map(remapMessageText),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function remapCompletionTextChanges(textChanges, targetFileName, virtualization) {
|
|
161
|
+
if (!Array.isArray(textChanges)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return textChanges.map((change) => {
|
|
166
|
+
const span = virtualization
|
|
167
|
+
? remapToolingTextSpanToOriginal(change.span, virtualization)
|
|
168
|
+
: change.span;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
fileName: targetFileName,
|
|
172
|
+
start: span.start,
|
|
173
|
+
length: span.length,
|
|
174
|
+
newText: change.newText,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function startsAtAuthoredSyntax(sourceText, start) {
|
|
180
|
+
if (typeof sourceText !== "string" || typeof start !== "number" || !(start >= 0 && start < sourceText.length)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return "@.?".includes(sourceText[start]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getBindingHoverInfo(attributeInfo) {
|
|
188
|
+
if (!attributeInfo) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const bindingInfo = BINDING_HOVER_BY_PREFIX[attributeInfo.prefix] ?? {
|
|
193
|
+
kindLabel: "binding",
|
|
194
|
+
detail: "LitSX binding",
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
name: attributeInfo.name,
|
|
199
|
+
start: attributeInfo.start,
|
|
200
|
+
length: attributeInfo.length,
|
|
201
|
+
kindLabel: bindingInfo.kindLabel,
|
|
202
|
+
detail: `${bindingInfo.detail} for <${attributeInfo.tagName}>.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getCompletionKindToken(name) {
|
|
207
|
+
return name.startsWith("@") ? "Event" : "Property";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getContextualCompletionEdit(name, context) {
|
|
211
|
+
const replacementStart = (context?.start ?? 0) + 1;
|
|
212
|
+
const replacementLength = Math.max((context?.length ?? 1) - 1, 0);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
insertText: name.slice(1),
|
|
216
|
+
filterText: name.slice(1),
|
|
217
|
+
start: replacementStart,
|
|
218
|
+
length: replacementLength,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getModuleExtension(ts, fileName) {
|
|
223
|
+
if (fileName.endsWith(".litsx.jsx") || fileName.endsWith(".jsx")) {
|
|
224
|
+
return ts.Extension.Jsx;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (fileName.endsWith(".litsx") || fileName.endsWith(".tsx")) {
|
|
228
|
+
return ts.Extension.Tsx;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (fileName.endsWith(".ts")) {
|
|
232
|
+
return ts.Extension.Ts;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return ts.Extension.Js;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isPathLikeModuleName(moduleName) {
|
|
239
|
+
return moduleName.startsWith("./") || moduleName.startsWith("../") || moduleName.startsWith("/");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getTransparentResolutionCandidates(modulePath) {
|
|
243
|
+
const requestedExtension = SUPPORTED_SOURCE_EXTENSIONS.find((extension) => modulePath.endsWith(extension)) ?? null;
|
|
244
|
+
|
|
245
|
+
if (requestedExtension) {
|
|
246
|
+
return [
|
|
247
|
+
modulePath,
|
|
248
|
+
path.join(modulePath, `index${requestedExtension}`),
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return [
|
|
253
|
+
...SUPPORTED_SOURCE_EXTENSIONS.map((extension) => `${modulePath}${extension}`),
|
|
254
|
+
...SUPPORTED_SOURCE_EXTENSIONS.map((extension) => path.join(modulePath, `index${extension}`)),
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function createResolvedModule(ts, resolvedFileName) {
|
|
259
|
+
return {
|
|
260
|
+
resolvedFileName,
|
|
261
|
+
extension: getModuleExtension(ts, resolvedFileName),
|
|
262
|
+
isExternalLibraryImport: false,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function findDeepestNodeAtPosition(sourceFile, position) {
|
|
267
|
+
let bestNode = null;
|
|
268
|
+
|
|
269
|
+
function visit(node) {
|
|
270
|
+
if (position < node.pos || position >= node.end) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
bestNode = node;
|
|
275
|
+
node.forEachChild(visit);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
visit(sourceFile);
|
|
279
|
+
return bestNode;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getSourceFileScriptKind(ts, fileName, languageId) {
|
|
283
|
+
if (fileName.endsWith(".litsx.jsx") || languageId === "litsx-jsx" || fileName.endsWith(".jsx")) {
|
|
284
|
+
return ts.ScriptKind.JSX;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (fileName.endsWith(".litsx") || languageId === "litsx" || fileName.endsWith(".tsx")) {
|
|
288
|
+
return ts.ScriptKind.TSX;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (fileName.endsWith(".ts")) {
|
|
292
|
+
return ts.ScriptKind.TS;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return ts.ScriptKind.JS;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function isCustomElementsDefineCall(ts, node) {
|
|
299
|
+
return ts.isCallExpression(node)
|
|
300
|
+
&& ts.isPropertyAccessExpression(node.expression)
|
|
301
|
+
&& ts.isIdentifier(node.expression.expression)
|
|
302
|
+
&& node.expression.expression.text === "customElements"
|
|
303
|
+
&& node.expression.name.text === "define";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function isLikelyLitsxComponentReference(ts, sourceFile, node) {
|
|
307
|
+
if (!ts.isIdentifier(node) || !/^[A-Z]/.test(node.text)) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let matches = false;
|
|
312
|
+
|
|
313
|
+
function visit(current) {
|
|
314
|
+
if (matches) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (
|
|
319
|
+
ts.isImportDeclaration(current)
|
|
320
|
+
&& ts.isStringLiteral(current.moduleSpecifier)
|
|
321
|
+
&& (current.moduleSpecifier.text.endsWith(".litsx") || current.moduleSpecifier.text.endsWith(".litsx.jsx"))
|
|
322
|
+
) {
|
|
323
|
+
const bindings = current.importClause?.namedBindings;
|
|
324
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
325
|
+
matches = bindings.elements.some((element) => element.name.text === node.text);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (
|
|
330
|
+
ts.isVariableDeclaration(current)
|
|
331
|
+
&& ts.isIdentifier(current.name)
|
|
332
|
+
&& current.name.text === node.text
|
|
333
|
+
&& (ts.isArrowFunction(current.initializer) || ts.isFunctionExpression(current.initializer))
|
|
334
|
+
) {
|
|
335
|
+
matches = true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (ts.isFunctionDeclaration(current) && current.name?.text === node.text) {
|
|
339
|
+
matches = true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!matches) {
|
|
343
|
+
current.forEachChild(visit);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
sourceFile.forEachChild(visit);
|
|
348
|
+
return matches;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function shouldSuppressCustomElementConstructorDiagnostic(ts, sourceFile, diagnostic) {
|
|
352
|
+
if (diagnostic.code !== 2345 || typeof diagnostic.start !== "number") {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const node = findDeepestNodeAtPosition(sourceFile, diagnostic.start);
|
|
357
|
+
let current = node;
|
|
358
|
+
|
|
359
|
+
while (current && !isCustomElementsDefineCall(ts, current)) {
|
|
360
|
+
current = current.parent ?? null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!current || !isCustomElementsDefineCall(ts, current)) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const candidate = current.arguments?.[1];
|
|
368
|
+
if (!candidate || diagnostic.start < candidate.getStart(sourceFile) || diagnostic.start >= candidate.getEnd()) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return isLikelyLitsxComponentReference(ts, sourceFile, candidate)
|
|
373
|
+
&& String(typeof diagnostic.messageText === "string" ? diagnostic.messageText : diagnostic.messageText?.messageText ?? "")
|
|
374
|
+
.includes("CustomElementConstructor");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getScopeCompletionPrefix(sourceText, position) {
|
|
378
|
+
const match = /[A-Za-z0-9_$]*$/.exec(sourceText.slice(0, position));
|
|
379
|
+
return match?.[0] ?? "";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function getJsxImportSourceExportEntries(service, ts, prefix, adaptKind, position) {
|
|
383
|
+
if (!prefix) {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const program = service.languageService.getProgram();
|
|
388
|
+
const checker = program?.getTypeChecker();
|
|
389
|
+
const jsxImportSource = program?.getCompilerOptions?.().jsxImportSource;
|
|
390
|
+
|
|
391
|
+
if (!program || !checker || typeof jsxImportSource !== "string" || jsxImportSource.length === 0) {
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const moduleSourceFile = program.getSourceFiles().find((sourceFile) => (
|
|
396
|
+
sourceFile.fileName.includes(`/node_modules/${jsxImportSource}/`)
|
|
397
|
+
|| sourceFile.fileName.includes(`/${jsxImportSource}/src/`)
|
|
398
|
+
));
|
|
399
|
+
|
|
400
|
+
const moduleSymbol = moduleSourceFile?.symbol ?? null;
|
|
401
|
+
if (!moduleSymbol) {
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const exportedSymbols = checker.getExportsOfModule(moduleSymbol);
|
|
406
|
+
|
|
407
|
+
return exportedSymbols
|
|
408
|
+
.filter((symbol) => {
|
|
409
|
+
const name = symbol.getName?.();
|
|
410
|
+
return typeof name === "string"
|
|
411
|
+
&& name.startsWith(prefix)
|
|
412
|
+
&& !isInternalCompletionName(name);
|
|
413
|
+
})
|
|
414
|
+
.map((symbol) => {
|
|
415
|
+
const name = symbol.getName();
|
|
416
|
+
return {
|
|
417
|
+
label: name,
|
|
418
|
+
kind: adaptKind(getSymbolCompletionKind(ts, symbol), {
|
|
419
|
+
source: jsxImportSource,
|
|
420
|
+
label: name,
|
|
421
|
+
}),
|
|
422
|
+
detail: "export",
|
|
423
|
+
documentation: `From ${jsxImportSource}`,
|
|
424
|
+
start: position - prefix.length,
|
|
425
|
+
length: prefix.length,
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getSourceParserPlugins(fileName) {
|
|
431
|
+
return /\.(litsx|tsx|ts)$/.test(fileName) ? ["typescript"] : [];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function getImportedComponentReference(ts, sourceFile, tagName) {
|
|
435
|
+
let reference = null;
|
|
436
|
+
|
|
437
|
+
sourceFile.forEachChild((node) => {
|
|
438
|
+
if (
|
|
439
|
+
reference ||
|
|
440
|
+
!ts.isImportDeclaration(node) ||
|
|
441
|
+
!ts.isStringLiteral(node.moduleSpecifier)
|
|
442
|
+
) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const bindings = node.importClause?.namedBindings;
|
|
447
|
+
if (!bindings || !ts.isNamedImports(bindings)) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
for (const element of bindings.elements) {
|
|
452
|
+
if (element.name.text !== tagName) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
reference = {
|
|
457
|
+
exportName: element.propertyName?.text ?? element.name.text,
|
|
458
|
+
moduleName: node.moduleSpecifier.text,
|
|
459
|
+
};
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
return reference;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function getComponentEventCompletionEntries(service, ts, queryFileName, sourceText, position, adaptKind) {
|
|
468
|
+
const context = inferLitsxAttributeCompletionContext(sourceText, position);
|
|
469
|
+
if (!context || context.prefix !== "@" || !/^[A-Z]/.test(context.tagName)) {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const program = service.languageService.getProgram();
|
|
474
|
+
const sourceFile = program?.getSourceFile(queryFileName);
|
|
475
|
+
if (!sourceFile) {
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const reference = getImportedComponentReference(ts, sourceFile, context.tagName);
|
|
480
|
+
if (!reference) {
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const resolvedModule = service.resolveModuleName?.(reference.moduleName, queryFileName);
|
|
485
|
+
const componentFileName = resolvedModule?.resolvedFileName;
|
|
486
|
+
if (!componentFileName) {
|
|
487
|
+
return [];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const componentSourceText = service.readSourceText?.(componentFileName);
|
|
491
|
+
if (typeof componentSourceText !== "string") {
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const eventNames = inferLitsxComponentEventNames(componentSourceText, {
|
|
496
|
+
plugins: getSourceParserPlugins(componentFileName),
|
|
497
|
+
})[reference.exportName] ?? [];
|
|
498
|
+
|
|
499
|
+
return eventNames
|
|
500
|
+
.filter((name) => typeof name === "string" && name.startsWith(context.partialName))
|
|
501
|
+
.map((name) => ({
|
|
502
|
+
...getContextualCompletionEdit(`@${name}`, context),
|
|
503
|
+
label: `@${name}`,
|
|
504
|
+
kind: adaptKind("Event", {
|
|
505
|
+
source: "litsx-component-event",
|
|
506
|
+
label: `@${name}`,
|
|
507
|
+
}),
|
|
508
|
+
detail: `Emitted by <${context.tagName}>`,
|
|
509
|
+
documentation: `LitSX custom event emitted by <${context.tagName}>.`,
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getComponentStaticPropCompletionEntries(service, ts, queryFileName, sourceText, position, adaptKind) {
|
|
514
|
+
const markupContext = inferLitsxMarkupCompletionContext(sourceText, position);
|
|
515
|
+
if (!markupContext || !/^[A-Z]/.test(markupContext.tagName)) {
|
|
516
|
+
return [];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const program = service.languageService.getProgram();
|
|
520
|
+
const sourceFile = program?.getSourceFile(queryFileName);
|
|
521
|
+
if (!sourceFile) {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const reference = getImportedComponentReference(ts, sourceFile, markupContext.tagName);
|
|
526
|
+
if (!reference) {
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const resolvedModule = service.resolveModuleName?.(reference.moduleName, queryFileName);
|
|
531
|
+
const componentFileName = resolvedModule?.resolvedFileName;
|
|
532
|
+
if (!componentFileName) {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const componentSourceText = service.readSourceText?.(componentFileName);
|
|
537
|
+
if (typeof componentSourceText !== "string") {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const propNames = inferLitsxComponentPropNames(componentSourceText, {
|
|
542
|
+
plugins: getSourceParserPlugins(componentFileName),
|
|
543
|
+
})[reference.exportName] ?? [];
|
|
544
|
+
|
|
545
|
+
return propNames
|
|
546
|
+
.filter((name) => typeof name === "string" && name.startsWith(markupContext.partialName))
|
|
547
|
+
.map((name) => ({
|
|
548
|
+
label: name,
|
|
549
|
+
kind: adaptKind("Property", {
|
|
550
|
+
source: "litsx-component-static-prop",
|
|
551
|
+
label: name,
|
|
552
|
+
}),
|
|
553
|
+
detail: `Static property of <${markupContext.tagName}>`,
|
|
554
|
+
documentation: `LitSX static property exposed by <${markupContext.tagName}>.`,
|
|
555
|
+
start: markupContext.start,
|
|
556
|
+
length: markupContext.length,
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function getCompletionEntryImportEdits(languageService, queryFileName, fileName, position, entry, virtualization) {
|
|
561
|
+
if (typeof languageService.getCompletionEntryDetails !== "function" || !entry?.hasAction) {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let details;
|
|
566
|
+
try {
|
|
567
|
+
details = languageService.getCompletionEntryDetails(
|
|
568
|
+
queryFileName,
|
|
569
|
+
position,
|
|
570
|
+
entry.name,
|
|
571
|
+
{},
|
|
572
|
+
entry.source,
|
|
573
|
+
{},
|
|
574
|
+
entry.data,
|
|
575
|
+
);
|
|
576
|
+
} catch {
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const fileChanges = details?.codeActions
|
|
581
|
+
?.flatMap((action) => action.changes ?? [])
|
|
582
|
+
?.filter((change) => change.fileName === queryFileName || change.fileName === fileName) ?? [];
|
|
583
|
+
|
|
584
|
+
return fileChanges.flatMap((change) => (
|
|
585
|
+
remapCompletionTextChanges(change.textChanges, fileName, virtualization)
|
|
586
|
+
));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function isInternalCompletionName(name) {
|
|
590
|
+
return typeof name === "string" && (
|
|
591
|
+
name.startsWith("__litsx_") ||
|
|
592
|
+
name.startsWith("_$L") ||
|
|
593
|
+
name.startsWith("_currentTarget") ||
|
|
594
|
+
name.includes("$")
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function isLikelyIntrinsicMarkupCompletionName(name) {
|
|
599
|
+
if (
|
|
600
|
+
typeof name !== "string" ||
|
|
601
|
+
name.length === 0 ||
|
|
602
|
+
isInternalCompletionName(name) ||
|
|
603
|
+
name.startsWith("_") ||
|
|
604
|
+
/^[A-Z0-9_]+$/.test(name) ||
|
|
605
|
+
/^[A-Z]/.test(name) ||
|
|
606
|
+
/^on[a-z]/.test(name) ||
|
|
607
|
+
/(Element|Elements|Node|Nodes)$/.test(name)
|
|
608
|
+
) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return /^[a-z][\w-]*$/.test(name) || /^aria[A-Z]/.test(name);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function isLikelyComponentPropCompletionName(name) {
|
|
616
|
+
return typeof name === "string"
|
|
617
|
+
&& name.length > 0
|
|
618
|
+
&& !isInternalCompletionName(name)
|
|
619
|
+
&& !name.startsWith("_")
|
|
620
|
+
&& !/^[A-Z]/.test(name)
|
|
621
|
+
&& !name.includes("$")
|
|
622
|
+
&& /^[a-z][\w-]*$/.test(name);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function rankCompletionLabel(label, prefix) {
|
|
626
|
+
const normalizedLabel = String(label ?? "").toLowerCase();
|
|
627
|
+
const normalizedPrefix = prefix.toLowerCase();
|
|
628
|
+
|
|
629
|
+
if (normalizedPrefix.length === 0) {
|
|
630
|
+
return 3;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (normalizedLabel === normalizedPrefix) {
|
|
634
|
+
return 0;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (normalizedLabel.startsWith(normalizedPrefix)) {
|
|
638
|
+
return 1;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (normalizedLabel.includes(normalizedPrefix)) {
|
|
642
|
+
return 2;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return 3;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function compareCompletionEntries(left, right, prefix) {
|
|
649
|
+
const leftLabel = String(left.label ?? "");
|
|
650
|
+
const rightLabel = String(right.label ?? "");
|
|
651
|
+
const leftRank = rankCompletionLabel(leftLabel, prefix);
|
|
652
|
+
const rightRank = rankCompletionLabel(rightLabel, prefix);
|
|
653
|
+
|
|
654
|
+
if (leftRank !== rightRank) {
|
|
655
|
+
return leftRank - rightRank;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const leftIsLitsxApi = left.documentation === "From @litsx/core";
|
|
659
|
+
const rightIsLitsxApi = right.documentation === "From @litsx/core";
|
|
660
|
+
|
|
661
|
+
if (leftIsLitsxApi !== rightIsLitsxApi) {
|
|
662
|
+
return leftIsLitsxApi ? -1 : 1;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (prefix.toLowerCase().startsWith("use")) {
|
|
666
|
+
const leftStartsWithUse = leftLabel.startsWith("use");
|
|
667
|
+
const rightStartsWithUse = rightLabel.startsWith("use");
|
|
668
|
+
if (leftStartsWithUse !== rightStartsWithUse) {
|
|
669
|
+
return leftStartsWithUse ? -1 : 1;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return leftLabel.localeCompare(rightLabel);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function getSymbolCompletionKind(ts, symbol) {
|
|
677
|
+
return SYMBOL_KIND_RULES.find(([, getMask]) => (symbol.flags & getMask(ts)) !== 0)?.[0] ?? "Variable";
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function createLitsxEditorSession(options = {}) {
|
|
681
|
+
const ts = options.typescript ?? defaultTs;
|
|
682
|
+
const bundledLibDir = options.bundledLibDir ?? null;
|
|
683
|
+
const trace = createDefaultLogger(options.logger);
|
|
684
|
+
const projectServiceCache = new Map();
|
|
685
|
+
|
|
686
|
+
function log(message) {
|
|
687
|
+
if (!options.trace) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
trace(message);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function getDefaultLibFilePath(localTs, compilerOptions) {
|
|
694
|
+
if (!bundledLibDir) {
|
|
695
|
+
return localTs.getDefaultLibFilePath(compilerOptions);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const libFileName = localTs.getDefaultLibFileName(compilerOptions);
|
|
699
|
+
const candidate = path.join(bundledLibDir, libFileName);
|
|
700
|
+
return fs.existsSync(candidate) ? candidate : localTs.getDefaultLibFilePath(compilerOptions);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function getProjectKey(fileName) {
|
|
704
|
+
const configPath =
|
|
705
|
+
ts.findConfigFile(path.dirname(fileName), ts.sys.fileExists, "tsconfig.json") ??
|
|
706
|
+
ts.findConfigFile(path.dirname(fileName), ts.sys.fileExists, "jsconfig.json");
|
|
707
|
+
return configPath ? normalizeFileName(configPath) : `<standalone>:${normalizeFileName(fileName)}`;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function getQueryFileName(fileName, languageId) {
|
|
711
|
+
return `${fileName}${QUERY_FILE_SUFFIX_BY_LANGUAGE_ID[languageId] ?? ""}`;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function getExtraFileExtensions(localTs) {
|
|
715
|
+
return [
|
|
716
|
+
{
|
|
717
|
+
extension: ".litsx",
|
|
718
|
+
isMixedContent: false,
|
|
719
|
+
scriptKind: localTs.ScriptKind.TSX,
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
extension: ".litsx.jsx",
|
|
723
|
+
isMixedContent: false,
|
|
724
|
+
scriptKind: localTs.ScriptKind.JSX,
|
|
725
|
+
},
|
|
726
|
+
];
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function getFileVersion(fileName) {
|
|
730
|
+
try {
|
|
731
|
+
const stats = fs.statSync(fileName);
|
|
732
|
+
return `${stats.mtimeMs}:${stats.size}`;
|
|
733
|
+
} catch {
|
|
734
|
+
return "0";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function readFileText(fileName, overlays) {
|
|
739
|
+
const normalizedFileName = normalizeFileName(fileName);
|
|
740
|
+
if (overlays.has(normalizedFileName)) {
|
|
741
|
+
return overlays.get(normalizedFileName).text;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
return fs.readFileSync(fileName, "utf8");
|
|
746
|
+
} catch {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function getOrCreateProjectService(fileName, sourceText, languageId) {
|
|
752
|
+
const projectKey = getProjectKey(fileName);
|
|
753
|
+
const queryFileName = getQueryFileName(fileName, languageId);
|
|
754
|
+
let service = projectServiceCache.get(projectKey);
|
|
755
|
+
|
|
756
|
+
if (!service) {
|
|
757
|
+
const configPath = projectKey.startsWith("<standalone>:") ? null : projectKey;
|
|
758
|
+
let compilerOptions;
|
|
759
|
+
let rootNames;
|
|
760
|
+
|
|
761
|
+
if (configPath) {
|
|
762
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
763
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
764
|
+
configFile.config,
|
|
765
|
+
{
|
|
766
|
+
...ts.sys,
|
|
767
|
+
onUnRecoverableConfigFileDiagnostic() {},
|
|
768
|
+
},
|
|
769
|
+
path.dirname(configPath),
|
|
770
|
+
undefined,
|
|
771
|
+
configPath,
|
|
772
|
+
undefined,
|
|
773
|
+
getExtraFileExtensions(ts),
|
|
774
|
+
);
|
|
775
|
+
|
|
776
|
+
compilerOptions = parsed.options;
|
|
777
|
+
rootNames = [...parsed.fileNames];
|
|
778
|
+
if (
|
|
779
|
+
rootNames.some((entry) => entry.endsWith(".litsx") || entry.endsWith(".litsx.jsx")) ||
|
|
780
|
+
queryFileName.endsWith(".tsx") ||
|
|
781
|
+
queryFileName.endsWith(".jsx") ||
|
|
782
|
+
fileName.endsWith(".litsx") ||
|
|
783
|
+
fileName.endsWith(".litsx.jsx")
|
|
784
|
+
) {
|
|
785
|
+
compilerOptions = {
|
|
786
|
+
...compilerOptions,
|
|
787
|
+
allowNonTsExtensions: true,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
compilerOptions = {
|
|
792
|
+
target: ts.ScriptTarget.ESNext,
|
|
793
|
+
module: ts.ModuleKind.ESNext,
|
|
794
|
+
jsx: ts.JsxEmit.Preserve,
|
|
795
|
+
allowJs: true,
|
|
796
|
+
checkJs: true,
|
|
797
|
+
allowNonTsExtensions: true,
|
|
798
|
+
types: [],
|
|
799
|
+
};
|
|
800
|
+
rootNames = [queryFileName];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!rootNames.includes(queryFileName)) {
|
|
804
|
+
rootNames.push(queryFileName);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const overlays = new Map();
|
|
808
|
+
const virtualizationCache = new Map();
|
|
809
|
+
|
|
810
|
+
function fileExistsForResolution(nextFileName) {
|
|
811
|
+
return overlays.has(normalizeFileName(nextFileName)) || ts.sys.fileExists(nextFileName);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function getScriptKind(nextFileName) {
|
|
815
|
+
const extension = /\.((?:litsx\.jsx)|litsx|tsx|jsx|ts|js)$/.exec(nextFileName)?.[1];
|
|
816
|
+
const scriptKindName = extension ? SCRIPT_KIND_BY_EXTENSION[extension] : undefined;
|
|
817
|
+
return scriptKindName ? ts.ScriptKind[scriptKindName] : undefined;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function getSnapshotRecord(nextFileName) {
|
|
821
|
+
const normalizedFileName = normalizeFileName(nextFileName);
|
|
822
|
+
const overlay = overlays.get(normalizedFileName);
|
|
823
|
+
const version = overlay?.version ?? getFileVersion(nextFileName);
|
|
824
|
+
const cached = virtualizationCache.get(normalizedFileName);
|
|
825
|
+
|
|
826
|
+
if (cached?.version === version) {
|
|
827
|
+
return cached;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const nextSourceText = readFileText(nextFileName, overlays);
|
|
831
|
+
if (typeof nextSourceText !== "string") {
|
|
832
|
+
virtualizationCache.delete(normalizedFileName);
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
let virtualization = null;
|
|
837
|
+
let toolingText = nextSourceText;
|
|
838
|
+
|
|
839
|
+
if (isRelevantFile(nextFileName) && looksLikeLitsxJsx(nextSourceText)) {
|
|
840
|
+
virtualization = createToolingVirtualLitsxSource(nextSourceText, {
|
|
841
|
+
plugins: getPluginsForFile(nextFileName, languageId),
|
|
842
|
+
});
|
|
843
|
+
toolingText = virtualization.code;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const record = {
|
|
847
|
+
version,
|
|
848
|
+
sourceText: nextSourceText,
|
|
849
|
+
toolingText,
|
|
850
|
+
virtualization,
|
|
851
|
+
snapshot: ts.ScriptSnapshot.fromString(toolingText),
|
|
852
|
+
};
|
|
853
|
+
virtualizationCache.set(normalizedFileName, record);
|
|
854
|
+
return record;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function resolveTransparentModuleName(moduleName, containingFile) {
|
|
858
|
+
if (!isPathLikeModuleName(moduleName)) {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const candidateBase = path.resolve(path.dirname(containingFile), moduleName);
|
|
863
|
+
for (const candidate of getTransparentResolutionCandidates(candidateBase)) {
|
|
864
|
+
if (fileExistsForResolution(candidate)) {
|
|
865
|
+
return createResolvedModule(ts, candidate);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function resolveModule(moduleName, containingFile) {
|
|
873
|
+
const resolved = ts.resolveModuleName(
|
|
874
|
+
moduleName,
|
|
875
|
+
containingFile,
|
|
876
|
+
compilerOptions,
|
|
877
|
+
{
|
|
878
|
+
fileExists: fileExistsForResolution,
|
|
879
|
+
readFile(nextFileName) {
|
|
880
|
+
return readFileText(nextFileName, overlays);
|
|
881
|
+
},
|
|
882
|
+
directoryExists(nextDirName) {
|
|
883
|
+
return ts.sys.directoryExists?.(nextDirName) ?? true;
|
|
884
|
+
},
|
|
885
|
+
getDirectories(nextDirName) {
|
|
886
|
+
return ts.sys.getDirectories?.(nextDirName) ?? [];
|
|
887
|
+
},
|
|
888
|
+
realpath(nextFileName) {
|
|
889
|
+
return ts.sys.realpath?.(nextFileName) ?? nextFileName;
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
).resolvedModule;
|
|
893
|
+
|
|
894
|
+
return resolved ?? resolveTransparentModuleName(moduleName, containingFile);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const host = {
|
|
898
|
+
extraFileExtensions: getExtraFileExtensions(ts),
|
|
899
|
+
getCompilationSettings() {
|
|
900
|
+
return compilerOptions;
|
|
901
|
+
},
|
|
902
|
+
getCurrentDirectory() {
|
|
903
|
+
return configPath ? path.dirname(configPath) : path.dirname(fileName);
|
|
904
|
+
},
|
|
905
|
+
getDefaultLibFileName(optionsForLib) {
|
|
906
|
+
return getDefaultLibFilePath(ts, optionsForLib);
|
|
907
|
+
},
|
|
908
|
+
getScriptFileNames() {
|
|
909
|
+
return rootNames;
|
|
910
|
+
},
|
|
911
|
+
getScriptVersion(nextFileName) {
|
|
912
|
+
const normalizedFileName = normalizeFileName(nextFileName);
|
|
913
|
+
const overlay = overlays.get(normalizedFileName);
|
|
914
|
+
return String(overlay?.version ?? getFileVersion(nextFileName));
|
|
915
|
+
},
|
|
916
|
+
getScriptKind,
|
|
917
|
+
getScriptSnapshot(nextFileName) {
|
|
918
|
+
return getSnapshotRecord(nextFileName)?.snapshot;
|
|
919
|
+
},
|
|
920
|
+
fileExists(nextFileName) {
|
|
921
|
+
return fileExistsForResolution(nextFileName);
|
|
922
|
+
},
|
|
923
|
+
readFile(nextFileName) {
|
|
924
|
+
return readFileText(nextFileName, overlays);
|
|
925
|
+
},
|
|
926
|
+
resolveModuleNames(moduleNames, containingFile) {
|
|
927
|
+
return moduleNames.map((moduleName) => resolveModule(moduleName, containingFile));
|
|
928
|
+
},
|
|
929
|
+
resolveModuleNameLiterals(moduleLiterals, containingFile) {
|
|
930
|
+
return moduleLiterals.map(({ text }) => ({ resolvedModule: resolveModule(text, containingFile) }));
|
|
931
|
+
},
|
|
932
|
+
readDirectory(...args) {
|
|
933
|
+
return ts.sys.readDirectory(...args);
|
|
934
|
+
},
|
|
935
|
+
directoryExists(nextDirName) {
|
|
936
|
+
return ts.sys.directoryExists?.(nextDirName) ?? true;
|
|
937
|
+
},
|
|
938
|
+
getDirectories(nextDirName) {
|
|
939
|
+
return ts.sys.getDirectories?.(nextDirName) ?? [];
|
|
940
|
+
},
|
|
941
|
+
getNewLine() {
|
|
942
|
+
return ts.sys.newLine;
|
|
943
|
+
},
|
|
944
|
+
useCaseSensitiveFileNames() {
|
|
945
|
+
return ts.sys.useCaseSensitiveFileNames;
|
|
946
|
+
},
|
|
947
|
+
getCanonicalFileName(nextFileName) {
|
|
948
|
+
return ts.sys.useCaseSensitiveFileNames
|
|
949
|
+
? nextFileName
|
|
950
|
+
: nextFileName.toLowerCase();
|
|
951
|
+
},
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
service = {
|
|
955
|
+
rootNames,
|
|
956
|
+
overlays,
|
|
957
|
+
languageService: ts.createLanguageService(host, ts.createDocumentRegistry()),
|
|
958
|
+
readSourceText(nextFileName) {
|
|
959
|
+
return readFileText(nextFileName, overlays);
|
|
960
|
+
},
|
|
961
|
+
resolveModuleName(moduleName, containingFile) {
|
|
962
|
+
return resolveModule(moduleName, containingFile);
|
|
963
|
+
},
|
|
964
|
+
getVirtualization(nextFileName) {
|
|
965
|
+
return getSnapshotRecord(nextFileName)?.virtualization ?? null;
|
|
966
|
+
},
|
|
967
|
+
setOverlay(nextFileName, nextSourceText) {
|
|
968
|
+
const normalizedFileName = normalizeFileName(nextFileName);
|
|
969
|
+
const previousVersion = overlays.get(normalizedFileName)?.version ?? 0;
|
|
970
|
+
overlays.set(normalizedFileName, {
|
|
971
|
+
text: nextSourceText,
|
|
972
|
+
version: previousVersion + 1,
|
|
973
|
+
});
|
|
974
|
+
if (!rootNames.includes(nextFileName)) {
|
|
975
|
+
rootNames.push(nextFileName);
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
projectServiceCache.set(projectKey, service);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
service.setOverlay(queryFileName, sourceText);
|
|
984
|
+
service.queryFileName = queryFileName;
|
|
985
|
+
return service;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function getDiagnostics(fileName, sourceText, languageId) {
|
|
989
|
+
const service = getOrCreateProjectService(fileName, sourceText, languageId);
|
|
990
|
+
const queryFileName = service.queryFileName ?? fileName;
|
|
991
|
+
const virtualization = service.getVirtualization(queryFileName);
|
|
992
|
+
const authoredSourceFile = ts.createSourceFile(
|
|
993
|
+
fileName,
|
|
994
|
+
sourceText,
|
|
995
|
+
ts.ScriptTarget.Latest,
|
|
996
|
+
true,
|
|
997
|
+
getSourceFileScriptKind(ts, fileName, languageId),
|
|
998
|
+
);
|
|
999
|
+
const diagnostics = [
|
|
1000
|
+
...service.languageService.getSyntacticDiagnostics(queryFileName),
|
|
1001
|
+
...service.languageService.getSemanticDiagnostics(queryFileName),
|
|
1002
|
+
];
|
|
1003
|
+
const remappedDiagnostics = diagnostics
|
|
1004
|
+
.map((diagnostic) => {
|
|
1005
|
+
if (!virtualization || typeof diagnostic.start !== "number") {
|
|
1006
|
+
return {
|
|
1007
|
+
...diagnostic,
|
|
1008
|
+
messageText: remapMessageText(diagnostic.messageText),
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const remappedSpan = remapToolingTextSpanToOriginal(
|
|
1013
|
+
{
|
|
1014
|
+
start: diagnostic.start,
|
|
1015
|
+
length: diagnostic.length ?? 0,
|
|
1016
|
+
},
|
|
1017
|
+
virtualization,
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
return {
|
|
1021
|
+
...diagnostic,
|
|
1022
|
+
start: remappedSpan.start,
|
|
1023
|
+
length: remappedSpan.length,
|
|
1024
|
+
messageText: remapMessageText(diagnostic.messageText),
|
|
1025
|
+
};
|
|
1026
|
+
})
|
|
1027
|
+
.filter((diagnostic) => (
|
|
1028
|
+
!String(diagnostic.messageText ?? "").includes("__litsx_") &&
|
|
1029
|
+
!startsAtAuthoredSyntax(sourceText, diagnostic.start) &&
|
|
1030
|
+
!shouldSuppressCustomElementConstructorDiagnostic(ts, authoredSourceFile, diagnostic)
|
|
1031
|
+
));
|
|
1032
|
+
|
|
1033
|
+
const authoredDiagnostics = collectLitsxAuthoredDiagnostics(sourceText, ts, {
|
|
1034
|
+
plugins: getParserPlugins(languageId),
|
|
1035
|
+
});
|
|
1036
|
+
const seen = new Set(
|
|
1037
|
+
remappedDiagnostics.map((diagnostic) => `${diagnostic.code}:${diagnostic.start}:${diagnostic.length}`),
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
return [
|
|
1041
|
+
...remappedDiagnostics,
|
|
1042
|
+
...authoredDiagnostics.filter((diagnostic) => {
|
|
1043
|
+
const key = `${diagnostic.code}:${diagnostic.start}:${diagnostic.length}`;
|
|
1044
|
+
if (seen.has(key)) {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
seen.add(key);
|
|
1048
|
+
return true;
|
|
1049
|
+
}),
|
|
1050
|
+
];
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function getHover(fileName, sourceText, languageId, position) {
|
|
1054
|
+
const service = getOrCreateProjectService(fileName, sourceText, languageId);
|
|
1055
|
+
const queryFileName = service.queryFileName ?? fileName;
|
|
1056
|
+
const virtualization = service.getVirtualization(queryFileName);
|
|
1057
|
+
const mappedPosition = virtualization
|
|
1058
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
1059
|
+
: position;
|
|
1060
|
+
const info = service.languageService.getQuickInfoAtPosition(queryFileName, mappedPosition);
|
|
1061
|
+
|
|
1062
|
+
const hoistInfo = inferLitsxStaticHoistInfoAtPosition(sourceText, position);
|
|
1063
|
+
if (hoistInfo) {
|
|
1064
|
+
return {
|
|
1065
|
+
start: hoistInfo.start,
|
|
1066
|
+
length: hoistInfo.length,
|
|
1067
|
+
code: `${hoistInfo.name}(...): static hoist`,
|
|
1068
|
+
documentation: hoistInfo.documentation,
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (!info) {
|
|
1073
|
+
const program = service.languageService.getProgram();
|
|
1074
|
+
const sourceFile = program?.getSourceFile(queryFileName);
|
|
1075
|
+
const checker = program?.getTypeChecker();
|
|
1076
|
+
if (!program || !sourceFile || !checker) {
|
|
1077
|
+
const bindingHoverInfo = getBindingHoverInfo(
|
|
1078
|
+
inferLitsxAttributeInfoAtPosition(sourceText, position),
|
|
1079
|
+
);
|
|
1080
|
+
return bindingHoverInfo
|
|
1081
|
+
? {
|
|
1082
|
+
start: bindingHoverInfo.start,
|
|
1083
|
+
length: bindingHoverInfo.length,
|
|
1084
|
+
code: `${bindingHoverInfo.name}: ${bindingHoverInfo.kindLabel}`,
|
|
1085
|
+
documentation: bindingHoverInfo.detail,
|
|
1086
|
+
}
|
|
1087
|
+
: null;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
let node = findDeepestNodeAtPosition(sourceFile, mappedPosition);
|
|
1091
|
+
while (node) {
|
|
1092
|
+
const symbol = checker.getSymbolAtLocation(node) ?? null;
|
|
1093
|
+
const type = checker.getTypeAtLocation(node) ?? null;
|
|
1094
|
+
if (!type) {
|
|
1095
|
+
node = node.parent ?? null;
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
const remappedNodeSpan = virtualization
|
|
1100
|
+
? remapToolingTextSpanToOriginal(
|
|
1101
|
+
{
|
|
1102
|
+
start: node.getStart(sourceFile),
|
|
1103
|
+
length: node.getWidth(sourceFile),
|
|
1104
|
+
},
|
|
1105
|
+
virtualization,
|
|
1106
|
+
)
|
|
1107
|
+
: {
|
|
1108
|
+
start: position,
|
|
1109
|
+
length: 0,
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
return {
|
|
1113
|
+
start: remappedNodeSpan.start,
|
|
1114
|
+
length: remappedNodeSpan.length,
|
|
1115
|
+
code: `${symbol?.getName?.() ?? node.getText(sourceFile)}: ${checker.typeToString(type)}`,
|
|
1116
|
+
documentation: symbol
|
|
1117
|
+
? ts.displayPartsToString(symbol.getDocumentationComment(checker))
|
|
1118
|
+
: "",
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const bindingHoverInfo = getBindingHoverInfo(
|
|
1123
|
+
inferLitsxAttributeInfoAtPosition(sourceText, position),
|
|
1124
|
+
);
|
|
1125
|
+
return bindingHoverInfo
|
|
1126
|
+
? {
|
|
1127
|
+
start: bindingHoverInfo.start,
|
|
1128
|
+
length: bindingHoverInfo.length,
|
|
1129
|
+
code: `${bindingHoverInfo.name}: ${bindingHoverInfo.kindLabel}`,
|
|
1130
|
+
documentation: bindingHoverInfo.detail,
|
|
1131
|
+
}
|
|
1132
|
+
: null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const remappedSpan = virtualization
|
|
1136
|
+
? remapToolingTextSpanToOriginal(info.textSpan, virtualization)
|
|
1137
|
+
: info.textSpan;
|
|
1138
|
+
const displayText = (remapDisplayParts(info.displayParts) ?? [])
|
|
1139
|
+
.map((entry) => entry.text)
|
|
1140
|
+
.join("");
|
|
1141
|
+
const documentation = (remapDisplayParts(info.documentation) ?? [])
|
|
1142
|
+
.map((entry) => entry.text)
|
|
1143
|
+
.join("");
|
|
1144
|
+
|
|
1145
|
+
return {
|
|
1146
|
+
start: remappedSpan.start,
|
|
1147
|
+
length: remappedSpan.length,
|
|
1148
|
+
code: displayText || "symbol",
|
|
1149
|
+
documentation,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function getCompletions(fileName, sourceText, languageId, position, completionKindAdapter) {
|
|
1154
|
+
const adaptKind = createCompletionKindAdapter(completionKindAdapter);
|
|
1155
|
+
const service = getOrCreateProjectService(fileName, sourceText, languageId);
|
|
1156
|
+
const queryFileName = service.queryFileName ?? fileName;
|
|
1157
|
+
const virtualization = service.getVirtualization(queryFileName);
|
|
1158
|
+
const mappedPosition = virtualization
|
|
1159
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
1160
|
+
: position;
|
|
1161
|
+
const completions = service.languageService.getCompletionsAtPosition(queryFileName, mappedPosition, {
|
|
1162
|
+
includeCompletionsForModuleExports: true,
|
|
1163
|
+
includeCompletionsWithInsertText: true,
|
|
1164
|
+
});
|
|
1165
|
+
const rawCompletionEntriesByName = new Map(
|
|
1166
|
+
(completions?.entries ?? []).map((entry) => [entry.name, entry]),
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
const context = inferLitsxAttributeCompletionContext(sourceText, position);
|
|
1170
|
+
const markupContext = inferLitsxMarkupCompletionContext(sourceText, position);
|
|
1171
|
+
const isComponentTag = /^[A-Z]/.test(markupContext?.tagName ?? "");
|
|
1172
|
+
const contextualEntries = context
|
|
1173
|
+
? getLitsxAttributeCompletionNames(context).map((name) => ({
|
|
1174
|
+
...getContextualCompletionEdit(name, context),
|
|
1175
|
+
label: name,
|
|
1176
|
+
kind: adaptKind(getCompletionKindToken(name), {
|
|
1177
|
+
source: "litsx",
|
|
1178
|
+
label: name,
|
|
1179
|
+
}),
|
|
1180
|
+
detail: "LitSX binding",
|
|
1181
|
+
documentation: `LitSX binding for <${context.tagName}>.`,
|
|
1182
|
+
}))
|
|
1183
|
+
: [];
|
|
1184
|
+
const componentEventEntries = getComponentEventCompletionEntries(
|
|
1185
|
+
service,
|
|
1186
|
+
ts,
|
|
1187
|
+
queryFileName,
|
|
1188
|
+
sourceText,
|
|
1189
|
+
position,
|
|
1190
|
+
adaptKind,
|
|
1191
|
+
).filter((entry) => !contextualEntries.some((contextualEntry) => contextualEntry.label === entry.label));
|
|
1192
|
+
const componentStaticPropEntries = getComponentStaticPropCompletionEntries(
|
|
1193
|
+
service,
|
|
1194
|
+
ts,
|
|
1195
|
+
queryFileName,
|
|
1196
|
+
sourceText,
|
|
1197
|
+
position,
|
|
1198
|
+
adaptKind,
|
|
1199
|
+
);
|
|
1200
|
+
const markupEntries = markupContext && !isComponentTag
|
|
1201
|
+
? getLitsxMarkupCompletionNames(markupContext).map((name) => ({
|
|
1202
|
+
label: name,
|
|
1203
|
+
kind: adaptKind(
|
|
1204
|
+
name.startsWith("@")
|
|
1205
|
+
? "Event"
|
|
1206
|
+
: name.startsWith(".") || name.startsWith("?")
|
|
1207
|
+
? "Property"
|
|
1208
|
+
: "Property",
|
|
1209
|
+
{
|
|
1210
|
+
source: "litsx-markup",
|
|
1211
|
+
label: name,
|
|
1212
|
+
},
|
|
1213
|
+
),
|
|
1214
|
+
detail: name.startsWith("@")
|
|
1215
|
+
? "LitSX event binding"
|
|
1216
|
+
: name.startsWith(".")
|
|
1217
|
+
? "LitSX property binding"
|
|
1218
|
+
: name.startsWith("?")
|
|
1219
|
+
? "LitSX boolean attribute binding"
|
|
1220
|
+
: "LitSX markup attribute",
|
|
1221
|
+
documentation: name.startsWith("@") || name.startsWith(".") || name.startsWith("?")
|
|
1222
|
+
? `LitSX binding for <${markupContext.tagName}>.`
|
|
1223
|
+
: `LitSX-authored attribute for <${markupContext.tagName}>.`,
|
|
1224
|
+
start: markupContext.start,
|
|
1225
|
+
length: markupContext.length,
|
|
1226
|
+
}))
|
|
1227
|
+
: [];
|
|
1228
|
+
const scopePrefix = getScopeCompletionPrefix(sourceText, position);
|
|
1229
|
+
const jsxImportSourceEntries = markupContext
|
|
1230
|
+
? []
|
|
1231
|
+
: getJsxImportSourceExportEntries(service, ts, scopePrefix, adaptKind, position).map((entry) => ({
|
|
1232
|
+
...entry,
|
|
1233
|
+
additionalTextEdits: getCompletionEntryImportEdits(
|
|
1234
|
+
service.languageService,
|
|
1235
|
+
queryFileName,
|
|
1236
|
+
fileName,
|
|
1237
|
+
mappedPosition,
|
|
1238
|
+
rawCompletionEntriesByName.get(entry.label),
|
|
1239
|
+
virtualization,
|
|
1240
|
+
),
|
|
1241
|
+
}));
|
|
1242
|
+
|
|
1243
|
+
const mergedEntries = [
|
|
1244
|
+
...contextualEntries,
|
|
1245
|
+
...componentEventEntries,
|
|
1246
|
+
...componentStaticPropEntries,
|
|
1247
|
+
...markupEntries,
|
|
1248
|
+
...jsxImportSourceEntries,
|
|
1249
|
+
];
|
|
1250
|
+
const seen = new Set(mergedEntries.map((entry) => entry.label));
|
|
1251
|
+
|
|
1252
|
+
for (const entry of completions?.entries ?? []) {
|
|
1253
|
+
if (
|
|
1254
|
+
decodeVirtualAttributeName(entry.name) ||
|
|
1255
|
+
seen.has(entry.name) ||
|
|
1256
|
+
isInternalCompletionName(entry.name)
|
|
1257
|
+
) {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (markupContext && !isComponentTag && !isLikelyIntrinsicMarkupCompletionName(entry.name)) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (markupContext && isComponentTag && !isLikelyComponentPropCompletionName(entry.name)) {
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
seen.add(entry.name);
|
|
1270
|
+
|
|
1271
|
+
const remappedSpan = virtualization && entry.replacementSpan
|
|
1272
|
+
? remapToolingTextSpanToOriginal(entry.replacementSpan, virtualization)
|
|
1273
|
+
: entry.replacementSpan ?? { start: position, length: 0 };
|
|
1274
|
+
|
|
1275
|
+
const kindToken = COMPLETION_KIND_BY_TS_KIND[entry.kind] ?? "Text";
|
|
1276
|
+
mergedEntries.push({
|
|
1277
|
+
label: entry.name,
|
|
1278
|
+
kind: adaptKind(kindToken, {
|
|
1279
|
+
source: "typescript",
|
|
1280
|
+
label: entry.name,
|
|
1281
|
+
tsKind: entry.kind,
|
|
1282
|
+
kindModifiers: entry.kindModifiers,
|
|
1283
|
+
}),
|
|
1284
|
+
detail: entry.kindModifiers || entry.kind,
|
|
1285
|
+
documentation: entry.source ? `From ${entry.source}` : "TypeScript completion",
|
|
1286
|
+
start: remappedSpan.start,
|
|
1287
|
+
length: remappedSpan.length,
|
|
1288
|
+
additionalTextEdits: getCompletionEntryImportEdits(
|
|
1289
|
+
service.languageService,
|
|
1290
|
+
queryFileName,
|
|
1291
|
+
fileName,
|
|
1292
|
+
mappedPosition,
|
|
1293
|
+
entry,
|
|
1294
|
+
virtualization,
|
|
1295
|
+
),
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
if (mergedEntries.length === (contextualEntries.length + componentEventEntries.length + componentStaticPropEntries.length)) {
|
|
1300
|
+
const program = service.languageService.getProgram();
|
|
1301
|
+
const sourceFile = program?.getSourceFile(queryFileName);
|
|
1302
|
+
const checker = program?.getTypeChecker();
|
|
1303
|
+
const node = sourceFile ? findDeepestNodeAtPosition(sourceFile, mappedPosition) : null;
|
|
1304
|
+
const prefix = scopePrefix;
|
|
1305
|
+
|
|
1306
|
+
if (node && checker && !markupContext) {
|
|
1307
|
+
const scopeSymbols = checker.getSymbolsInScope(
|
|
1308
|
+
node,
|
|
1309
|
+
ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace | ts.SymbolFlags.Alias,
|
|
1310
|
+
);
|
|
1311
|
+
|
|
1312
|
+
for (const symbol of scopeSymbols) {
|
|
1313
|
+
const name = symbol.getName?.();
|
|
1314
|
+
if (!name || isInternalCompletionName(name) || seen.has(name) || !name.startsWith(prefix)) {
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
seen.add(name);
|
|
1319
|
+
const kindToken = getSymbolCompletionKind(ts, symbol);
|
|
1320
|
+
mergedEntries.push({
|
|
1321
|
+
label: name,
|
|
1322
|
+
kind: adaptKind(kindToken, {
|
|
1323
|
+
source: "typescript-scope",
|
|
1324
|
+
label: name,
|
|
1325
|
+
}),
|
|
1326
|
+
detail: "TypeScript symbol",
|
|
1327
|
+
documentation: "Project-backed TypeScript completion",
|
|
1328
|
+
start: position - prefix.length,
|
|
1329
|
+
length: prefix.length,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if (markupContext) {
|
|
1336
|
+
return mergedEntries;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return mergedEntries.sort((left, right) => (
|
|
1340
|
+
compareCompletionEntries(left, right, scopePrefix)
|
|
1341
|
+
));
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
log("LitSX editor session initialized");
|
|
1345
|
+
|
|
1346
|
+
return {
|
|
1347
|
+
typescript: ts,
|
|
1348
|
+
bundledLibDir,
|
|
1349
|
+
getDiagnostics,
|
|
1350
|
+
getHover,
|
|
1351
|
+
getCompletions,
|
|
1352
|
+
clear() {
|
|
1353
|
+
projectServiceCache.clear();
|
|
1354
|
+
},
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
export {
|
|
1359
|
+
createLitsxEditorSession,
|
|
1360
|
+
};
|