@glint/ember-tsc 1.0.1-unstable.845d5fa

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.
Files changed (180) hide show
  1. package/README.md +46 -0
  2. package/bin/glint-language-server.js +2 -0
  3. package/bin/glint.js +4 -0
  4. package/lib/cli/run-volar-tsc.d.ts +2 -0
  5. package/lib/cli/run-volar-tsc.d.ts.map +1 -0
  6. package/lib/cli/run-volar-tsc.js +30 -0
  7. package/lib/cli/run-volar-tsc.js.map +1 -0
  8. package/lib/config/config.d.ts +15 -0
  9. package/lib/config/config.d.ts.map +1 -0
  10. package/lib/config/config.js +21 -0
  11. package/lib/config/config.js.map +1 -0
  12. package/lib/config/environment.d.ts +26 -0
  13. package/lib/config/environment.d.ts.map +1 -0
  14. package/lib/config/environment.js +96 -0
  15. package/lib/config/environment.js.map +1 -0
  16. package/lib/config/index.d.ts +17 -0
  17. package/lib/config/index.d.ts.map +1 -0
  18. package/lib/config/index.js +26 -0
  19. package/lib/config/index.js.map +1 -0
  20. package/lib/config/loader.d.ts +25 -0
  21. package/lib/config/loader.d.ts.map +1 -0
  22. package/lib/config/loader.js +110 -0
  23. package/lib/config/loader.js.map +1 -0
  24. package/lib/config/types.cjs +3 -0
  25. package/lib/config/types.cjs.map +1 -0
  26. package/lib/config/types.d.cts +60 -0
  27. package/lib/config/types.d.cts.map +1 -0
  28. package/lib/environment-ember-template-imports/-private/environment/common.d.ts +13 -0
  29. package/lib/environment-ember-template-imports/-private/environment/common.d.ts.map +1 -0
  30. package/lib/environment-ember-template-imports/-private/environment/common.js +2 -0
  31. package/lib/environment-ember-template-imports/-private/environment/common.js.map +1 -0
  32. package/lib/environment-ember-template-imports/-private/environment/index.d.ts +3 -0
  33. package/lib/environment-ember-template-imports/-private/environment/index.d.ts.map +1 -0
  34. package/lib/environment-ember-template-imports/-private/environment/index.js +76 -0
  35. package/lib/environment-ember-template-imports/-private/environment/index.js.map +1 -0
  36. package/lib/environment-ember-template-imports/-private/environment/preprocess.d.ts +4 -0
  37. package/lib/environment-ember-template-imports/-private/environment/preprocess.d.ts.map +1 -0
  38. package/lib/environment-ember-template-imports/-private/environment/preprocess.js +73 -0
  39. package/lib/environment-ember-template-imports/-private/environment/preprocess.js.map +1 -0
  40. package/lib/environment-ember-template-imports/-private/environment/transform.d.ts +4 -0
  41. package/lib/environment-ember-template-imports/-private/environment/transform.d.ts.map +1 -0
  42. package/lib/environment-ember-template-imports/-private/environment/transform.js +134 -0
  43. package/lib/environment-ember-template-imports/-private/environment/transform.js.map +1 -0
  44. package/lib/index.d.ts +7 -0
  45. package/lib/index.d.ts.map +1 -0
  46. package/lib/index.js +6 -0
  47. package/lib/index.js.map +1 -0
  48. package/lib/plugins/g-compiler-errors.d.ts +12 -0
  49. package/lib/plugins/g-compiler-errors.d.ts.map +1 -0
  50. package/lib/plugins/g-compiler-errors.js +58 -0
  51. package/lib/plugins/g-compiler-errors.js.map +1 -0
  52. package/lib/plugins/g-template-tag-symbols.d.ts +11 -0
  53. package/lib/plugins/g-template-tag-symbols.d.ts.map +1 -0
  54. package/lib/plugins/g-template-tag-symbols.js +48 -0
  55. package/lib/plugins/g-template-tag-symbols.js.map +1 -0
  56. package/lib/plugins/utils.d.ts +25 -0
  57. package/lib/plugins/utils.d.ts.map +1 -0
  58. package/lib/plugins/utils.js +63 -0
  59. package/lib/plugins/utils.js.map +1 -0
  60. package/lib/transform/diagnostics/augmentation.d.ts +4 -0
  61. package/lib/transform/diagnostics/augmentation.d.ts.map +1 -0
  62. package/lib/transform/diagnostics/augmentation.js +223 -0
  63. package/lib/transform/diagnostics/augmentation.js.map +1 -0
  64. package/lib/transform/diagnostics/index.d.ts +5 -0
  65. package/lib/transform/diagnostics/index.d.ts.map +1 -0
  66. package/lib/transform/diagnostics/index.js +2 -0
  67. package/lib/transform/diagnostics/index.js.map +1 -0
  68. package/lib/transform/index.d.ts +4 -0
  69. package/lib/transform/index.d.ts.map +1 -0
  70. package/lib/transform/index.js +2 -0
  71. package/lib/transform/index.js.map +1 -0
  72. package/lib/transform/template/code-features.d.ts +30 -0
  73. package/lib/transform/template/code-features.d.ts.map +1 -0
  74. package/lib/transform/template/code-features.js +26 -0
  75. package/lib/transform/template/code-features.js.map +1 -0
  76. package/lib/transform/template/glimmer-ast-mapping-tree.d.ts +80 -0
  77. package/lib/transform/template/glimmer-ast-mapping-tree.d.ts.map +1 -0
  78. package/lib/transform/template/glimmer-ast-mapping-tree.js +132 -0
  79. package/lib/transform/template/glimmer-ast-mapping-tree.js.map +1 -0
  80. package/lib/transform/template/inlining/index.d.ts +16 -0
  81. package/lib/transform/template/inlining/index.d.ts.map +1 -0
  82. package/lib/transform/template/inlining/index.js +21 -0
  83. package/lib/transform/template/inlining/index.js.map +1 -0
  84. package/lib/transform/template/inlining/tagged-strings.d.ts +8 -0
  85. package/lib/transform/template/inlining/tagged-strings.d.ts.map +1 -0
  86. package/lib/transform/template/inlining/tagged-strings.js +140 -0
  87. package/lib/transform/template/inlining/tagged-strings.js.map +1 -0
  88. package/lib/transform/template/map-template-contents.d.ts +121 -0
  89. package/lib/transform/template/map-template-contents.d.ts.map +1 -0
  90. package/lib/transform/template/map-template-contents.js +287 -0
  91. package/lib/transform/template/map-template-contents.js.map +1 -0
  92. package/lib/transform/template/rewrite-module.d.ts +22 -0
  93. package/lib/transform/template/rewrite-module.d.ts.map +1 -0
  94. package/lib/transform/template/rewrite-module.js +265 -0
  95. package/lib/transform/template/rewrite-module.js.map +1 -0
  96. package/lib/transform/template/scope-stack.d.ts +13 -0
  97. package/lib/transform/template/scope-stack.d.ts.map +1 -0
  98. package/lib/transform/template/scope-stack.js +28 -0
  99. package/lib/transform/template/scope-stack.js.map +1 -0
  100. package/lib/transform/template/template-to-typescript.d.ts +19 -0
  101. package/lib/transform/template/template-to-typescript.d.ts.map +1 -0
  102. package/lib/transform/template/template-to-typescript.js +1095 -0
  103. package/lib/transform/template/template-to-typescript.js.map +1 -0
  104. package/lib/transform/template/transformed-module.d.ts +111 -0
  105. package/lib/transform/template/transformed-module.d.ts.map +1 -0
  106. package/lib/transform/template/transformed-module.js +287 -0
  107. package/lib/transform/template/transformed-module.js.map +1 -0
  108. package/lib/transform/util.d.ts +7 -0
  109. package/lib/transform/util.d.ts.map +1 -0
  110. package/lib/transform/util.js +15 -0
  111. package/lib/transform/util.js.map +1 -0
  112. package/lib/volar/ember-language-plugin.d.ts +14 -0
  113. package/lib/volar/ember-language-plugin.d.ts.map +1 -0
  114. package/lib/volar/ember-language-plugin.js +91 -0
  115. package/lib/volar/ember-language-plugin.js.map +1 -0
  116. package/lib/volar/gts-virtual-code.d.ts +83 -0
  117. package/lib/volar/gts-virtual-code.d.ts.map +1 -0
  118. package/lib/volar/gts-virtual-code.js +210 -0
  119. package/lib/volar/gts-virtual-code.js.map +1 -0
  120. package/lib/volar/language-server.d.ts +2 -0
  121. package/lib/volar/language-server.d.ts.map +1 -0
  122. package/lib/volar/language-server.js +214 -0
  123. package/lib/volar/language-server.js.map +1 -0
  124. package/lib/volar/script-snapshot.d.ts +17 -0
  125. package/lib/volar/script-snapshot.d.ts.map +1 -0
  126. package/lib/volar/script-snapshot.js +24 -0
  127. package/lib/volar/script-snapshot.js.map +1 -0
  128. package/package.json +114 -0
  129. package/src/cli/run-volar-tsc.ts +36 -0
  130. package/src/config/config.ts +33 -0
  131. package/src/config/environment.ts +128 -0
  132. package/src/config/index.ts +30 -0
  133. package/src/config/loader.ts +143 -0
  134. package/src/config/types.cts +85 -0
  135. package/src/environment-ember-template-imports/-private/environment/common.ts +14 -0
  136. package/src/environment-ember-template-imports/-private/environment/index.ts +83 -0
  137. package/src/environment-ember-template-imports/-private/environment/preprocess.ts +90 -0
  138. package/src/environment-ember-template-imports/-private/environment/transform.ts +202 -0
  139. package/src/index.ts +9 -0
  140. package/src/plugins/g-compiler-errors.ts +67 -0
  141. package/src/plugins/g-template-tag-symbols.ts +54 -0
  142. package/src/plugins/utils.ts +86 -0
  143. package/src/transform/diagnostics/augmentation.ts +333 -0
  144. package/src/transform/diagnostics/index.ts +5 -0
  145. package/src/transform/index.ts +4 -0
  146. package/src/transform/template/code-features.ts +30 -0
  147. package/src/transform/template/glimmer-ast-mapping-tree.ts +173 -0
  148. package/src/transform/template/inlining/index.ts +33 -0
  149. package/src/transform/template/inlining/tagged-strings.ts +187 -0
  150. package/src/transform/template/map-template-contents.ts +501 -0
  151. package/src/transform/template/rewrite-module.ts +372 -0
  152. package/src/transform/template/scope-stack.ts +34 -0
  153. package/src/transform/template/template-to-typescript.ts +1476 -0
  154. package/src/transform/template/transformed-module.ts +431 -0
  155. package/src/transform/util.ts +24 -0
  156. package/src/volar/ember-language-plugin.ts +108 -0
  157. package/src/volar/gts-virtual-code.ts +249 -0
  158. package/src/volar/language-server.ts +250 -0
  159. package/src/volar/script-snapshot.ts +27 -0
  160. package/types/-private/dsl/globals.d.ts +204 -0
  161. package/types/-private/dsl/index.d.ts +50 -0
  162. package/types/-private/dsl/integration-declarations.d.ts +143 -0
  163. package/types/-private/intrinsics/action.d.ts +45 -0
  164. package/types/-private/intrinsics/concat.d.ts +6 -0
  165. package/types/-private/intrinsics/each-in.d.ts +24 -0
  166. package/types/-private/intrinsics/each.d.ts +17 -0
  167. package/types/-private/intrinsics/fn.d.ts +44 -0
  168. package/types/-private/intrinsics/get.d.ts +31 -0
  169. package/types/-private/intrinsics/input.d.ts +24 -0
  170. package/types/-private/intrinsics/link-to.d.ts +31 -0
  171. package/types/-private/intrinsics/log.d.ts +6 -0
  172. package/types/-private/intrinsics/mount.d.ts +9 -0
  173. package/types/-private/intrinsics/mut.d.ts +14 -0
  174. package/types/-private/intrinsics/on.d.ts +21 -0
  175. package/types/-private/intrinsics/outlet.d.ts +8 -0
  176. package/types/-private/intrinsics/textarea.d.ts +16 -0
  177. package/types/-private/intrinsics/unbound.d.ts +10 -0
  178. package/types/-private/intrinsics/unique-id.d.ts +5 -0
  179. package/types/globals/index.d.ts +3 -0
  180. package/types/silent-error.d.ts +4 -0
