@glint/ember-tsc 1.0.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.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -0
  3. package/bin/ember-tsc.js +4 -0
  4. package/bin/glint-language-server.js +2 -0
  5. package/lib/cli/run-volar-tsc.d.ts +2 -0
  6. package/lib/cli/run-volar-tsc.d.ts.map +1 -0
  7. package/lib/cli/run-volar-tsc.js +30 -0
  8. package/lib/cli/run-volar-tsc.js.map +1 -0
  9. package/lib/config/config.d.ts +15 -0
  10. package/lib/config/config.d.ts.map +1 -0
  11. package/lib/config/config.js +21 -0
  12. package/lib/config/config.js.map +1 -0
  13. package/lib/config/environment.d.ts +26 -0
  14. package/lib/config/environment.d.ts.map +1 -0
  15. package/lib/config/environment.js +96 -0
  16. package/lib/config/environment.js.map +1 -0
  17. package/lib/config/index.d.ts +17 -0
  18. package/lib/config/index.d.ts.map +1 -0
  19. package/lib/config/index.js +26 -0
  20. package/lib/config/index.js.map +1 -0
  21. package/lib/config/loader.d.ts +25 -0
  22. package/lib/config/loader.d.ts.map +1 -0
  23. package/lib/config/loader.js +110 -0
  24. package/lib/config/loader.js.map +1 -0
  25. package/lib/config/types.cjs +3 -0
  26. package/lib/config/types.cjs.map +1 -0
  27. package/lib/config/types.d.cts +60 -0
  28. package/lib/config/types.d.cts.map +1 -0
  29. package/lib/environment-ember-template-imports/-private/environment/common.d.ts +13 -0
  30. package/lib/environment-ember-template-imports/-private/environment/common.d.ts.map +1 -0
  31. package/lib/environment-ember-template-imports/-private/environment/common.js +2 -0
  32. package/lib/environment-ember-template-imports/-private/environment/common.js.map +1 -0
  33. package/lib/environment-ember-template-imports/-private/environment/index.d.ts +3 -0
  34. package/lib/environment-ember-template-imports/-private/environment/index.d.ts.map +1 -0
  35. package/lib/environment-ember-template-imports/-private/environment/index.js +76 -0
  36. package/lib/environment-ember-template-imports/-private/environment/index.js.map +1 -0
  37. package/lib/environment-ember-template-imports/-private/environment/preprocess.d.ts +4 -0
  38. package/lib/environment-ember-template-imports/-private/environment/preprocess.d.ts.map +1 -0
  39. package/lib/environment-ember-template-imports/-private/environment/preprocess.js +73 -0
  40. package/lib/environment-ember-template-imports/-private/environment/preprocess.js.map +1 -0
  41. package/lib/environment-ember-template-imports/-private/environment/transform.d.ts +4 -0
  42. package/lib/environment-ember-template-imports/-private/environment/transform.d.ts.map +1 -0
  43. package/lib/environment-ember-template-imports/-private/environment/transform.js +134 -0
  44. package/lib/environment-ember-template-imports/-private/environment/transform.js.map +1 -0
  45. package/lib/index.d.ts +7 -0
  46. package/lib/index.d.ts.map +1 -0
  47. package/lib/index.js +6 -0
  48. package/lib/index.js.map +1 -0
  49. package/lib/plugins/g-compiler-errors.d.ts +12 -0
  50. package/lib/plugins/g-compiler-errors.d.ts.map +1 -0
  51. package/lib/plugins/g-compiler-errors.js +58 -0
  52. package/lib/plugins/g-compiler-errors.js.map +1 -0
  53. package/lib/plugins/g-template-tag-symbols.d.ts +11 -0
  54. package/lib/plugins/g-template-tag-symbols.d.ts.map +1 -0
  55. package/lib/plugins/g-template-tag-symbols.js +48 -0
  56. package/lib/plugins/g-template-tag-symbols.js.map +1 -0
  57. package/lib/plugins/utils.d.ts +25 -0
  58. package/lib/plugins/utils.d.ts.map +1 -0
  59. package/lib/plugins/utils.js +63 -0
  60. package/lib/plugins/utils.js.map +1 -0
  61. package/lib/transform/diagnostics/augmentation.d.ts +4 -0
  62. package/lib/transform/diagnostics/augmentation.d.ts.map +1 -0
  63. package/lib/transform/diagnostics/augmentation.js +223 -0
  64. package/lib/transform/diagnostics/augmentation.js.map +1 -0
  65. package/lib/transform/diagnostics/index.d.ts +5 -0
  66. package/lib/transform/diagnostics/index.d.ts.map +1 -0
  67. package/lib/transform/diagnostics/index.js +2 -0
  68. package/lib/transform/diagnostics/index.js.map +1 -0
  69. package/lib/transform/index.d.ts +4 -0
  70. package/lib/transform/index.d.ts.map +1 -0
  71. package/lib/transform/index.js +2 -0
  72. package/lib/transform/index.js.map +1 -0
  73. package/lib/transform/template/code-features.d.ts +30 -0
  74. package/lib/transform/template/code-features.d.ts.map +1 -0
  75. package/lib/transform/template/code-features.js +26 -0
  76. package/lib/transform/template/code-features.js.map +1 -0
  77. package/lib/transform/template/glimmer-ast-mapping-tree.d.ts +80 -0
  78. package/lib/transform/template/glimmer-ast-mapping-tree.d.ts.map +1 -0
  79. package/lib/transform/template/glimmer-ast-mapping-tree.js +132 -0
  80. package/lib/transform/template/glimmer-ast-mapping-tree.js.map +1 -0
  81. package/lib/transform/template/inlining/index.d.ts +16 -0
  82. package/lib/transform/template/inlining/index.d.ts.map +1 -0
  83. package/lib/transform/template/inlining/index.js +21 -0
  84. package/lib/transform/template/inlining/index.js.map +1 -0
  85. package/lib/transform/template/inlining/tagged-strings.d.ts +8 -0
  86. package/lib/transform/template/inlining/tagged-strings.d.ts.map +1 -0
  87. package/lib/transform/template/inlining/tagged-strings.js +140 -0
  88. package/lib/transform/template/inlining/tagged-strings.js.map +1 -0
  89. package/lib/transform/template/map-template-contents.d.ts +121 -0
  90. package/lib/transform/template/map-template-contents.d.ts.map +1 -0
  91. package/lib/transform/template/map-template-contents.js +287 -0
  92. package/lib/transform/template/map-template-contents.js.map +1 -0
  93. package/lib/transform/template/rewrite-module.d.ts +22 -0
  94. package/lib/transform/template/rewrite-module.d.ts.map +1 -0
  95. package/lib/transform/template/rewrite-module.js +265 -0
  96. package/lib/transform/template/rewrite-module.js.map +1 -0
  97. package/lib/transform/template/scope-stack.d.ts +13 -0
  98. package/lib/transform/template/scope-stack.d.ts.map +1 -0
  99. package/lib/transform/template/scope-stack.js +28 -0
  100. package/lib/transform/template/scope-stack.js.map +1 -0
  101. package/lib/transform/template/template-to-typescript.d.ts +19 -0
  102. package/lib/transform/template/template-to-typescript.d.ts.map +1 -0
  103. package/lib/transform/template/template-to-typescript.js +1095 -0
  104. package/lib/transform/template/template-to-typescript.js.map +1 -0
  105. package/lib/transform/template/transformed-module.d.ts +111 -0
  106. package/lib/transform/template/transformed-module.d.ts.map +1 -0
  107. package/lib/transform/template/transformed-module.js +287 -0
  108. package/lib/transform/template/transformed-module.js.map +1 -0
  109. package/lib/transform/util.d.ts +7 -0
  110. package/lib/transform/util.d.ts.map +1 -0
  111. package/lib/transform/util.js +15 -0
  112. package/lib/transform/util.js.map +1 -0
  113. package/lib/volar/ember-language-plugin.d.ts +14 -0
  114. package/lib/volar/ember-language-plugin.d.ts.map +1 -0
  115. package/lib/volar/ember-language-plugin.js +91 -0
  116. package/lib/volar/ember-language-plugin.js.map +1 -0
  117. package/lib/volar/gts-virtual-code.d.ts +83 -0
  118. package/lib/volar/gts-virtual-code.d.ts.map +1 -0
  119. package/lib/volar/gts-virtual-code.js +210 -0
  120. package/lib/volar/gts-virtual-code.js.map +1 -0
  121. package/lib/volar/language-server.d.ts +2 -0
  122. package/lib/volar/language-server.d.ts.map +1 -0
  123. package/lib/volar/language-server.js +214 -0
  124. package/lib/volar/language-server.js.map +1 -0
  125. package/lib/volar/script-snapshot.d.ts +17 -0
  126. package/lib/volar/script-snapshot.d.ts.map +1 -0
  127. package/lib/volar/script-snapshot.js +24 -0
  128. package/lib/volar/script-snapshot.js.map +1 -0
  129. package/package.json +104 -0
  130. package/src/cli/run-volar-tsc.ts +36 -0
  131. package/src/config/config.ts +33 -0
  132. package/src/config/environment.ts +128 -0
  133. package/src/config/index.ts +30 -0
  134. package/src/config/loader.ts +143 -0
  135. package/src/config/types.cts +85 -0
  136. package/src/environment-ember-template-imports/-private/environment/common.ts +14 -0
  137. package/src/environment-ember-template-imports/-private/environment/index.ts +83 -0
  138. package/src/environment-ember-template-imports/-private/environment/preprocess.ts +90 -0
  139. package/src/environment-ember-template-imports/-private/environment/transform.ts +202 -0
  140. package/src/index.ts +9 -0
  141. package/src/plugins/g-compiler-errors.ts +67 -0
  142. package/src/plugins/g-template-tag-symbols.ts +54 -0
  143. package/src/plugins/utils.ts +86 -0
  144. package/src/transform/diagnostics/augmentation.ts +333 -0
  145. package/src/transform/diagnostics/index.ts +5 -0
  146. package/src/transform/index.ts +4 -0
  147. package/src/transform/template/code-features.ts +30 -0
  148. package/src/transform/template/glimmer-ast-mapping-tree.ts +173 -0
  149. package/src/transform/template/inlining/index.ts +33 -0
  150. package/src/transform/template/inlining/tagged-strings.ts +187 -0
  151. package/src/transform/template/map-template-contents.ts +501 -0
  152. package/src/transform/template/rewrite-module.ts +372 -0
  153. package/src/transform/template/scope-stack.ts +34 -0
  154. package/src/transform/template/template-to-typescript.ts +1476 -0
  155. package/src/transform/template/transformed-module.ts +431 -0
  156. package/src/transform/util.ts +24 -0
  157. package/src/volar/ember-language-plugin.ts +108 -0
  158. package/src/volar/gts-virtual-code.ts +249 -0
  159. package/src/volar/language-server.ts +250 -0
  160. package/src/volar/script-snapshot.ts +27 -0
  161. package/types/-private/dsl/globals.d.ts +204 -0
  162. package/types/-private/dsl/index.d.ts +50 -0
  163. package/types/-private/dsl/integration-declarations.d.ts +143 -0
  164. package/types/-private/intrinsics/action.d.ts +45 -0
  165. package/types/-private/intrinsics/concat.d.ts +6 -0
  166. package/types/-private/intrinsics/each-in.d.ts +24 -0
  167. package/types/-private/intrinsics/each.d.ts +17 -0
  168. package/types/-private/intrinsics/fn.d.ts +44 -0
  169. package/types/-private/intrinsics/get.d.ts +31 -0
  170. package/types/-private/intrinsics/input.d.ts +24 -0
  171. package/types/-private/intrinsics/link-to.d.ts +31 -0
  172. package/types/-private/intrinsics/log.d.ts +6 -0
  173. package/types/-private/intrinsics/mount.d.ts +9 -0
  174. package/types/-private/intrinsics/mut.d.ts +14 -0
  175. package/types/-private/intrinsics/on.d.ts +21 -0
  176. package/types/-private/intrinsics/outlet.d.ts +8 -0
  177. package/types/-private/intrinsics/textarea.d.ts +16 -0
  178. package/types/-private/intrinsics/unbound.d.ts +10 -0
  179. package/types/-private/intrinsics/unique-id.d.ts +5 -0
  180. package/types/globals/index.d.ts +3 -0
  181. package/types/silent-error.d.ts +4 -0
