@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
package/src/index.js
ADDED
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
collectLitsxAuthoredDiagnostics,
|
|
5
|
+
createToolingVirtualLitsxSource,
|
|
6
|
+
decodeVirtualAttributeName,
|
|
7
|
+
getLitsxAttributeCompletionNames,
|
|
8
|
+
inferLitsxAttributeInfoAtPosition,
|
|
9
|
+
inferLitsxStaticHoistInfoAtPosition,
|
|
10
|
+
inferLitsxAttributeCompletionContext,
|
|
11
|
+
inferLitsxMarkupCompletionContext,
|
|
12
|
+
looksLikeLitsxJsx,
|
|
13
|
+
mapOriginalPositionToToolingVirtual,
|
|
14
|
+
remapVirtualText,
|
|
15
|
+
remapToolingTextSpanToOriginal,
|
|
16
|
+
} from "./virtualization.js";
|
|
17
|
+
export { createLitsxTypecheckSession, runLitsxTypecheck } from "./typecheck.js";
|
|
18
|
+
|
|
19
|
+
const PROFILE_ENABLED = process.env.LITSX_PROFILE === "1";
|
|
20
|
+
const EXTERNAL_FILES_CACHE = new WeakMap();
|
|
21
|
+
|
|
22
|
+
function profilePhase(namespace, name, callback) {
|
|
23
|
+
if (!PROFILE_ENABLED) {
|
|
24
|
+
return callback();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const start = performance.now();
|
|
28
|
+
try {
|
|
29
|
+
return callback();
|
|
30
|
+
} finally {
|
|
31
|
+
globalThis.__litsxProfileEvents ??= [];
|
|
32
|
+
globalThis.__litsxProfileEvents.push({
|
|
33
|
+
namespace,
|
|
34
|
+
name,
|
|
35
|
+
durationMs: performance.now() - start,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isRelevantFile(fileName) {
|
|
41
|
+
return /\.(jsx|tsx|litsx)$/.test(fileName) || fileName.endsWith(".litsx.jsx");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readSnapshotText(snapshot) {
|
|
45
|
+
return snapshot.getText(0, snapshot.getLength());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createSnapshot(ts, sourceText) {
|
|
49
|
+
return ts.ScriptSnapshot.fromString(sourceText);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function remapDisplayParts(parts) {
|
|
53
|
+
return parts?.map((part) => ({
|
|
54
|
+
...part,
|
|
55
|
+
text: remapVirtualText(part.text),
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function remapMessageText(messageText) {
|
|
60
|
+
if (typeof messageText === "string") {
|
|
61
|
+
return remapVirtualText(messageText);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!messageText || typeof messageText !== "object") {
|
|
65
|
+
return messageText;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...messageText,
|
|
70
|
+
messageText: remapMessageText(messageText.messageText),
|
|
71
|
+
next: messageText.next?.map(remapMessageText),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function remapNumericTextSpan(start, length, virtualization) {
|
|
76
|
+
if (typeof start !== "number") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return remapToolingTextSpanToOriginal(
|
|
81
|
+
{ start, length: length ?? 0 },
|
|
82
|
+
virtualization,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function remapTextSpanField(record, fieldName, virtualization) {
|
|
87
|
+
const span = record?.[fieldName];
|
|
88
|
+
if (!span) {
|
|
89
|
+
return record;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...record,
|
|
94
|
+
[fieldName]: remapToolingTextSpanToOriginal(span, virtualization),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function remapFileSpanRecord(record, getVirtualization) {
|
|
99
|
+
const virtualization = record?.fileName
|
|
100
|
+
? getVirtualization(record.fileName)
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
if (!virtualization) {
|
|
104
|
+
return record;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let remapped = {
|
|
108
|
+
...record,
|
|
109
|
+
textSpan: record.textSpan
|
|
110
|
+
? remapToolingTextSpanToOriginal(record.textSpan, virtualization)
|
|
111
|
+
: record.textSpan,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
remapped = remapTextSpanField(remapped, "contextSpan", virtualization);
|
|
115
|
+
remapped = remapTextSpanField(remapped, "originalTextSpan", virtualization);
|
|
116
|
+
|
|
117
|
+
return remapped;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function remapRelatedInformation(info, getVirtualization, fallbackVirtualization) {
|
|
121
|
+
const virtualization = info.file?.fileName
|
|
122
|
+
? getVirtualization(info.file.fileName) ?? fallbackVirtualization
|
|
123
|
+
: fallbackVirtualization;
|
|
124
|
+
|
|
125
|
+
if (!virtualization) {
|
|
126
|
+
return info;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const remappedStartLength = remapNumericTextSpan(info.start, info.length, virtualization);
|
|
130
|
+
const remappedSpan = info.span
|
|
131
|
+
? remapToolingTextSpanToOriginal(info.span, virtualization)
|
|
132
|
+
: info.span;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
...info,
|
|
136
|
+
...(remappedStartLength
|
|
137
|
+
? {
|
|
138
|
+
start: remappedStartLength.start,
|
|
139
|
+
length: remappedStartLength.length,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
span: remappedSpan,
|
|
143
|
+
messageText: remapMessageText(info.messageText),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function wrapDiagnostics(method, getVirtualization) {
|
|
148
|
+
return (fileName) => {
|
|
149
|
+
const diagnostics = method(fileName) ?? [];
|
|
150
|
+
const virtualization = getVirtualization(fileName);
|
|
151
|
+
|
|
152
|
+
if (!virtualization) {
|
|
153
|
+
return diagnostics;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return diagnostics.map((diagnostic) => {
|
|
157
|
+
const remappedSpan = remapNumericTextSpan(
|
|
158
|
+
diagnostic.start,
|
|
159
|
+
diagnostic.length,
|
|
160
|
+
virtualization,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...diagnostic,
|
|
165
|
+
...(remappedSpan
|
|
166
|
+
? remappedSpan
|
|
167
|
+
: {
|
|
168
|
+
start: diagnostic.start,
|
|
169
|
+
length: diagnostic.length,
|
|
170
|
+
}),
|
|
171
|
+
messageText: remapMessageText(diagnostic.messageText),
|
|
172
|
+
relatedInformation: diagnostic.relatedInformation?.map((info) => (
|
|
173
|
+
remapRelatedInformation(info, getVirtualization, virtualization)
|
|
174
|
+
)),
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function wrapSemanticDiagnostics(method, getVirtualization, getAuthoredDiagnostics) {
|
|
181
|
+
return (fileName) => {
|
|
182
|
+
const diagnostics = wrapDiagnostics(method, getVirtualization)(fileName);
|
|
183
|
+
const authoredDiagnostics = getAuthoredDiagnostics(fileName);
|
|
184
|
+
|
|
185
|
+
if (!authoredDiagnostics?.length) {
|
|
186
|
+
return diagnostics;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const warningCategory = 0;
|
|
190
|
+
const authoredErrors = authoredDiagnostics.filter(
|
|
191
|
+
(diagnostic) => diagnostic.category !== warningCategory,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (authoredErrors.length === 0) {
|
|
195
|
+
return diagnostics;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [...diagnostics, ...authoredErrors];
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function wrapSyntacticDiagnostics(method, getVirtualization, getAuthoredDiagnostics, ts) {
|
|
203
|
+
return (fileName) => {
|
|
204
|
+
const diagnostics = wrapDiagnostics(method, getVirtualization)(fileName);
|
|
205
|
+
const virtualization = getVirtualization(fileName);
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
!(/\.[cm]?[jt]sx$/.test(fileName) || fileName.endsWith(".litsx") || fileName.endsWith(".litsx.jsx")) ||
|
|
209
|
+
!virtualization
|
|
210
|
+
) {
|
|
211
|
+
return diagnostics;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const authoredDiagnostics = getAuthoredDiagnostics(fileName);
|
|
215
|
+
if (!authoredDiagnostics?.length) {
|
|
216
|
+
return fileName.endsWith(".jsx") ? [] : diagnostics;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// TypeScript's JSX parser does not understand LitSX-authored syntax in
|
|
220
|
+
// plain .jsx files, so its raw syntactic diagnostics are mostly parser
|
|
221
|
+
// cascades. Replace them wholesale with authored diagnostics instead.
|
|
222
|
+
if (fileName.endsWith(".jsx")) {
|
|
223
|
+
const seen = new Set();
|
|
224
|
+
return authoredDiagnostics.filter((diagnostic) => {
|
|
225
|
+
const key = `${diagnostic.code}:${diagnostic.start}:${diagnostic.length}`;
|
|
226
|
+
if (seen.has(key)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
seen.add(key);
|
|
230
|
+
return true;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const warningCategory = ts?.DiagnosticCategory?.Warning ?? 0;
|
|
235
|
+
const authoredWarnings = authoredDiagnostics.filter(
|
|
236
|
+
(diagnostic) => diagnostic.category === warningCategory,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (authoredWarnings.length === 0) {
|
|
240
|
+
return diagnostics;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const seen = new Set(
|
|
244
|
+
diagnostics.map(
|
|
245
|
+
(diagnostic) => `${diagnostic.code}:${diagnostic.start}:${diagnostic.length}`,
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
return [
|
|
250
|
+
...diagnostics,
|
|
251
|
+
...authoredWarnings.filter((diagnostic) => {
|
|
252
|
+
const key = `${diagnostic.code}:${diagnostic.start}:${diagnostic.length}`;
|
|
253
|
+
if (seen.has(key)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
seen.add(key);
|
|
257
|
+
return true;
|
|
258
|
+
}),
|
|
259
|
+
];
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function wrapDefinitionAtPosition(method, getVirtualization) {
|
|
264
|
+
return (fileName, position) => {
|
|
265
|
+
const virtualization = getVirtualization(fileName);
|
|
266
|
+
const mappedPosition = virtualization
|
|
267
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
268
|
+
: position;
|
|
269
|
+
const definitions = method?.(fileName, mappedPosition);
|
|
270
|
+
|
|
271
|
+
if (!definitions?.length) {
|
|
272
|
+
return definitions;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return definitions.map((definition) => remapFileSpanRecord(definition, getVirtualization));
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function wrapDefinitionAndBoundSpan(method, getVirtualization) {
|
|
280
|
+
return (fileName, position) => {
|
|
281
|
+
const virtualization = getVirtualization(fileName);
|
|
282
|
+
const mappedPosition = virtualization
|
|
283
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
284
|
+
: position;
|
|
285
|
+
const result = method?.(fileName, mappedPosition);
|
|
286
|
+
|
|
287
|
+
if (!result) {
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
...result,
|
|
293
|
+
textSpan: virtualization && result.textSpan
|
|
294
|
+
? remapToolingTextSpanToOriginal(result.textSpan, virtualization)
|
|
295
|
+
: result.textSpan,
|
|
296
|
+
definitions: result.definitions?.map((definition) => remapFileSpanRecord(definition, getVirtualization)),
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function wrapReferences(method, getVirtualization) {
|
|
302
|
+
return (fileName, position) => {
|
|
303
|
+
const virtualization = getVirtualization(fileName);
|
|
304
|
+
const mappedPosition = virtualization
|
|
305
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
306
|
+
: position;
|
|
307
|
+
const references = method?.(fileName, mappedPosition);
|
|
308
|
+
|
|
309
|
+
if (!references?.length) {
|
|
310
|
+
return references;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return references.map((reference) => remapFileSpanRecord(reference, getVirtualization));
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function wrapRenameInfo(method, getVirtualization) {
|
|
318
|
+
return (fileName, position, ...rest) => {
|
|
319
|
+
const virtualization = getVirtualization(fileName);
|
|
320
|
+
const mappedPosition = virtualization
|
|
321
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
322
|
+
: position;
|
|
323
|
+
const info = method?.(fileName, mappedPosition, ...rest);
|
|
324
|
+
|
|
325
|
+
if (!info || !virtualization || !info.triggerSpan) {
|
|
326
|
+
return info;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
...info,
|
|
331
|
+
triggerSpan: remapToolingTextSpanToOriginal(info.triggerSpan, virtualization),
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function wrapRenameLocations(method, getVirtualization) {
|
|
337
|
+
return (fileName, position, ...rest) => {
|
|
338
|
+
const virtualization = getVirtualization(fileName);
|
|
339
|
+
const mappedPosition = virtualization
|
|
340
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
341
|
+
: position;
|
|
342
|
+
const locations = method?.(fileName, mappedPosition, ...rest);
|
|
343
|
+
|
|
344
|
+
if (!locations?.length) {
|
|
345
|
+
return locations;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return locations.map((location) => remapFileSpanRecord(location, getVirtualization));
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function wrapQuickInfo(method, getVirtualization) {
|
|
353
|
+
return (fileName, position) => {
|
|
354
|
+
const virtualization = getVirtualization(fileName);
|
|
355
|
+
const mappedPosition = virtualization
|
|
356
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
357
|
+
: position;
|
|
358
|
+
const info = method(fileName, mappedPosition);
|
|
359
|
+
|
|
360
|
+
if (!virtualization) {
|
|
361
|
+
return info;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function createHoistQuickInfo(hoistInfo, fallbackSpan = null) {
|
|
365
|
+
return {
|
|
366
|
+
kind: "function",
|
|
367
|
+
kindModifiers: "",
|
|
368
|
+
textSpan: hoistInfo
|
|
369
|
+
? {
|
|
370
|
+
start: hoistInfo.start,
|
|
371
|
+
length: hoistInfo.length,
|
|
372
|
+
}
|
|
373
|
+
: fallbackSpan,
|
|
374
|
+
displayParts: [
|
|
375
|
+
{ text: hoistInfo?.name ?? "static hoist", kind: "functionName" },
|
|
376
|
+
{ text: "(...)", kind: "punctuation" },
|
|
377
|
+
{ text: ": ", kind: "punctuation" },
|
|
378
|
+
{ text: "static hoist", kind: "keyword" },
|
|
379
|
+
],
|
|
380
|
+
documentation: [
|
|
381
|
+
{
|
|
382
|
+
text: hoistInfo?.documentation ?? "LitSX static hoist.",
|
|
383
|
+
kind: "text",
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!info) {
|
|
390
|
+
const hoistInfo = inferLitsxStaticHoistInfoAtPosition(virtualization.sourceText, position);
|
|
391
|
+
if (hoistInfo) {
|
|
392
|
+
return createHoistQuickInfo(hoistInfo);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const attributeInfo = inferLitsxAttributeInfoAtPosition(virtualization.sourceText, position);
|
|
396
|
+
if (!attributeInfo) {
|
|
397
|
+
return info;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let kindLabel = "binding";
|
|
401
|
+
let detail = "LitSX binding";
|
|
402
|
+
|
|
403
|
+
if (attributeInfo.prefix === "@") {
|
|
404
|
+
kindLabel = "event";
|
|
405
|
+
detail = "LitSX event listener binding";
|
|
406
|
+
} else if (attributeInfo.prefix === ".") {
|
|
407
|
+
kindLabel = "property";
|
|
408
|
+
detail = "LitSX property binding";
|
|
409
|
+
} else if (attributeInfo.prefix === "?") {
|
|
410
|
+
kindLabel = "boolean";
|
|
411
|
+
detail = "LitSX boolean attribute binding";
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
kind: "property",
|
|
416
|
+
kindModifiers: "",
|
|
417
|
+
textSpan: {
|
|
418
|
+
start: attributeInfo.start,
|
|
419
|
+
length: attributeInfo.length,
|
|
420
|
+
},
|
|
421
|
+
displayParts: [
|
|
422
|
+
{ text: attributeInfo.name, kind: "propertyName" },
|
|
423
|
+
{ text: ": ", kind: "punctuation" },
|
|
424
|
+
{ text: kindLabel, kind: "keyword" },
|
|
425
|
+
],
|
|
426
|
+
documentation: [
|
|
427
|
+
{
|
|
428
|
+
text: `${detail} for <${attributeInfo.tagName}>.`,
|
|
429
|
+
kind: "text",
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const hoistInfo = inferLitsxStaticHoistInfoAtPosition(virtualization.sourceText, position);
|
|
436
|
+
if (hoistInfo) {
|
|
437
|
+
return createHoistQuickInfo(hoistInfo);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const remappedDisplayParts = remapDisplayParts(info.displayParts);
|
|
441
|
+
return {
|
|
442
|
+
...info,
|
|
443
|
+
textSpan: remapToolingTextSpanToOriginal(info.textSpan, virtualization),
|
|
444
|
+
displayParts: remappedDisplayParts,
|
|
445
|
+
documentation: remapDisplayParts(info.documentation),
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function getLitsxCompletionMetadata(name) {
|
|
451
|
+
if (typeof name !== "string" || name.length < 2) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (name.startsWith("@")) {
|
|
456
|
+
return {
|
|
457
|
+
kind: "memberVariableElement",
|
|
458
|
+
kindLabel: "event",
|
|
459
|
+
detail: "LitSX event listener binding",
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (name.startsWith(".")) {
|
|
464
|
+
return {
|
|
465
|
+
kind: "property",
|
|
466
|
+
kindLabel: "property",
|
|
467
|
+
detail: "LitSX property binding",
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (name.startsWith("?")) {
|
|
472
|
+
return {
|
|
473
|
+
kind: "property",
|
|
474
|
+
kindLabel: "boolean",
|
|
475
|
+
detail: "LitSX boolean attribute binding",
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function createContextualReplacementSpan(context) {
|
|
483
|
+
if (!context) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
start: context.start + 1,
|
|
489
|
+
length: Math.max(context.length - 1, 0),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function createContextualCompletionEntries(virtualization, position) {
|
|
494
|
+
if (!virtualization) {
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const context = inferLitsxAttributeCompletionContext(virtualization.sourceText, position);
|
|
499
|
+
|
|
500
|
+
return getLitsxAttributeCompletionNames(context).map((name, index) => {
|
|
501
|
+
const metadata = getLitsxCompletionMetadata(name);
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
name,
|
|
505
|
+
kind: metadata?.kind ?? "property",
|
|
506
|
+
kindModifiers: "",
|
|
507
|
+
sortText: `0${index}`,
|
|
508
|
+
insertText: name.slice(1),
|
|
509
|
+
filterText: name.slice(1),
|
|
510
|
+
replacementSpan: createContextualReplacementSpan(context),
|
|
511
|
+
source: "LitSX",
|
|
512
|
+
data: {
|
|
513
|
+
__litsxContextualCompletion: true,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function wrapCompletions(method, getVirtualization) {
|
|
520
|
+
return (fileName, position, options, formattingSettings) => {
|
|
521
|
+
const virtualization = getVirtualization(fileName);
|
|
522
|
+
const mappedPosition = virtualization
|
|
523
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
524
|
+
: position;
|
|
525
|
+
const completions = method(fileName, mappedPosition, options, formattingSettings);
|
|
526
|
+
const contextualEntries = createContextualCompletionEntries(virtualization, position);
|
|
527
|
+
|
|
528
|
+
const filteredEntries = (completions?.entries ?? []).filter(
|
|
529
|
+
(entry) => !decodeVirtualAttributeName(entry.name),
|
|
530
|
+
);
|
|
531
|
+
const mergedEntries = [...contextualEntries];
|
|
532
|
+
const seenNames = new Set(contextualEntries.map((entry) => entry.name));
|
|
533
|
+
|
|
534
|
+
for (const entry of filteredEntries) {
|
|
535
|
+
if (seenNames.has(entry.name)) continue;
|
|
536
|
+
seenNames.add(entry.name);
|
|
537
|
+
mergedEntries.push(entry);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!completions && mergedEntries.length === 0) {
|
|
541
|
+
return completions;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
...(completions ?? {}),
|
|
546
|
+
entries: mergedEntries,
|
|
547
|
+
};
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function wrapCompletionEntryDetails(method, getVirtualization) {
|
|
552
|
+
return (fileName, position, entryName, ...rest) => {
|
|
553
|
+
const virtualization = getVirtualization(fileName);
|
|
554
|
+
const metadata = getLitsxCompletionMetadata(entryName);
|
|
555
|
+
|
|
556
|
+
if (virtualization && metadata) {
|
|
557
|
+
const context = inferLitsxAttributeCompletionContext(virtualization.sourceText, position);
|
|
558
|
+
if (context && getLitsxAttributeCompletionNames(context).includes(entryName)) {
|
|
559
|
+
return {
|
|
560
|
+
name: entryName,
|
|
561
|
+
kind: metadata.kind,
|
|
562
|
+
kindModifiers: "",
|
|
563
|
+
displayParts: [
|
|
564
|
+
{ text: entryName, kind: "propertyName" },
|
|
565
|
+
{ text: ": ", kind: "punctuation" },
|
|
566
|
+
{ text: metadata.kindLabel, kind: "keyword" },
|
|
567
|
+
],
|
|
568
|
+
documentation: [
|
|
569
|
+
{
|
|
570
|
+
text: `${metadata.detail} for <${context.tagName}>.`,
|
|
571
|
+
kind: "text",
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
tags: [],
|
|
575
|
+
codeActions: [],
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (typeof method !== "function") {
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const mappedPosition = virtualization
|
|
585
|
+
? mapOriginalPositionToToolingVirtual(position, virtualization)
|
|
586
|
+
: position;
|
|
587
|
+
const details = method(fileName, mappedPosition, entryName, ...rest);
|
|
588
|
+
|
|
589
|
+
if (!details || !virtualization) {
|
|
590
|
+
return details;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
...details,
|
|
595
|
+
displayParts: remapDisplayParts(details.displayParts),
|
|
596
|
+
documentation: remapDisplayParts(details.documentation),
|
|
597
|
+
};
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export default function init(modules) {
|
|
602
|
+
const ts = modules.typescript;
|
|
603
|
+
|
|
604
|
+
return {
|
|
605
|
+
create(info) {
|
|
606
|
+
const host = info.languageServiceHost;
|
|
607
|
+
|
|
608
|
+
if (!host || typeof host.getScriptSnapshot !== "function") {
|
|
609
|
+
return info.languageService;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const originalGetScriptSnapshot = host.getScriptSnapshot.bind(host);
|
|
613
|
+
const virtualizations = new Map();
|
|
614
|
+
|
|
615
|
+
function getPluginsForFile(fileName) {
|
|
616
|
+
return (fileName.endsWith(".tsx") || fileName.endsWith(".litsx")) ? ["typescript"] : [];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const originalGetScriptKind = typeof host.getScriptKind === "function"
|
|
620
|
+
? host.getScriptKind.bind(host)
|
|
621
|
+
: null;
|
|
622
|
+
|
|
623
|
+
host.getScriptKind = (fileName) => {
|
|
624
|
+
if (fileName.endsWith(".litsx")) {
|
|
625
|
+
return ts.ScriptKind.TSX;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (fileName.endsWith(".litsx.jsx")) {
|
|
629
|
+
return ts.ScriptKind.JSX;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return originalGetScriptKind?.(fileName);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
function getCachedRecord(fileName) {
|
|
636
|
+
return virtualizations.get(fileName) ?? null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function clearRecord(fileName) {
|
|
640
|
+
virtualizations.delete(fileName);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function getOrBuildVirtualizationRecord(fileName) {
|
|
644
|
+
const snapshot = originalGetScriptSnapshot(fileName);
|
|
645
|
+
|
|
646
|
+
if (!snapshot || !isRelevantFile(fileName)) {
|
|
647
|
+
clearRecord(fileName);
|
|
648
|
+
return {
|
|
649
|
+
snapshot,
|
|
650
|
+
record: null,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const cachedRecord = getCachedRecord(fileName);
|
|
655
|
+
if (cachedRecord?.snapshot === snapshot) {
|
|
656
|
+
return {
|
|
657
|
+
snapshot,
|
|
658
|
+
record: cachedRecord,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const sourceText = readSnapshotText(snapshot);
|
|
663
|
+
if (cachedRecord?.sourceText === sourceText) {
|
|
664
|
+
cachedRecord.snapshot = snapshot;
|
|
665
|
+
return {
|
|
666
|
+
snapshot,
|
|
667
|
+
record: cachedRecord,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!looksLikeLitsxJsx(sourceText)) {
|
|
672
|
+
clearRecord(fileName);
|
|
673
|
+
return {
|
|
674
|
+
snapshot,
|
|
675
|
+
record: null,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const parserPlugins = getPluginsForFile(fileName);
|
|
680
|
+
const virtualization = profilePhase(
|
|
681
|
+
"typescript-plugin",
|
|
682
|
+
"tooling-virtualization",
|
|
683
|
+
() => createToolingVirtualLitsxSource(sourceText, {
|
|
684
|
+
plugins: parserPlugins,
|
|
685
|
+
}),
|
|
686
|
+
);
|
|
687
|
+
|
|
688
|
+
if (virtualization.code === sourceText) {
|
|
689
|
+
clearRecord(fileName);
|
|
690
|
+
return {
|
|
691
|
+
snapshot,
|
|
692
|
+
record: null,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const record = {
|
|
697
|
+
...virtualization,
|
|
698
|
+
sourceText,
|
|
699
|
+
parserPlugins,
|
|
700
|
+
snapshot,
|
|
701
|
+
virtualizedSnapshot: null,
|
|
702
|
+
authoredDiagnostics: null,
|
|
703
|
+
authoredDiagnosticsReady: false,
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
virtualizations.set(fileName, record);
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
snapshot,
|
|
710
|
+
record,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function getVirtualization(fileName) {
|
|
715
|
+
return getOrBuildVirtualizationRecord(fileName).record;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function getAuthoredDiagnostics(fileName) {
|
|
719
|
+
const record = getVirtualization(fileName);
|
|
720
|
+
if (!record) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (!record.authoredDiagnosticsReady) {
|
|
725
|
+
record.authoredDiagnostics = profilePhase(
|
|
726
|
+
"typescript-plugin",
|
|
727
|
+
"authored-diagnostics",
|
|
728
|
+
() => collectLitsxAuthoredDiagnostics(record.sourceText, ts, {
|
|
729
|
+
plugins: record.parserPlugins,
|
|
730
|
+
}),
|
|
731
|
+
);
|
|
732
|
+
record.authoredDiagnosticsReady = true;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return record.authoredDiagnostics;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
host.getScriptSnapshot = (fileName) => {
|
|
739
|
+
const { snapshot, record } = getOrBuildVirtualizationRecord(fileName);
|
|
740
|
+
|
|
741
|
+
if (!record) {
|
|
742
|
+
return snapshot;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (!record.virtualizedSnapshot) {
|
|
746
|
+
record.virtualizedSnapshot = createSnapshot(ts, record.code);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return record.virtualizedSnapshot;
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
const languageService = info.languageService;
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
...languageService,
|
|
756
|
+
getSyntacticDiagnostics: wrapSyntacticDiagnostics(
|
|
757
|
+
languageService.getSyntacticDiagnostics.bind(languageService),
|
|
758
|
+
getVirtualization,
|
|
759
|
+
getAuthoredDiagnostics,
|
|
760
|
+
ts,
|
|
761
|
+
),
|
|
762
|
+
getSemanticDiagnostics: wrapSemanticDiagnostics(
|
|
763
|
+
languageService.getSemanticDiagnostics.bind(languageService),
|
|
764
|
+
getVirtualization,
|
|
765
|
+
getAuthoredDiagnostics,
|
|
766
|
+
),
|
|
767
|
+
getSuggestionDiagnostics: wrapDiagnostics(
|
|
768
|
+
languageService.getSuggestionDiagnostics.bind(languageService),
|
|
769
|
+
getVirtualization,
|
|
770
|
+
),
|
|
771
|
+
getQuickInfoAtPosition: wrapQuickInfo(
|
|
772
|
+
languageService.getQuickInfoAtPosition.bind(languageService),
|
|
773
|
+
getVirtualization,
|
|
774
|
+
),
|
|
775
|
+
getCompletionsAtPosition: wrapCompletions(
|
|
776
|
+
languageService.getCompletionsAtPosition.bind(languageService),
|
|
777
|
+
getVirtualization,
|
|
778
|
+
),
|
|
779
|
+
getCompletionEntryDetails: wrapCompletionEntryDetails(
|
|
780
|
+
languageService.getCompletionEntryDetails?.bind(languageService),
|
|
781
|
+
getVirtualization,
|
|
782
|
+
),
|
|
783
|
+
getDefinitionAtPosition: wrapDefinitionAtPosition(
|
|
784
|
+
languageService.getDefinitionAtPosition?.bind(languageService),
|
|
785
|
+
getVirtualization,
|
|
786
|
+
),
|
|
787
|
+
getDefinitionAndBoundSpan: wrapDefinitionAndBoundSpan(
|
|
788
|
+
languageService.getDefinitionAndBoundSpan?.bind(languageService),
|
|
789
|
+
getVirtualization,
|
|
790
|
+
),
|
|
791
|
+
getReferencesAtPosition: wrapReferences(
|
|
792
|
+
languageService.getReferencesAtPosition?.bind(languageService),
|
|
793
|
+
getVirtualization,
|
|
794
|
+
),
|
|
795
|
+
getRenameInfo: wrapRenameInfo(
|
|
796
|
+
languageService.getRenameInfo?.bind(languageService),
|
|
797
|
+
getVirtualization,
|
|
798
|
+
),
|
|
799
|
+
findRenameLocations: wrapRenameLocations(
|
|
800
|
+
languageService.findRenameLocations?.bind(languageService),
|
|
801
|
+
getVirtualization,
|
|
802
|
+
),
|
|
803
|
+
};
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
getExternalFiles(project) {
|
|
807
|
+
const fileNames = project.getFileNames?.() ?? [];
|
|
808
|
+
const projectVersion = project.getProjectVersion?.() ?? "";
|
|
809
|
+
const cacheKey = `${projectVersion}:${fileNames.join("\0")}`;
|
|
810
|
+
const cached = EXTERNAL_FILES_CACHE.get(project);
|
|
811
|
+
if (cached?.cacheKey === cacheKey) {
|
|
812
|
+
return cached.result;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const result = fileNames.filter((fileName) => {
|
|
816
|
+
if (!isRelevantFile(fileName) || !fs.existsSync(fileName)) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const sourceText = fs.readFileSync(fileName, "utf8");
|
|
821
|
+
return looksLikeLitsxJsx(sourceText);
|
|
822
|
+
});
|
|
823
|
+
EXTERNAL_FILES_CACHE.set(project, { cacheKey, result });
|
|
824
|
+
return result;
|
|
825
|
+
},
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export {
|
|
830
|
+
collectLitsxAuthoredDiagnostics,
|
|
831
|
+
createVirtualLitsxJsxSource,
|
|
832
|
+
createToolingVirtualLitsxSource,
|
|
833
|
+
decodeVirtualAttributeName,
|
|
834
|
+
getLitsxAttributeCompletionNames,
|
|
835
|
+
inferLitsxAttributeInfoAtPosition,
|
|
836
|
+
inferLitsxAttributeCompletionContext,
|
|
837
|
+
inferLitsxMarkupCompletionContext,
|
|
838
|
+
looksLikeLitsxJsx,
|
|
839
|
+
mapOriginalPositionToVirtual,
|
|
840
|
+
mapOriginalPositionToToolingVirtual,
|
|
841
|
+
remapVirtualText,
|
|
842
|
+
remapTextSpanToOriginal,
|
|
843
|
+
remapToolingTextSpanToOriginal,
|
|
844
|
+
} from "./virtualization.js";
|