@@ -0,0 +1,431 @@
1
+ import type { AST } from '@glimmer/syntax';
2
+ import { CodeInformation, CodeMapping } from '@volar/language-core';
3
+ import { assert } from '../util.js';
4
+ import { codeFeatures } from './code-features.js';
5
+ import GlimmerASTMappingTree from './glimmer-ast-mapping-tree.js';
6
+
7
+ export type Range = { start: number; end: number };
8
+ export type RangeWithMapping = Range & { mapping?: GlimmerASTMappingTree };
9
+ export type RangeWithMappingAndSource = RangeWithMapping & { source: SourceFile };
10
+
11
+ export type CorrelatedSpan = {
12
+ /** Where this span of content originated */
13
+ originalFile: SourceFile;
14
+ /** The offset where this content began in its original source */
15
+ originalStart: number;
16
+ /** The length of this span's content in its original source */
17
+ originalLength: number;
18
+ /** The location in the untransformed source where this span is spliced in */
19
+ insertionPoint: number;
20
+ /** The contents of this span in the transformed output */
21
+ transformedSource: string;
22
+ /** The offset in the transformed output of this span */
23
+ transformedStart: number;
24
+ /** The length of this span in the transformed output */
25
+ transformedLength: number;
26
+ /** (Glimmer/Handlebars spans only:) A mapping of offsets within this span between its original and transformed versions */
27
+ glimmerAstMapping?: GlimmerASTMappingTree;
28
+ };
29
+
30
+ export type DirectiveKind = 'ignore' | 'expect-error' | 'nocheck';
31
+ export type Directive = {
32
+ kind: DirectiveKind;
33
+ commentNode: AST.CommentStatement | AST.MustacheCommentStatement;
34
+ source: SourceFile;
35
+ location: Range;
36
+ areaOfEffect: Range;
37
+ /** The number of errors that have been reported for this directive */
38
+ errorCount: number;
39
+ };
40
+
41
+ export type TransformError = {
42
+ isContentTagError?: boolean;
43
+ message: string;
44
+ location: Range;
45
+ source: SourceFile;
46
+ };
47
+
48
+ export type SourceFile = {
49
+ filename: string;
50
+ contents: string;
51
+ };
52
+
53
+ export type TemplateSymbol = {
54
+ start: number;
55
+ end: number;
56
+ startTagEnd: number;
57
+ };
58
+
59
+ /**
60
+ * This class represents the result of transforming a TypeScript
61
+ * module with one or more embedded HBS templates. It contains
62
+ * both the original and transformed source text of the module, as
63
+ * well any errors encountered during transformation.
64
+ *
65
+ * It is used heavily for bidirectional source mapping between the original TS/HBS code
66
+ * and the singular transformed TS output (aka the Intermediate Representation).
67
+ * It can be queried with an offset or range in either the
68
+ * original or transformed source to determine the corresponding
69
+ * offset or range in the other.
70
+ */
71
+ export default class TransformedModule {
72
+ public constructor(
73
+ public readonly transformedContents: string,
74
+ public readonly errors: ReadonlyArray<TransformError>,
75
+ public readonly directives: ReadonlyArray<Directive>,
76
+ public readonly correlatedSpans: Array<CorrelatedSpan>,
77
+ public readonly originalFileName: string,
78
+ ) {}
79
+
80
+ public toDebugString(): string {
81
+ let mappingStrings = this.correlatedSpans.map((span) =>
82
+ span.glimmerAstMapping?.toDebugString({
83
+ originalStart: span.originalStart,
84
+ originalSource: span.originalFile.contents.slice(
85
+ span.originalStart,
86
+ span.originalStart + span.originalLength,
87
+ ),
88
+ transformedStart: span.transformedStart,
89
+ transformedSource: span.transformedSource,
90
+ }),
91
+ );
92
+
93
+ return `TransformedModule\n\n${mappingStrings.filter(Boolean).join('\n\n')}`;
94
+ }
95
+
96
+ public getOriginalOffset(transformedOffset: number): { source?: SourceFile; offset: number } {
97
+ let { start, source } = this.getOriginalRange(transformedOffset, transformedOffset);
98
+ return { source, offset: start };
99
+ }
100
+
101
+ public getTransformedOffset(originalFileName: string, originalOffset: number): number {
102
+ return this.getTransformedRange(originalFileName, originalOffset, originalOffset).start;
103
+ }
104
+
105
+ public getOriginalRange(
106
+ transformedStart: number,
107
+ transformedEnd: number,
108
+ ): RangeWithMappingAndSource {
109
+ let startInfo = this.determineOriginalOffsetAndSpan(transformedStart);
110
+ let endInfo = this.determineOriginalOffsetAndSpan(transformedEnd);
111
+
112
+ assert(
113
+ startInfo.correlatedSpan.originalFile === endInfo.correlatedSpan.originalFile,
114
+ 'Attempted to transform a range across two different files',
115
+ );
116
+
117
+ let source = startInfo.correlatedSpan.originalFile;
118
+ let start = startInfo.originalOffset;
119
+ let end = endInfo.originalOffset;
120
+
121
+ if (startInfo.correlatedSpan === endInfo.correlatedSpan) {
122
+ let { correlatedSpan } = startInfo;
123
+ let mapping = correlatedSpan.glimmerAstMapping?.narrowestMappingForTransformedRange({
124
+ start: start - correlatedSpan.originalStart,
125
+ end: end - correlatedSpan.originalStart,
126
+ });
127
+
128
+ if (mapping) {
129
+ let start = correlatedSpan.originalStart + mapping.originalRange.start;
130
+ let end = correlatedSpan.originalStart + mapping.originalRange.end;
131
+ return { mapping, start, end, source };
132
+ }
133
+ }
134
+
135
+ return { start, end, source };
136
+ }
137
+
138
+ public getTransformedRange(
139
+ originalFileName: string,
140
+ originalStart: number,
141
+ originalEnd: number,
142
+ ): RangeWithMapping {
143
+ let startInfo = this.determineTransformedOffsetAndSpan(originalFileName, originalStart);
144
+ let endInfo = this.determineTransformedOffsetAndSpan(originalFileName, originalEnd);
145
+
146
+ let start = startInfo.transformedOffset;
147
+ let end = endInfo.transformedOffset;
148
+
149
+ if (startInfo.correlatedSpan && startInfo.correlatedSpan === endInfo.correlatedSpan) {
150
+ let { correlatedSpan } = startInfo;
151
+ let mapping = correlatedSpan.glimmerAstMapping?.narrowestMappingForOriginalRange({
152
+ start: start - correlatedSpan.transformedStart,
153
+ end: end - correlatedSpan.transformedStart,
154
+ });
155
+
156
+ if (mapping) {
157
+ let start = correlatedSpan.transformedStart + mapping.transformedRange.start;
158
+ let end = correlatedSpan.transformedStart + mapping.transformedRange.end;
159
+ return { mapping, start, end };
160
+ }
161
+ }
162
+
163
+ return { start, end };
164
+ }
165
+
166
+ public getExactTransformedRanges(
167
+ originalFileName: string,
168
+ originalStart: number,
169
+ originalEnd: number,
170
+ ): GlimmerASTMappingTree[] {
171
+ let startInfo = this.determineTransformedOffsetAndSpan(originalFileName, originalStart);
172
+ let endInfo = this.determineTransformedOffsetAndSpan(originalFileName, originalEnd);
173
+
174
+ let start = startInfo.transformedOffset;
175
+ let end = endInfo.transformedOffset;
176
+
177
+ if (startInfo.correlatedSpan && startInfo.correlatedSpan === endInfo.correlatedSpan) {
178
+ let { correlatedSpan } = startInfo;
179
+ return (
180
+ correlatedSpan.glimmerAstMapping?.exactMappingsForOriginalRange({
181
+ start: start - correlatedSpan.transformedStart,
182
+ end: end - correlatedSpan.transformedStart,
183
+ }) || []
184
+ );
185
+ }
186
+ return [];
187
+ }
188
+
189
+ public findTemplateAtOriginalOffset(
190
+ originalFileName: string,
191
+ originalOffset: number,
192
+ ): { originalContentStart: number; originalContentEnd: number; originalContent: string } | null {
193
+ let { correlatedSpan } = this.determineTransformedOffsetAndSpan(
194
+ originalFileName,
195
+ originalOffset,
196
+ );
197
+
198
+ if (!correlatedSpan.glimmerAstMapping) {
199
+ return null;
200
+ }
201
+
202
+ let templateMapping = correlatedSpan.glimmerAstMapping?.children[0];
203
+
204
+ assert(
205
+ correlatedSpan.glimmerAstMapping?.sourceNode.type === 'TemplateEmbedding' &&
206
+ templateMapping?.sourceNode.type === 'Template',
207
+ 'Internal error: unexpected mapping structure.' + ` (${templateMapping?.sourceNode.type})`,
208
+ );
209
+
210
+ let originalContentStart = correlatedSpan.originalStart + templateMapping.originalRange.start;
211
+ let originalContentEnd = correlatedSpan.originalStart + templateMapping.originalRange.end;
212
+ let originalContent = correlatedSpan.originalFile.contents.slice(
213
+ originalContentStart,
214
+ originalContentEnd,
215
+ );
216
+
217
+ return { originalContentStart, originalContentEnd, originalContent };
218
+ }
219
+
220
+ private determineOriginalOffsetAndSpan(transformedOffset: number): {
221
+ originalOffset: number;
222
+ correlatedSpan: CorrelatedSpan;
223
+ } {
224
+ for (let span of this.correlatedSpans) {
225
+ if (
226
+ transformedOffset >= span.transformedStart &&
227
+ transformedOffset <= span.transformedStart + span.transformedLength
228
+ ) {
229
+ return {
230
+ originalOffset: transformedOffset - span.transformedStart + span.originalStart,
231
+ correlatedSpan: span,
232
+ };
233
+ }
234
+ }
235
+
236
+ assert(false, 'Internal error: offset out of bounds');
237
+ }
238
+
239
+ private determineTransformedOffsetAndSpan(
240
+ originalFileName: string,
241
+ originalOffset: number,
242
+ ): { transformedOffset: number; correlatedSpan: CorrelatedSpan } {
243
+ for (let span of this.correlatedSpans) {
244
+ if (
245
+ span.originalFile.filename === originalFileName &&
246
+ originalOffset >= span.originalStart &&
247
+ originalOffset < span.originalStart + span.originalLength
248
+ ) {
249
+ return {
250
+ transformedOffset: originalOffset - span.originalStart + span.transformedStart,
251
+ correlatedSpan: span,
252
+ };
253
+ }
254
+ }
255
+
256
+ assert(false, 'Internal error: offset out of bounds');
257
+ }
258
+
259
+ /**
260
+ * Converts the mappings in this transformed module to the format expected by Volar.
261
+ *
262
+ * The main difference between the two formats is that while the classic Glint transformation
263
+ * mappings support mapping a differently sized source region to a differently sized target region
264
+ * (e.g. `{{expectsAtLeastOneArg}}` in an .hbs file to `__glintDSL__.emitContent(__glintDSL__.resolveOrReturn(expectsAtLeastOneArg)());`
265
+ * in a generated TS file, in Volar you can only map regions of the same size.
266
+ *
267
+ * In the case that you need to map regions of different sizes in Volar, you need to use
268
+ * zero-length mappings to delineate regions/boundaries that should map to each other, otherwise there will
269
+ * be cases where TS diagnostics will fail to transform/map back to the original source. Example:
270
+ *
271
+ * - `{{[[ZEROLEN-A]][[expectsAtLeastOneArg]][[ZEROLEN-B]]}}`
272
+ * - to
273
+ * - `[[ZEROLEN-A]]__glintDSL__.emitContent(__glintDSL__.resolveOrReturn([[expectsAtLeastOneArg]])());[[ZEROLEN-B]]`
274
+ */
275
+ public toVolarMappings(filenameFilter?: string): CodeMapping[] {
276
+ const codeMappings: CodeMapping[] = [];
277
+
278
+ const push = (
279
+ sourceOffset: number,
280
+ generatedOffset: number,
281
+ length: number,
282
+ codeInformation: CodeInformation | undefined,
283
+ ): void => {
284
+ // if (sourceOffsets.length > 0) {
285
+ // TODO: these assertions are firing for certain files/transformations, which means
286
+ // we're emitting unsorted mappings, which means volar has to fall back to an inefficient
287
+ // source mapping algorithm rather than using binary search:
288
+ // https://github.com/volarjs/volar.js/blob/3798f27684f5c671f06bf7a19e32bc489e652e14/packages/source-map/lib/translateOffset.ts#L18
289
+ //
290
+ // The fix for this is probably somewhere in the `template-to-typescript.ts` file, but I
291
+ // don't have a sense for how complicated that'll be.
292
+ // assert(
293
+ // sourceOffset >= sourceOffsets[sourceOffsets.length - 1],
294
+ // 'Source offsets should be monotonically increasing',
295
+ // );
296
+ // assert(
297
+ // generatedOffset >= generatedOffsets[generatedOffsets.length - 1],
298
+ // 'Generated offsets should be monotonically increasing',
299
+ // );
300
+ // }
301
+
302
+ codeMappings.push({
303
+ sourceOffsets: [sourceOffset],
304
+ generatedOffsets: [generatedOffset],
305
+ lengths: [length],
306
+ data: codeInformation || {},
307
+ });
308
+ };
309
+
310
+ let recurse = (span: CorrelatedSpan, mapping: GlimmerASTMappingTree): void => {
311
+ const children = mapping.children;
312
+ let { originalRange, transformedRange } = mapping;
313
+ let hbsStart = span.originalStart + originalRange.start;
314
+ let hbsEnd = span.originalStart + originalRange.end;
315
+ let tsStart = span.transformedStart + transformedRange.start;
316
+ let tsEnd = span.transformedStart + transformedRange.end;
317
+
318
+ // Push mappings for equal length mapping even if there are children;
319
+ // Need to support recursive `forNode()`s map monotonically
320
+ // smaller chunks of generated code to the same source region, e.g.
321
+ //
322
+ // source: `{{foo 1 2 3}}`
323
+ //
324
+ // This might produce something like:
325
+ //
326
+ // __glintDSL__.resolve('foo')(1, 2, 3)
327
+ //
328
+ // and depending on the nature of the type error, the diagnostic raised
329
+ // might originate from any of the following ranges:
330
+ //
331
+ // __glintDSL__.resolve('foo')(1, 2, 3)
332
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~
333
+ //
334
+ // __glintDSL__.resolve('foo')(1, 2, 3)
335
+ // ~~~~~
336
+ //
337
+ // __glintDSL__.resolve('foo')(1, 2, 3)
338
+ // ~~~~~~~~~
339
+ //
340
+ // We have some flexibility as to whether we want to map back to the entire
341
+ // `{{foo 1 2 3}}` region or perhaps just `1 2 3` or some other subset, but regardless,
342
+ // we need to support the case that multiple overlapping (or even non-overlapping) spans
343
+ // of generated code map back to the same source region.
344
+ const hbsLength = hbsEnd - hbsStart;
345
+ const tsLength = tsEnd - tsStart;
346
+
347
+ // Disregard the "null zone" mappings, i.e. cases where TS code maps to empty HBS code
348
+ const isNonNullMapping = hbsLength > 0 && tsLength > 0;
349
+
350
+ if (children.length > 0) {
351
+ if (isNonNullMapping) {
352
+ push(hbsStart, tsStart, 0, mapping.codeInformation);
353
+ }
354
+
355
+ mapping.children.forEach((child) => {
356
+ recurse(span, child);
357
+ });
358
+
359
+ // Disregard the "null zone" mappings, i.e. cases where TS code maps to empty HBS code
360
+ if (isNonNullMapping) {
361
+ push(hbsEnd, tsEnd, 0, mapping.codeInformation);
362
+ }
363
+ } else {
364
+ if (hbsLength === tsLength) {
365
+ push(hbsStart, tsStart, hbsLength, mapping.codeInformation);
366
+ } else {
367
+ // Disregard the "null zone" mappings, i.e. cases where TS code maps to empty HBS code
368
+ if (isNonNullMapping) {
369
+ push(hbsStart, tsStart, 0, mapping.codeInformation);
370
+ push(hbsEnd, tsEnd, 0, mapping.codeInformation);
371
+ }
372
+ }
373
+ }
374
+ };
375
+
376
+ this.correlatedSpans.forEach((span) => {
377
+ if (filenameFilter && span.originalFile.filename !== filenameFilter) {
378
+ return;
379
+ }
380
+
381
+ if (span.glimmerAstMapping) {
382
+ // this span is transformation from HBS to TS (either the replaced contents
383
+ // within `<template>` tags in a .gts file
384
+ recurse(span, span.glimmerAstMapping);
385
+ } else {
386
+ // This span contains untransformed TS content (because it comes
387
+ // from a region of source code that is already TS, e.g. the top of a .gts file,
388
+ // outside of any `<template>` tags). Because there's no transformation,
389
+ // we expect these to be the same length (in fact, they
390
+ // should be the same string entirely)
391
+
392
+ // This assertion seemed valid when parsing .gts files with extracted hbs in <template> tags,
393
+ // but when parsing solo .hbs files in loose mode there were cases where, e.g.,
394
+ // originalLength == 0 and transformLength == 1;
395
+ // assert(
396
+ // span.originalLength === span.transformedLength,
397
+ // 'span length mismatch for untransformed content'
398
+ // );
399
+
400
+ if (span.originalLength === span.transformedLength) {
401
+ // TODO: audit usage of `codeFeatures.all` here: https://github.com/typed-ember/glint/issues/769
402
+ // There are cases where we need to be disabling certain features to prevent, e.g., navigation
403
+ // that targets an "in-between" piece of generated code.
404
+ push(span.originalStart, span.transformedStart, span.originalLength, {
405
+ ...codeFeatures.all,
406
+
407
+ // This enables symbol/outline info for the transformed TS to appear in for the .gts file.
408
+ structure: true,
409
+ });
410
+ }
411
+ }
412
+ });
413
+
414
+ return codeMappings;
415
+ }
416
+
417
+ templateSymbols(): Array<TemplateSymbol> {
418
+ const result: Array<TemplateSymbol> = [];
419
+ this.correlatedSpans.forEach((span) => {
420
+ if (span.glimmerAstMapping) {
421
+ // This is a template span
422
+ result.push({
423
+ start: span.originalStart,
424
+ end: span.originalStart + span.originalLength,
425
+ startTagEnd: span.originalStart + span.originalLength,
426
+ });
427
+ }
428
+ });
429
+ return result;
430
+ }
431
+ }
@@ -0,0 +1,24 @@
1
+ import type ts from 'typescript';
2
+ import { SourceFile } from './template/transformed-module.js';
3
+
4
+ export type TSLib = typeof ts;
5
+
6
+ export function unreachable(value: never, message = 'unreachable code'): never {
7
+ throw new Error(`[ember-tsc] Internal error: ${message}`);
8
+ }
9
+
10
+ export function assert(
11
+ test: unknown,
12
+ message: string | (() => string) = 'Internal error',
13
+ ): asserts test {
14
+ if (test == null || test === false) {
15
+ throw new Error(typeof message === 'string' ? message : message());
16
+ }
17
+ }
18
+
19
+ export function createSyntheticSourceFile(ts: TSLib, source: SourceFile): ts.SourceFile {
20
+ return Object.assign(ts.createSourceFile(source.filename, '', ts.ScriptTarget.Latest), {
21
+ text: source.contents,
22
+ end: source.contents.length,
23
+ });
24
+ }
@@ -0,0 +1,108 @@
1
+ import { LanguagePlugin } from '@volar/language-core';
2
+ import type ts from 'typescript';
3
+ import { URI } from 'vscode-uri';
4
+ import { GlintConfig } from '../index.js';
5
+ import { VirtualGtsCode } from './gts-virtual-code.js';
6
+ export type TS = typeof ts;
7
+
8
+ /**
9
+ * Create a [Volar](https://volarjs.dev) language plugin to support
10
+ *
11
+ * - .gts/.gjs files (the `ember-template-imports` environment)
12
+ */
13
+ export function createEmberLanguagePlugin<T extends URI | string>(
14
+ glintConfig: GlintConfig,
15
+ { clientId }: { clientId?: string } = {},
16
+ ): LanguagePlugin<T> {
17
+ return {
18
+ /**
19
+ * For files that are not opened in the IDE, the language ID will not be provided
20
+ * to the language server, so a hook is needed to parse the language ID of files
21
+ * that are known extension but not opened in the IDE.
22
+ *
23
+ * In other words, clients like VSCode and other editors are in charge of determining
24
+ * the language ID and passing it in, but the language ID isn't available in other
25
+ * contexts, in which case this hook is called to determine it for a file based on its
26
+ * extension.
27
+ */
28
+ getLanguageId(fileNameOrUri) {
29
+ if (String(fileNameOrUri).endsWith('.gts')) {
30
+ return 'glimmer-ts';
31
+ }
32
+ if (String(fileNameOrUri).endsWith('.gjs')) {
33
+ return 'glimmer-js';
34
+ }
35
+ },
36
+
37
+ createVirtualCode(scriptId: URI | string, languageId, snapshot, codegenContext) {
38
+ const scriptIdStr = String(scriptId);
39
+
40
+ if (
41
+ languageId === 'glimmer-ts' ||
42
+ languageId === 'glimmer-js' ||
43
+ languageId === 'typescript.glimmer' ||
44
+ languageId === 'javascript.glimmer'
45
+ ) {
46
+ return new VirtualGtsCode(glintConfig, snapshot, languageId, clientId);
47
+ }
48
+ },
49
+
50
+ typescript: {
51
+ extraFileExtensions: [
52
+ { extension: 'gts', isMixedContent: true, scriptKind: 7 satisfies ts.ScriptKind.Deferred },
53
+ { extension: 'gjs', isMixedContent: true, scriptKind: 7 satisfies ts.ScriptKind.Deferred },
54
+ ],
55
+
56
+ // Allow extension-less imports, e.g. `import Foo from './Foo`.
57
+ // Upstream Volar support for our extension-less use case was added here:
58
+ // https://github.com/volarjs/volar.js/pull/190
59
+ //
60
+ // NOTE: as of Mar 7, 2025, TS Plugin mode does not support extension-less imports.
61
+ // It's possible this could be fixed upstream but we should maybe not count on it.
62
+ //
63
+ // Tracking here: https://github.com/typed-ember/glint/issues/806
64
+ resolveHiddenExtensions: true,
65
+
66
+ // This is called when TS requests the file that we'll be typechecking, which in our case
67
+ // is the transformed Intermediate Representation of ths .gts with the <template> tags
68
+ // converted to type-checkable TS.
69
+ getServiceScript(rootVirtualCode) {
70
+ // The first embeddedCode is always the TS Intermediate Representation code
71
+ const transformedCode = rootVirtualCode.embeddedCodes?.[0];
72
+ if (!transformedCode) {
73
+ return;
74
+ }
75
+
76
+ switch (rootVirtualCode.languageId) {
77
+ case 'glimmer-ts':
78
+ case 'typescript.glimmer':
79
+ return {
80
+ code: transformedCode,
81
+ extension: '.ts',
82
+ scriptKind: 3 satisfies ts.ScriptKind.TS,
83
+ };
84
+ case 'glimmer-js':
85
+ case 'javascript.glimmer':
86
+ return {
87
+ code: transformedCode,
88
+ extension: '.js',
89
+ scriptKind: 1 satisfies ts.ScriptKind.JS,
90
+ };
91
+ default:
92
+ throw new Error(`getScript: Unexpected languageId: ${rootVirtualCode.languageId}`);
93
+ }
94
+ },
95
+
96
+ resolveLanguageServiceHost(host) {
97
+ return {
98
+ ...host,
99
+ getCompilationSettings: () => ({
100
+ ...host.getCompilationSettings(),
101
+ // Always allow JS for type checking.
102
+ allowJs: true,
103
+ }),
104
+ };
105
+ },
106
+ },
107
+ };
108
+ }