@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/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";