@@ -0,0 +1,333 @@
1
+ import type ts from 'typescript';
2
+ import GlimmerASTMappingTree, { MappingSource } from '../template/glimmer-ast-mapping-tree.js';
3
+ import TransformedModule from '../template/transformed-module.js';
4
+ import { Diagnostic } from './index.js';
5
+
6
+ export function augmentDiagnostics<T extends Diagnostic>(
7
+ transformedModule: TransformedModule,
8
+ diagnostics: T[],
9
+ ): T[] {
10
+ const mappingForDiagnostic = (diagnostic: ts.Diagnostic): GlimmerASTMappingTree[] => {
11
+ if (!transformedModule) {
12
+ return [];
13
+ }
14
+
15
+ if (!diagnostic.start || !diagnostic.length) {
16
+ return [];
17
+ }
18
+
19
+ const start = diagnostic.start;
20
+ const end = start + diagnostic.length;
21
+
22
+ return transformedModule.getExactTransformedRanges(
23
+ transformedModule.originalFileName,
24
+ start,
25
+ end,
26
+ );
27
+ };
28
+
29
+ const augmentedDiagnostics: Diagnostic[] = [];
30
+
31
+ for (const diagnostic of diagnostics) {
32
+ const augmentedDiagnostic = rewriteMessageText(diagnostic, mappingForDiagnostic);
33
+
34
+ augmentedDiagnostics.push(augmentedDiagnostic);
35
+ }
36
+
37
+ // @ts-expect-error not sure how to fix
38
+ return augmentedDiagnostics;
39
+ }
40
+
41
+ type DiagnosticHandler<T extends Diagnostic> = (
42
+ diagnostic: T,
43
+ mapping: GlimmerASTMappingTree,
44
+ ) => T | undefined;
45
+
46
+ function rewriteMessageText(
47
+ diagnostic: Diagnostic,
48
+ mappingGetter: (diagnostic: Diagnostic) => GlimmerASTMappingTree[],
49
+ ): Diagnostic {
50
+ const handler = diagnosticHandlers[diagnostic.code?.toString() ?? ''];
51
+ if (!handler) {
52
+ return diagnostic;
53
+ }
54
+
55
+ for (let mapping of mappingGetter(diagnostic)) {
56
+ const augmentedDiagnostic = handler(diagnostic, mapping);
57
+ if (augmentedDiagnostic) {
58
+ return augmentedDiagnostic;
59
+ }
60
+ }
61
+
62
+ return diagnostic;
63
+ }
64
+
65
+ const diagnosticHandlers: Record<string, DiagnosticHandler<Diagnostic> | undefined> = {
66
+ '2307': checkGlintLibImports, // TS2307: An import cannot be found.
67
+ '2322': checkAssignabilityError, // TS2322: Type 'X' is not assignable to type 'Y'.
68
+ '2345': checkAssignabilityError, // TS2345: Argument of type 'X' is not assignable to parameter of type 'Y'.
69
+ '2554': noteNamedArgsAffectArity, // TS2554: Expected N arguments, but got M.
70
+ '2555': noteNamedArgsAffectArity, // TS2555: Expected at least N arguments, but got M.
71
+ '2769': checkResolveError, // TS2769: No overload matches this call.
72
+ '4111': checkIndexAccessError, // TS4111: Property 'x' comes from an index signature, so it must be accessed with ['x'].
73
+ '7053': checkImplicitAnyError, // TS7053: Element implicitly has an 'any' type because expression of type '"X"' can't be used to index type 'Y'.
74
+ };
75
+
76
+ const bindHelpers = ['component', 'helper', 'modifier'];
77
+
78
+ function checkGlintLibImports(
79
+ diagnostic: Diagnostic,
80
+ _mapping: GlimmerASTMappingTree,
81
+ ): Diagnostic | undefined {
82
+ let messageText =
83
+ typeof diagnostic.messageText === 'string'
84
+ ? diagnostic.messageText
85
+ : diagnostic.messageText.messageText;
86
+
87
+ const typesModules = '@glint/ember-tsc/-private/dsl';
88
+
89
+ if (messageText.includes(typesModules)) {
90
+ return addGlintDetails(
91
+ diagnostic,
92
+ 'You appear to be using version 2 of the Glint VS Code extension ' +
93
+ '(or a v2 Glint plugin for another IDE), but your package.json still ' +
94
+ 'references version 1 Glint dependencies (or they are missing). You need to either upgrade your `@glint/core` ' +
95
+ 'and `@glint/template` project dependencies, or downgrade your VS Code Glint extension to version 1.x. ' +
96
+ 'Please see the Glint v2 release notes and upgrade guide for more information.',
97
+ );
98
+ } else {
99
+ return diagnostic;
100
+ }
101
+ }
102
+
103
+ function checkAssignabilityError(
104
+ diagnostic: Diagnostic,
105
+ mapping: GlimmerASTMappingTree,
106
+ ): Diagnostic | undefined {
107
+ let node = mapping.sourceNode;
108
+ let parentNode = mapping.parent?.sourceNode;
109
+ if (!parentNode) return;
110
+
111
+ if (parentNode.type === node.type) {
112
+ // For Volar, we added a few more artificial nestings / wrappings around Handlebars
113
+ // AST nodes to provide more hookpoints for TS diagnostics to correctly source-map
114
+ // back to .gts/.hbs source code. The result of this is that sometimes you have
115
+ // things like MustacheNodes being "parents" of MustacheNodes, which we try and
116
+ // detect here.
117
+ //
118
+ // This can go away if/when we refactor the `transformModule` code.
119
+ parentNode = mapping.parent?.parent?.sourceNode;
120
+
121
+ if (!parentNode) return;
122
+ }
123
+
124
+ if (
125
+ node.type === 'Identifier' &&
126
+ parentNode.type === 'AttrNode' &&
127
+ !/^(@|\.)/.test(parentNode.name)
128
+ ) {
129
+ // If the assignability issue is on an attribute name and it's not an `@arg`
130
+ // or `...attributes`, then it's an HTML attribute type issue.
131
+ return addGlintDetails(
132
+ diagnostic,
133
+ 'Only primitive values (see `AttrValue` in `@glint/template`) are assignable as HTML attributes. ' +
134
+ 'If you want to set an event listener, consider using the `{{on}}` modifier instead.',
135
+ );
136
+ } else if (
137
+ node.type === 'AttrNode' &&
138
+ parentNode.type === 'ElementNode' &&
139
+ !/^(@|\.)/.test(node.name)
140
+ ) {
141
+ // If the assignability issue is on an attribute name and it's not an `@arg`
142
+ // or `...attributes`, then it's an HTML attribute type issue.
143
+ return addGlintDetails(
144
+ diagnostic,
145
+ 'An Element must be specified in the component signature in order to pass in HTML attributes.',
146
+ );
147
+ } else if (
148
+ node.type === 'MustacheStatement' &&
149
+ (parentNode.type === 'Template' ||
150
+ parentNode.type === 'BlockStatement' ||
151
+ parentNode.type === 'ElementNode') &&
152
+ !(node.path.type === 'PathExpression' && node.path.original === 'yield')
153
+ ) {
154
+ // If we're looking at a top-level {{mustache}}, we first double check whether
155
+ // it's an attempted inline {{component 'foo'}} invocation...
156
+ if (node.path.type === 'PathExpression' && node.path.original === 'component') {
157
+ return addGlintDetails(
158
+ diagnostic,
159
+ `The {{component}} helper can't be used to directly invoke a component under Glint. ` +
160
+ `Consider first binding the result to a variable, e.g. ` +
161
+ `'{{#let (component 'component-name') as |ComponentName|}}' and then invoking it as ` +
162
+ `'<ComponentName @arg={{value}} />'.`,
163
+ );
164
+ }
165
+
166
+ // Otherwise, it's a DOM content type issue.
167
+ return addGlintDetails(
168
+ diagnostic,
169
+ 'Only primitive values and certain DOM objects (see `ContentValue` in `@glint/template`) are ' +
170
+ 'usable as top-level template content.',
171
+ );
172
+ } else if (
173
+ (mapping?.sourceNode.type === 'SubExpression' ||
174
+ mapping?.sourceNode.type === 'MustacheStatement') &&
175
+ mapping.sourceNode.path.type === 'PathExpression' &&
176
+ bindHelpers.includes(mapping.sourceNode.path.original)
177
+ ) {
178
+ // If we're looking at a binding helper subexpression like `(component ...)`, error messages
179
+ // may be very straightforward or may be horrendously complex when users start playing games
180
+ // with parametrized types, so we add a hint here.
181
+ let kind = mapping.sourceNode.path.original;
182
+ return {
183
+ ...diagnostic,
184
+ messageText: `Unable to pre-bind the given args to the given ${kind}. This likely indicates a type mismatch between its signature and the values you're passing.`,
185
+ };
186
+ }
187
+ }
188
+
189
+ function noteNamedArgsAffectArity(
190
+ diagnostic: Diagnostic,
191
+ mapping: GlimmerASTMappingTree,
192
+ ): Diagnostic | undefined {
193
+ // In normal template entity invocations, named args (if specified) are effectively
194
+ // passed as the final positional argument. Because of this, the reported "expected
195
+ // N arguments, but got M" message may appear to be off-by-one to the developer.
196
+
197
+ // Since this treatment of named args as a final "options hash" argument is user
198
+ // visible in cases like type errors, we can't just paper over this by subtracting
199
+ // 1 from the numbers in the message. Instead, if the invocation has named args, we
200
+ // explicitly note that they're effectively the final positional parameter.
201
+
202
+ let callNode = findAncestor(mapping, 'MustacheStatement', 'SubExpression');
203
+ if (callNode?.path.type === 'PathExpression' && callNode.path.original === 'yield') {
204
+ // Yield is directly transformed rather than being treated as a normal mustache
205
+ return;
206
+ }
207
+
208
+ let hasNamedArgs = !!callNode?.hash.pairs.length;
209
+ if (hasNamedArgs) {
210
+ let note =
211
+ 'Note that named args are passed together as a final argument, so they ' +
212
+ 'collectively increase the given arg count by 1.';
213
+
214
+ return {
215
+ ...diagnostic,
216
+ messageText: `${diagnostic.messageText} ${note}`,
217
+ };
218
+ }
219
+ }
220
+
221
+ function checkResolveError(
222
+ diagnostic: Diagnostic,
223
+ mapping: GlimmerASTMappingTree,
224
+ ): Diagnostic | undefined {
225
+ // The diagnostic might fall on a lone identifier or a full path; if the former,
226
+ // we need to traverse up through the path to find the true parent.
227
+ let sourceMapping = mapping.sourceNode.type === 'Identifier' ? mapping.parent : mapping;
228
+ let parentNode = sourceMapping?.parent?.sourceNode;
229
+
230
+ // If this error is on the first param to a {{component}} or other bind invocation, this means
231
+ // we either have a non-component value or a string that's missing from the registry.
232
+ if (
233
+ (parentNode?.type === 'SubExpression' || parentNode?.type === 'MustacheStatement') &&
234
+ parentNode.path.type === 'PathExpression' &&
235
+ bindHelpers.includes(parentNode.path.original) &&
236
+ parentNode.params[0] === sourceMapping?.sourceNode
237
+ ) {
238
+ let kind = parentNode.path.original;
239
+
240
+ if (sourceMapping.sourceNode.type === 'StringLiteral') {
241
+ return addGlintDetails(
242
+ diagnostic,
243
+ `Unknown ${kind} name '${sourceMapping.sourceNode.value}'. If this isn't a typo, you may be ` +
244
+ `missing a registry entry for this name; see the Template Registry page in the Glint ` +
245
+ `documentation for more details.`,
246
+ );
247
+ } else {
248
+ return addGlintDetails(
249
+ diagnostic,
250
+ `The type of this expression doesn't appear to be a valid value to pass the {{${kind}}} ` +
251
+ `helper. If possible, you may need to give the expression a narrower type, ` +
252
+ `for example \`'thing-a' | 'thing-b'\` rather than \`string\`.`,
253
+ );
254
+ }
255
+ }
256
+
257
+ // Otherwise if this is on a top level invocation, we're trying to use a template-unaware
258
+ // value in a template-specific way.
259
+ let nodeType = sourceMapping?.sourceNode.type;
260
+ if (nodeType === 'ElementNode' || nodeType === 'PathExpression' || nodeType === 'Identifier') {
261
+ return addGlintDetails(
262
+ diagnostic,
263
+ 'The given value does not appear to be usable as a component, modifier or helper.',
264
+ );
265
+ }
266
+ }
267
+
268
+ function checkImplicitAnyError(
269
+ diagnostic: Diagnostic,
270
+ mapping: GlimmerASTMappingTree,
271
+ ): Diagnostic | undefined {
272
+ let message = diagnostic.messageText;
273
+
274
+ // We don't want to bake in assumptions about the exact format of TS error messages,
275
+ // but we can assume that the name of the type we're indexing (`Globals`) will appear
276
+ // in the text in the case we're interested in.
277
+ if (typeof message === 'string' && message.includes('Globals')) {
278
+ let { sourceNode } = mapping;
279
+
280
+ // This error may appear either on `<Foo />` or `{{foo}}`/`(foo)`
281
+ let globalName =
282
+ sourceNode.type === 'ElementNode'
283
+ ? sourceNode.path.head.original
284
+ : sourceNode.type === 'PathExpression' && sourceNode.head.type === 'VarHead'
285
+ ? sourceNode.head.name
286
+ : null;
287
+
288
+ if (globalName) {
289
+ return addGlintDetails(
290
+ diagnostic,
291
+ `Unknown name '${globalName}'. If this isn't a typo, you may be missing a registry entry ` +
292
+ `for this value; see the Template Registry page in the Glint documentation for more details.`,
293
+ );
294
+ }
295
+ }
296
+ }
297
+
298
+ function checkIndexAccessError(
299
+ diagnostic: Diagnostic,
300
+ mapping: GlimmerASTMappingTree,
301
+ ): Diagnostic | undefined {
302
+ if (mapping.sourceNode.type === 'Identifier' && typeof diagnostic.messageText === 'string') {
303
+ let message = diagnostic.messageText;
304
+
305
+ // "accessed with ['x']" => "accessed with {{get ... 'x'}}"
306
+ return {
307
+ ...diagnostic,
308
+ messageText: message.replace(/\[(['"])(.*)\1\]/, `{{get ... $1$2$1}}`),
309
+ };
310
+ }
311
+ }
312
+
313
+ function addGlintDetails(diagnostic: Diagnostic, details: string): Diagnostic {
314
+ return {
315
+ ...diagnostic,
316
+ messageText: `${details}\n${diagnostic.messageText}`,
317
+ };
318
+ }
319
+
320
+ // Find the nearest mapping node at or above the given one whose `source` AST node
321
+ // matches one of the given types.
322
+ function findAncestor<K extends MappingSource['type']>(
323
+ mapping: GlimmerASTMappingTree,
324
+ ...types: Array<K>
325
+ ): Extract<MappingSource, { type: K }> | null {
326
+ let current: GlimmerASTMappingTree | null = mapping;
327
+ do {
328
+ if (types.includes(current.sourceNode.type as K)) {
329
+ return current.sourceNode as Extract<MappingSource, { type: K }>;
330
+ }
331
+ } while ((current = current.parent));
332
+ return null;
333
+ }
@@ -0,0 +1,5 @@
1
+ import type * as ts from 'typescript';
2
+
3
+ export type Diagnostic = ts.Diagnostic & {
4
+ isContentTagError?: boolean;
5
+ };
@@ -0,0 +1,4 @@
1
+ export type { Directive, default as TransformedModule } from './template/transformed-module.js';
2
+ export type { Diagnostic } from './diagnostics/index.js';
3
+
4
+ export { rewriteModule } from './template/rewrite-module.js';
@@ -0,0 +1,30 @@
1
+ import { CodeInformation } from '@volar/language-server/node.js';
2
+
3
+ /**
4
+ * Based on https://github.com/machty/vue-language-tools/blob/deff856aa8c15f691544e646519215a15aacbef1/packages/language-core/lib/codegen/codeFeatures.ts
5
+ *
6
+ * This file exports a "code features" object which provides a useful shorthand/shortcut
7
+ * for specifying a nubmer of Volar CodeInformation configuration options in the code
8
+ * we use for mapping between source code (e.g. .gts files) and transformed (i.e. type-checkable TS) code.
9
+ *
10
+ * Volar uses the CodeInformation we pass along with each mapping to determine what sort of language
11
+ * features should be activated/processed for a given span of code.
12
+ */
13
+ const raw = {
14
+ all: {
15
+ verification: true,
16
+ completion: true,
17
+ semantic: true,
18
+ navigation: true,
19
+ },
20
+ withoutHighlight: {
21
+ semantic: { shouldHighlight: () => false },
22
+ verification: true,
23
+ navigation: true,
24
+ completion: true,
25
+ },
26
+ } satisfies Record<string, CodeInformation>;
27
+
28
+ export const codeFeatures = raw as {
29
+ [K in keyof typeof raw]: CodeInformation;
30
+ };
@@ -0,0 +1,173 @@
1
+ import { AST } from '@glimmer/syntax';
2
+ import { Range } from './transformed-module.js';
3
+ import { Identifier } from './map-template-contents.js';
4
+ import { CodeInformation } from '@volar/language-server/node.js';
5
+
6
+ export type MappingSource = AST.Node | TemplateEmbedding | TextContent | Identifier | ParseError;
7
+
8
+ /**
9
+ * In cases where we're unable to parse a template, we still want to
10
+ * be able to hold a placeholder mapping so that we can respond sensibly
11
+ * to offset transformation queries. This class acts as a standin for
12
+ * the proper AST node we were unable to obtain in such cases.
13
+ */
14
+ export class ParseError {
15
+ public readonly type = 'ParseError';
16
+ }
17
+
18
+ /**
19
+ * The Glimmer AST uses `TextNode` for both string arg values on elements
20
+ * and for top-level text content floating in the DOM itself. Since we
21
+ * want to treat the two differently (namely, string args may have useful
22
+ * completion suggestions but plain text doesn't), we use a stand-in
23
+ * node for the latter.
24
+ */
25
+ export class TextContent {
26
+ public readonly type = 'TextContent';
27
+ }
28
+
29
+ /**
30
+ * This node represents the root of an embedded template, including
31
+ * the surrounding `<template>...</template>` boilerplate that designates
32
+ * the surrounded contents as a template.
33
+ */
34
+ export class TemplateEmbedding {
35
+ public readonly type = 'TemplateEmbedding';
36
+ }
37
+
38
+ /**
39
+ * A `MappingTree` maintains a hierarchy of mappings between ranges of
40
+ * locations in original and transformed source strings. These mappings
41
+ * are naturally hierarchical due to the tree structure of the underlying
42
+ * code.
43
+ *
44
+ * For instance, given an expression like `{{foo.bar}}` in a template, a
45
+ * corresponding expression in TypeScript might be `foo?.bar`. The individual
46
+ * identifiers `foo` and `bar` map directly to one another, but the full
47
+ * expressions do as well. By maintaining a full hierarchy of these mappings,
48
+ * we can always report diagnostics in the template at roughly the same
49
+ * level of granularity as TS itself uses when reporting on the transformed
50
+ * output.
51
+ */
52
+ export default class GlimmerASTMappingTree {
53
+ public parent: GlimmerASTMappingTree | null = null;
54
+
55
+ public constructor(
56
+ public transformedRange: Range,
57
+ public originalRange: Range,
58
+ public children: Array<GlimmerASTMappingTree> = [],
59
+ public sourceNode: MappingSource,
60
+ public codeInformation?: CodeInformation,
61
+ ) {
62
+ children.forEach((child) => (child.parent = this));
63
+ }
64
+
65
+ /**
66
+ * Returns the mapping corresponding to the smallest span in the transformed source
67
+ * that contains the given range, or `null` if that range doesn't fall within
68
+ * this mapping tree.
69
+ */
70
+ public narrowestMappingForTransformedRange(range: Range): GlimmerASTMappingTree | null {
71
+ if (range.start < this.transformedRange.start || range.end > this.transformedRange.end) {
72
+ return null;
73
+ }
74
+
75
+ for (let child of this.children) {
76
+ let mapping = child.narrowestMappingForTransformedRange(range);
77
+ if (mapping) {
78
+ return mapping;
79
+ }
80
+ }
81
+
82
+ return this;
83
+ }
84
+
85
+ /**
86
+ * Returns the mapping corresponding to the smallest span in the original source
87
+ * that contains the given range, or `null` if that range doesn't fall within
88
+ * this mapping tree.
89
+ */
90
+ public narrowestMappingForOriginalRange(range: Range): GlimmerASTMappingTree | null {
91
+ if (range.start < this.originalRange.start || range.end > this.originalRange.end) {
92
+ return null;
93
+ }
94
+
95
+ for (let child of this.children) {
96
+ let mapping = child.narrowestMappingForOriginalRange(range);
97
+ if (mapping) {
98
+ return mapping;
99
+ }
100
+ }
101
+
102
+ return this;
103
+ }
104
+
105
+ /**
106
+ * Returns all mappings that exactly match the provided range.
107
+ */
108
+ public exactMappingsForOriginalRange(range: Range): GlimmerASTMappingTree[] {
109
+ const results: GlimmerASTMappingTree[] = [];
110
+
111
+ if (range.start < this.originalRange.start || range.end > this.originalRange.end) {
112
+ return results;
113
+ }
114
+
115
+ if (range.start == this.originalRange.start && range.end == this.originalRange.end) {
116
+ results.push(this);
117
+ }
118
+
119
+ for (let child of this.children) {
120
+ results.push(...child.exactMappingsForOriginalRange(range));
121
+ }
122
+
123
+ return results;
124
+ }
125
+
126
+ public toDebugString(options: {
127
+ originalStart: number;
128
+ originalSource: string;
129
+ transformedStart: number;
130
+ transformedSource: string;
131
+ indent?: string;
132
+ }): string {
133
+ let { originalSource, transformedSource, indent = '| ' } = options;
134
+ let { sourceNode, originalRange, transformedRange, children } = this;
135
+ let hbsStart = options.originalStart + originalRange.start;
136
+ let hbsEnd = options.originalStart + originalRange.end;
137
+ let tsStart = options.transformedStart + transformedRange.start;
138
+ let tsEnd = options.transformedStart + transformedRange.end;
139
+ let lines = [];
140
+
141
+ lines.push(`${indent}Mapping: ${sourceNode.type}`);
142
+
143
+ lines.push(
144
+ `${indent}${` hbs(${hbsStart}:${hbsEnd}):`.padEnd(15)}${this.getSourceRange(
145
+ originalSource,
146
+ originalRange,
147
+ )}`,
148
+ );
149
+
150
+ lines.push(
151
+ `${indent}${` ts(${tsStart}:${tsEnd}):`.padEnd(15)}${this.getSourceRange(
152
+ transformedSource,
153
+ transformedRange,
154
+ )}`,
155
+ );
156
+
157
+ lines.push(indent);
158
+
159
+ for (let child of children) {
160
+ lines.push(child.toDebugString({ ...options, indent: indent + '| ' }));
161
+ }
162
+
163
+ if (children.length) {
164
+ lines.push(indent);
165
+ }
166
+
167
+ return lines.map((line) => line.trimEnd()).join('\n');
168
+ }
169
+
170
+ private getSourceRange(source: string, range: Range): string {
171
+ return source.slice(range.start, range.end).trim().replace(/\n/g, '\\n').replace(/\r/g, '\\r');
172
+ }
173
+ }
@@ -0,0 +1,33 @@
1
+ import type ts from 'typescript';
2
+ import { CorrelatedSpan, Directive, TransformError } from '../transformed-module.js';
3
+ import { TSLib } from '../../util.js';
4
+
5
+ export type PartialCorrelatedSpan = Omit<CorrelatedSpan, 'transformedStart' | 'transformedLength'>;
6
+
7
+ export type CorrelatedSpansResult = {
8
+ errors: Array<TransformError>;
9
+ directives: Array<Directive>;
10
+ partialSpans: Array<PartialCorrelatedSpan>;
11
+ };
12
+
13
+ /**
14
+ * Given an AST node for an embedded template, determines whether it's embedded
15
+ * within a class in such a way that that class should be treated as its backing
16
+ * value.
17
+ */
18
+ export function isEmbeddedInClass(ts: TSLib, node: ts.Node): boolean {
19
+ let current: ts.Node | null = node;
20
+ do {
21
+ // TODO: this should likely actually filter on whether the template appears in a
22
+ // static block or property definition, but just "am I in a class body" is the
23
+ // current status quo and has been ok so far.
24
+ if (ts.isHeritageClause(current)) {
25
+ return false;
26
+ }
27
+ if (ts.isClassLike(current)) {
28
+ return true;
29
+ }
30
+ } while ((current = current.parent));
31
+
32
+ return false;
33
+ }