@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,372 @@
1
+ import { GlintEmitMetadata } from '@glint/ember-tsc/config-types';
2
+ import * as path from 'node:path';
3
+ import type ts from 'typescript';
4
+ import { GlintEnvironment } from '../../config/index.js';
5
+ import { assert, TSLib } from '../util.js';
6
+
7
+ import { CorrelatedSpansResult, PartialCorrelatedSpan } from './inlining/index.js';
8
+ import { calculateTaggedTemplateSpans } from './inlining/tagged-strings.js';
9
+ import TransformedModule, {
10
+ CorrelatedSpan,
11
+ Directive,
12
+ SourceFile,
13
+ TransformError,
14
+ } from './transformed-module.js';
15
+
16
+ /**
17
+ * Input to the process of rewriting a template, containing:
18
+ * script: the backing JS/TS module for a component, which may contain
19
+ * embedded templates depending on the environment
20
+ */
21
+ export type RewriteInput = { script: SourceFile };
22
+
23
+ /**
24
+ * Given the script and/or template that together comprise a component module,
25
+ * returns a `TransformedModule` representing the combined result, with the
26
+ * template(s), either alongside or inline, rewritten into equivalent TypeScript
27
+ * in terms of the active glint environment's exported types.
28
+ *
29
+ * May return `null` if an unrecoverable parse error occurs or if there is
30
+ * no transformation to be done.
31
+ */
32
+ export function rewriteModule(
33
+ ts: TSLib,
34
+ { script }: RewriteInput,
35
+ environment: GlintEnvironment,
36
+ clientId?: string,
37
+ ): TransformedModule | null {
38
+ let { errors, directives, partialSpans } = calculateCorrelatedSpans(
39
+ ts,
40
+ script,
41
+ environment,
42
+ clientId,
43
+ );
44
+
45
+ if (!partialSpans.length && !errors.length) {
46
+ return null;
47
+ }
48
+
49
+ let sparseSpans = completeCorrelatedSpans(partialSpans);
50
+ let { contents, correlatedSpans } = calculateTransformedSource(script, sparseSpans, directives);
51
+
52
+ return new TransformedModule(contents, errors, directives, correlatedSpans, script.filename);
53
+ }
54
+
55
+ /**
56
+ * Locates any embedded templates in the given AST and returns a corresponding
57
+ * `PartialCorrelatedSpan` for each, as well as any errors encountered. These
58
+ * spans are then used in `rewriteModule` above to calculate the full set of
59
+ * source-to-source location information as well as the final transformed source
60
+ * string.
61
+ */
62
+ function calculateCorrelatedSpans(
63
+ ts: TSLib,
64
+ script: SourceFile,
65
+ environment: GlintEnvironment,
66
+ clientId?: string,
67
+ ): CorrelatedSpansResult {
68
+ let directives: Array<Directive> = [];
69
+ let errors: Array<TransformError> = [];
70
+
71
+ let partialSpans: Array<PartialCorrelatedSpan> = [];
72
+
73
+ if (clientId === 'tsserver-plugin') {
74
+ partialSpans.push(generateAutoImportAnchor(script));
75
+ }
76
+
77
+ let { ast, emitMetadata, error } = parseScript(ts, script, environment);
78
+
79
+ if (error) {
80
+ if (typeof error === 'string') {
81
+ errors.push({
82
+ message: error,
83
+ location: { start: 0, end: script.contents.length - 1 },
84
+ source: script,
85
+ });
86
+ } else if ('isContentTagError' in error && error.isContentTagError) {
87
+ // these lines exclude the line with the error, because
88
+ // adding the column offset will get us on to the line with the error
89
+ let lines = script.contents.split('\n').slice(0, error.line);
90
+ let start = lines.reduce((sum, line) => sum + line.length, 0) + error.column - 1;
91
+ let end = start + 1;
92
+
93
+ errors.push({
94
+ isContentTagError: true,
95
+ // we have to show the "help" because content-tag has different line numbers
96
+ // than we are able to discern ourselves.
97
+ message: error.message + '\n\n' + error.help,
98
+ source: script,
99
+ location: {
100
+ start,
101
+ end,
102
+ },
103
+ });
104
+ }
105
+
106
+ // We've hit a parsing error, so we need to immediately return as the parsed
107
+ // document must be correct before we can continue.
108
+ return { errors, directives, partialSpans };
109
+ }
110
+
111
+ ts.transform(ast, [
112
+ (context) =>
113
+ function visit<T extends ts.Node>(node: T): T {
114
+ // Here we look for ```hbs``` tagged template expressions, originally introduced
115
+ // in the now-removed GlimmerX environment. We can consider getting rid of this, but
116
+ // then again there are still some use cases in the wild (e.g. Glimmer Next / GXT)
117
+ // where have tagged templates closing over outer scope is desirable:
118
+ // https://github.com/lifeart/glimmer-next/tree/master/glint-environment-gxt
119
+ // https://discord.com/channels/480462759797063690/717767358743183412/1259061848632721480
120
+ if (ts.isTaggedTemplateExpression(node)) {
121
+ let meta = emitMetadata.get(node);
122
+ let result = calculateTaggedTemplateSpans(ts, node, meta, script, environment);
123
+
124
+ directives.push(...result.directives);
125
+ errors.push(...result.errors);
126
+ partialSpans.push(...result.partialSpans);
127
+ } else if (ts.isModuleDeclaration(node)) {
128
+ // don't traverse into declare module
129
+ return node;
130
+ }
131
+
132
+ return ts.visitEachChild(node, visit, context);
133
+ },
134
+ ]);
135
+
136
+ return { errors, directives, partialSpans };
137
+ }
138
+
139
+ type ParseError =
140
+ | string
141
+ | {
142
+ isContentTagError: true;
143
+ message: string;
144
+ line: number;
145
+ column: number;
146
+ file: string;
147
+ help: string;
148
+ raw: string;
149
+ };
150
+
151
+ type ParseResult = {
152
+ ast: ts.SourceFile;
153
+ emitMetadata: WeakMap<ts.Node, GlintEmitMetadata>;
154
+ error?: ParseError;
155
+ };
156
+
157
+ function parseScript(ts: TSLib, script: SourceFile, environment: GlintEnvironment): ParseResult {
158
+ let { filename, contents } = script;
159
+ let extension = path.extname(filename);
160
+ let emitMetadata = new WeakMap<ts.Node, GlintEmitMetadata>();
161
+ let setEmitMetadata = (node: ts.Node, data: GlintEmitMetadata): void =>
162
+ void emitMetadata.set(node, Object.assign(emitMetadata.get(node) ?? {}, data));
163
+
164
+ let { preprocess, transform } = environment.getConfigForExtension(extension) ?? {};
165
+
166
+ let original: {
167
+ contents: string;
168
+ data?: {
169
+ // SAFETY: type exists elsewhere (the environments)
170
+ templateLocations: any[];
171
+ };
172
+ } = { contents, data: { templateLocations: [] } };
173
+
174
+ let preprocessed = original;
175
+ let error: ParseError | undefined;
176
+
177
+ try {
178
+ preprocessed = preprocess?.(contents, filename) ?? original;
179
+ } catch (e) {
180
+ error = parseError(e, filename);
181
+ }
182
+
183
+ let ast = ts.createSourceFile(
184
+ filename,
185
+ // contents will be transformed and placeholder'd content
186
+ // or, in the event of a parse error, the original content
187
+ // in which case, TS will report a ton of errors about some goofy syntax.
188
+ // We'll want to ignore all of that and only display our parsing error from content-tag.
189
+ preprocessed.contents,
190
+ ts.ScriptTarget.Latest,
191
+ true, // setParentNodes
192
+ );
193
+
194
+ // Only transform if we don't have a parse error
195
+ if (!error && transform) {
196
+ let { transformed } = ts.transform(ast, [
197
+ (context) => transform!(preprocessed.data, { ts, context, setEmitMetadata }),
198
+ ]);
199
+
200
+ assert(transformed.length === 1 && ts.isSourceFile(transformed[0]));
201
+ ast = transformed[0];
202
+ }
203
+
204
+ return { ast, emitMetadata, error };
205
+ }
206
+
207
+ function parseError(e: unknown, filename: string): ParseError {
208
+ if (typeof e === 'object' && e !== null) {
209
+ // Parse Errors from the rust parser
210
+ if ('source_code' in e) {
211
+ // We remove the blank links in the error because swc
212
+ // pads errors with a leading and trailing blank line.
213
+ // the error is typically meant for the terminal, so making it
214
+ // stand out a bit more is a good, but that's more a presentation
215
+ // concern than just pure error information (which is what we need).
216
+ // @ts-expect-error object / property narrowing isn't available until TS 5.1
217
+ let lines = e.source_code.split('\n').filter(Boolean);
218
+ // Example:
219
+ // ' × Unexpected eof'
220
+ // ' ╭─[/home/nullvoxpopuli/Development/OpenSource/glint/test-packages/ts-template-imports-app/src/index.gts:6:1]'
221
+ // ' 6 │ '
222
+ // ' 7 │ export const X = <tem'
223
+ // ' ╰────'
224
+ let raw = lines.join('\n');
225
+ let message = lines[0].replace('×', '').trim();
226
+ let info = lines[1];
227
+ // a filename may have numbers in it, so we want to remove the filename
228
+ // before regex searching for numbers at the end of this line
229
+ let strippedInfo = info.replace(filename, '');
230
+ let matches = [...strippedInfo.matchAll(/\d+/g)];
231
+ let line = parseInt(matches[0][0], 10);
232
+ let column = parseInt(matches[1][0], 10);
233
+ // The help omits the original file name, because TS will provide that.
234
+ let help = lines.slice(2).join('\n');
235
+
236
+ return {
237
+ isContentTagError: true,
238
+ raw,
239
+ message,
240
+ line,
241
+ column,
242
+ file: filename,
243
+ help,
244
+ };
245
+ }
246
+ }
247
+
248
+ return `${e}`;
249
+ }
250
+
251
+ /**
252
+ * Given a sparse `CorrelatedSpan` array and the original source for a module,
253
+ * returns the resulting full transformed source string for that module, as
254
+ * well as a filled-in array of correlated spans that includes chunks of the
255
+ * original source that were not transformed.
256
+ */
257
+ function calculateTransformedSource(
258
+ originalFile: SourceFile,
259
+ sparseSpans: Array<CorrelatedSpan>,
260
+ directives: Array<Directive>,
261
+ ): { contents: string; correlatedSpans: Array<CorrelatedSpan> } {
262
+ let correlatedSpans: Array<CorrelatedSpan> = [];
263
+ let originalOffset = 0;
264
+ let transformedOffset = 0;
265
+
266
+ for (let span of sparseSpans) {
267
+ let interstitial = originalFile.contents.slice(originalOffset, span.insertionPoint);
268
+
269
+ if (interstitial.length) {
270
+ correlatedSpans.push({
271
+ originalFile,
272
+ originalStart: originalOffset,
273
+ originalLength: interstitial.length,
274
+ insertionPoint: originalOffset,
275
+ transformedStart: transformedOffset,
276
+ transformedLength: interstitial.length,
277
+ transformedSource: interstitial,
278
+ });
279
+ }
280
+
281
+ correlatedSpans.push(span);
282
+
283
+ transformedOffset += interstitial.length + span.transformedLength;
284
+ originalOffset +=
285
+ interstitial.length + (span.originalFile === originalFile ? span.originalLength : 0);
286
+ }
287
+
288
+ let trailingContent = originalFile.contents.slice(originalOffset);
289
+
290
+ const trailingSpan: CorrelatedSpan = {
291
+ originalFile,
292
+ originalStart: originalOffset,
293
+ originalLength: trailingContent.length + 1,
294
+ insertionPoint: originalOffset,
295
+ transformedStart: transformedOffset,
296
+ transformedLength: trailingContent.length + 1,
297
+ transformedSource: trailingContent,
298
+ };
299
+
300
+ correlatedSpans.push(trailingSpan);
301
+
302
+ const placeholderContent = '\n\n// @ts-expect-error placeholder for glint-expect-error\n;\n';
303
+
304
+ for (let directive of directives) {
305
+ if (directive.kind === 'expect-error') {
306
+ correlatedSpans.push({
307
+ originalFile,
308
+ originalStart: directive.location.start,
309
+ originalLength: directive.location.end - directive.location.start,
310
+ insertionPoint: originalOffset + trailingContent.length,
311
+ transformedSource: placeholderContent,
312
+ transformedStart: transformedOffset + trailingContent.length,
313
+ transformedLength: placeholderContent.length,
314
+ });
315
+ }
316
+ }
317
+
318
+ return {
319
+ contents: correlatedSpans.map((span) => span.transformedSource).join(''),
320
+ correlatedSpans,
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Given an array of `PartialCorrelatedSpan`s for a file, calculates
326
+ * their `transformedLength` and `transformedStart` values, resulting
327
+ * in full `CorrelatedSpan`s.
328
+ */
329
+ function completeCorrelatedSpans(
330
+ partialSpans: Array<PartialCorrelatedSpan>,
331
+ ): Array<CorrelatedSpan> {
332
+ let correlatedSpans: Array<CorrelatedSpan> = [];
333
+
334
+ for (let i = 0; i < partialSpans.length; i++) {
335
+ let current = partialSpans[i];
336
+ let transformedLength = current.transformedSource.length;
337
+ let transformedStart = current.insertionPoint;
338
+ if (i > 0) {
339
+ let previous = correlatedSpans[i - 1];
340
+ transformedStart =
341
+ previous.transformedStart +
342
+ previous.transformedSource.length +
343
+ (current.insertionPoint - previous.insertionPoint - previous.originalLength);
344
+ }
345
+
346
+ correlatedSpans.push({ ...current, transformedStart, transformedLength });
347
+ }
348
+
349
+ return correlatedSpans;
350
+ }
351
+
352
+ /**
353
+ * Insert a triple-slash reference directive to the top of the transformed file which provides
354
+ * an anchor/insertion point for auto-imports. This fixes an issue where auto-imports
355
+ * don't work for the first import in a file.
356
+ *
357
+ * Because we don't provide any mapping information to Volar, the internal error that
358
+ * this generates is quietly swallowed when Volar reverse source-maps the error back to
359
+ * .gts/.gjs. This is hacky but I don't know of any other (non-upstream) fix for this.
360
+ *
361
+ * Potential upstream Volar fix for this:
362
+ * https://github.com/volarjs/volar.js/pull/281
363
+ */
364
+ function generateAutoImportAnchor(script: SourceFile): PartialCorrelatedSpan {
365
+ return {
366
+ originalFile: script,
367
+ originalStart: 0,
368
+ originalLength: 0,
369
+ insertionPoint: 0,
370
+ transformedSource: '/// <reference types="__GLINT_AUTO_IMPORT_ANCHOR__" />\n',
371
+ };
372
+ }
@@ -0,0 +1,34 @@
1
+ import { assert } from '../util.js';
2
+
3
+ /**
4
+ * A `ScopeStack` is used while traversing a template
5
+ * to track what identifiers are currently in scope.
6
+ */
7
+ export default class ScopeStack {
8
+ private stack: Array<Set<string>>;
9
+
10
+ public constructor(identifiers: string[]) {
11
+ this.stack = [new Set(identifiers)];
12
+ }
13
+
14
+ public push(identifiers: Array<string>): void {
15
+ let scope = new Set(this.top);
16
+ for (let identifier of identifiers) {
17
+ scope.add(identifier);
18
+ }
19
+ this.stack.unshift(scope);
20
+ }
21
+
22
+ public pop(): void {
23
+ assert(this.stack.length > 1);
24
+ this.stack.shift();
25
+ }
26
+
27
+ public hasBinding(identifier: string): boolean {
28
+ return this.top.has(identifier);
29
+ }
30
+
31
+ private get top(): Set<string> {
32
+ return this.stack[0];
33
+ }
34
+ }